1047 Zeilen
35 KiB
C++
1047 Zeilen
35 KiB
C++
/**
|
|
* server/src/network/session.cpp
|
|
*
|
|
* This file is part of the traintastic source code.
|
|
*
|
|
* Copyright (C) 2019-2022 Reinder Feenstra
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "session.hpp"
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/uuid/random_generator.hpp>
|
|
#include "../traintastic/traintastic.hpp"
|
|
#include "client.hpp"
|
|
#include <traintastic/enum/interfaceitemtype.hpp>
|
|
#include <traintastic/enum/attributetype.hpp>
|
|
#ifndef NDEBUG
|
|
#include "../core/eventloop.hpp" // for: isEventLoopThread()
|
|
#endif
|
|
#include "../core/tablemodel.hpp"
|
|
#include "../log/log.hpp"
|
|
#include "../log/memorylogger.hpp"
|
|
#include "../board/tile/tiles.hpp"
|
|
|
|
#ifdef GetObject
|
|
#undef GetObject // GetObject is defined by a winapi header
|
|
#endif
|
|
|
|
Session::Session(const std::shared_ptr<Client>& client) :
|
|
m_client{client},
|
|
m_uuid{boost::uuids::random_generator()()}
|
|
{
|
|
assert(isEventLoopThread());
|
|
}
|
|
|
|
Session::~Session()
|
|
{
|
|
assert(isEventLoopThread());
|
|
m_memoryLoggerChanged.disconnect();
|
|
for(const auto& it : m_objectSignals)
|
|
it.second.disconnect();
|
|
}
|
|
|
|
bool Session::processMessage(const Message& message)
|
|
{
|
|
switch(message.command())
|
|
{
|
|
case Message::Command::GetObject:
|
|
{
|
|
std::string id;
|
|
message.read(id);
|
|
|
|
std::vector<std::string> ids;
|
|
boost::split(ids, id, [](char c){ return c == '.'; });
|
|
auto it = ids.cbegin();
|
|
|
|
ObjectPtr obj;
|
|
if(*it == Traintastic::classId)
|
|
obj = Traintastic::instance;
|
|
else if(Traintastic::instance->world)
|
|
obj = Traintastic::instance->world->getObjectById(*it);
|
|
|
|
while(obj && ++it != ids.cend())
|
|
{
|
|
if(AbstractProperty* property = obj->getProperty(*it); property && property->type() == ValueType::Object)
|
|
obj = property->toObject();
|
|
else if(AbstractVectorProperty* vectorProperty = obj->getVectorProperty(*it); vectorProperty && vectorProperty->type() == ValueType::Object)
|
|
{
|
|
obj = nullptr;
|
|
const size_t size = vectorProperty->size();
|
|
for(size_t i = 0; i < size; i++)
|
|
{
|
|
ObjectPtr v = vectorProperty->getObject(i);
|
|
if(id == v->getObjectId())
|
|
{
|
|
obj = v;
|
|
it++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
obj = nullptr;
|
|
}
|
|
|
|
if(obj)
|
|
{
|
|
auto response = Message::newResponse(message.command(), message.requestId());
|
|
writeObject(*response, obj);
|
|
m_client->sendMessage(std::move(response));
|
|
}
|
|
else
|
|
m_client->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), Message::ErrorCode::UnknownObject));
|
|
|
|
return true;
|
|
}
|
|
case Message::Command::ReleaseObject:
|
|
{
|
|
// client counter value must match server counter value,
|
|
// to make sure no handles are "on the wire"
|
|
//
|
|
const Handle handle = message.read<Handle>();
|
|
const uint32_t counter = message.read<uint32_t>();
|
|
if(counter == m_handles.getCounter(handle))
|
|
{
|
|
m_handles.removeHandle(handle);
|
|
m_objectSignals.erase(handle);
|
|
|
|
auto event = Message::newEvent(message.command(), sizeof(Handle));
|
|
event->write(handle);
|
|
m_client->sendMessage(std::move(event));
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::ObjectSetProperty:
|
|
{
|
|
if(message.isRequest() || message.isEvent())
|
|
{
|
|
if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
|
|
{
|
|
if(AbstractProperty* property = object->getProperty(message.read<std::string>()); property && !property->isInternal())
|
|
{
|
|
try
|
|
{
|
|
switch(message.read<ValueType>())
|
|
{
|
|
case ValueType::Boolean:
|
|
property->fromBool(message.read<bool>());
|
|
break;
|
|
|
|
case ValueType::Integer:
|
|
property->fromInt64(message.read<int64_t>());
|
|
break;
|
|
|
|
case ValueType::Float:
|
|
property->fromDouble(message.read<double>());
|
|
break;
|
|
|
|
case ValueType::String:
|
|
property->fromString(message.read<std::string>());
|
|
break;
|
|
|
|
default:
|
|
throw std::runtime_error("invalid value type");
|
|
}
|
|
}
|
|
catch(const std::exception& e) // set property failed
|
|
{
|
|
if(message.isRequest()) // send error response
|
|
m_client->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), e.what()));
|
|
else // send changed event with current value:
|
|
objectPropertyChanged(*property);
|
|
}
|
|
|
|
if(message.isRequest()) // send success response
|
|
m_client->sendMessage(Message::newResponse(message.command(), message.requestId()));
|
|
}
|
|
else if(message.isRequest()) // send error response
|
|
m_client->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), "unknown property"));
|
|
}
|
|
else if(message.isRequest()) // send error response
|
|
m_client->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), "unknown object"));
|
|
}
|
|
return true;
|
|
}
|
|
case Message::Command::ObjectSetUnitPropertyUnit:
|
|
{
|
|
if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
|
|
{
|
|
if(AbstractUnitProperty* property = dynamic_cast<AbstractUnitProperty*>(object->getProperty(message.read<std::string>())); property && !property->isInternal())
|
|
{
|
|
try
|
|
{
|
|
property->setUnitValue(message.read<int64_t>());
|
|
}
|
|
catch(const std::exception&)
|
|
{
|
|
// set unit property unit failed, send changed event with current value:
|
|
objectPropertyChanged(*property);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::ObjectSetObjectPropertyById:
|
|
{
|
|
if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
|
|
{
|
|
if(AbstractObjectProperty* property = dynamic_cast<AbstractObjectProperty*>(object->getProperty(message.read<std::string>())); property && !property->isInternal())
|
|
{
|
|
try
|
|
{
|
|
const std::string id = message.read<std::string>();
|
|
if(!id.empty())
|
|
{
|
|
if(ObjectPtr obj = Traintastic::instance->world->getObjectByPath(id))
|
|
property->fromObject(obj);
|
|
else
|
|
throw std::runtime_error("");
|
|
}
|
|
else
|
|
property->fromObject(nullptr);
|
|
}
|
|
catch(const std::exception&)
|
|
{
|
|
// set object property failed, send changed event with current value:
|
|
objectPropertyChanged(*property);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::ObjectCallMethod:
|
|
{
|
|
if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
|
|
{
|
|
if(AbstractMethod* method = object->getMethod(message.read<std::string>()); method && !method->isInternal())
|
|
{
|
|
const ValueType resultType = message.read<ValueType>();
|
|
const uint8_t argumentCount = message.read<uint8_t>();
|
|
|
|
Arguments args;
|
|
for(uint8_t i = 0; i < argumentCount; i++)
|
|
{
|
|
switch(message.read<ValueType>())
|
|
{
|
|
case ValueType::Boolean:
|
|
args.push_back(message.read<bool>());
|
|
break;
|
|
|
|
case ValueType::Enum:
|
|
case ValueType::Integer:
|
|
case ValueType::Set:
|
|
args.push_back(message.read<int64_t>());
|
|
break;
|
|
|
|
case ValueType::Float:
|
|
args.push_back(message.read<double>());
|
|
break;
|
|
|
|
case ValueType::String:
|
|
{
|
|
std::string arg = message.read<std::string>();
|
|
if(i < method->argumentTypeInfo().size() && method->argumentTypeInfo()[i].type == ValueType::Object)
|
|
{
|
|
if(arg.empty())
|
|
args.push_back(ObjectPtr());
|
|
else if(ObjectPtr obj = Traintastic::instance->world->getObjectByPath(arg))
|
|
args.push_back(obj);
|
|
else
|
|
args.push_back(arg);
|
|
}
|
|
else
|
|
args.push_back(arg);
|
|
break;
|
|
}
|
|
case ValueType::Object:
|
|
args.push_back(m_handles.getItem(message.read<Handle>()));
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
AbstractMethod::Result result = method->call(args);
|
|
|
|
if(message.isRequest())
|
|
{
|
|
auto response = Message::newResponse(message.command(), message.requestId());
|
|
|
|
switch(resultType)
|
|
{
|
|
case ValueType::Boolean:
|
|
response->write(std::get<bool>(result));
|
|
break;
|
|
|
|
case ValueType::Integer:
|
|
case ValueType::Enum:
|
|
case ValueType::Set:
|
|
response->write(std::get<int64_t>(result));
|
|
break;
|
|
|
|
case ValueType::Float:
|
|
response->write(std::get<double>(result));
|
|
break;
|
|
|
|
case ValueType::String:
|
|
response->write(std::get<std::string>(result));
|
|
break;
|
|
|
|
case ValueType::Object:
|
|
writeObject(*response, std::get<ObjectPtr>(result));
|
|
break;
|
|
|
|
case ValueType::Invalid:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
m_client->sendMessage(std::move(response));
|
|
return true;
|
|
}
|
|
}
|
|
catch(const std::exception& e)
|
|
{
|
|
if(message.isRequest())
|
|
{
|
|
m_client->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), Message::ErrorCode::Failed));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::GetTableModel:
|
|
{
|
|
if(ObjectPtr object = m_handles.getItem(message.read<Handle>()))
|
|
{
|
|
if(Table* table = dynamic_cast<Table*>(object.get()))
|
|
{
|
|
TableModelPtr model = table->getModel();
|
|
assert(model);
|
|
auto response = Message::newResponse(message.command(), message.requestId());
|
|
writeTableModel(*response, model);
|
|
m_client->sendMessage(std::move(response));
|
|
|
|
model->columnHeadersChanged = [this](const TableModelPtr& tableModel)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::TableModelColumnHeadersChanged);
|
|
event->write(m_handles.getHandle(std::dynamic_pointer_cast<Object>(tableModel)));
|
|
event->write(tableModel->columnCount());
|
|
for(const auto& text : tableModel->columnHeaders())
|
|
event->write(text);
|
|
m_client->sendMessage(std::move(event));
|
|
};
|
|
|
|
model->rowCountChanged = [this](const TableModelPtr& tableModel)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::TableModelRowCountChanged);
|
|
event->write(m_handles.getHandle(std::dynamic_pointer_cast<Object>(tableModel)));
|
|
event->write(tableModel->rowCount());
|
|
m_client->sendMessage(std::move(event));
|
|
};
|
|
|
|
model->updateRegion = [this](const TableModelPtr& tableModel, const TableModel::Region& region)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::TableModelUpdateRegion);
|
|
event->write(m_handles.getHandle(std::dynamic_pointer_cast<Object>(tableModel)));
|
|
event->write(region.columnMin);
|
|
event->write(region.columnMax);
|
|
event->write(region.rowMin);
|
|
event->write(region.rowMax);
|
|
|
|
for(uint32_t row = region.rowMin; row <= region.rowMax; row++)
|
|
for(uint32_t column = region.columnMin; column <= region.columnMax; column++)
|
|
event->write(tableModel->getText(column, row));
|
|
|
|
m_client->sendMessage(std::move(event));
|
|
};
|
|
|
|
return true;
|
|
}
|
|
}
|
|
m_client->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), Message::ErrorCode::ObjectNotTable));
|
|
return true;
|
|
}
|
|
case Message::Command::ReleaseTableModel:
|
|
m_handles.removeHandle(message.read<Handle>());
|
|
break;
|
|
|
|
case Message::Command::TableModelSetRegion:
|
|
{
|
|
TableModelPtr model = std::dynamic_pointer_cast<TableModel>(m_handles.getItem(message.read<Handle>()));
|
|
if(model)
|
|
{
|
|
TableModel::Region region;
|
|
message.read(region.columnMin);
|
|
message.read(region.columnMax);
|
|
message.read(region.rowMin);
|
|
message.read(region.rowMax);
|
|
model->setRegion(region);
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::InputMonitorGetInputInfo:
|
|
{
|
|
auto inputMonitor = std::dynamic_pointer_cast<InputMonitor>(m_handles.getItem(message.read<Handle>()));
|
|
if(inputMonitor)
|
|
{
|
|
auto inputInfo = inputMonitor->getInputInfo();
|
|
auto response = Message::newResponse(message.command(), message.requestId());
|
|
response->write<uint32_t>(inputInfo.size());
|
|
for(auto& info : inputInfo)
|
|
{
|
|
response->write(info.address);
|
|
response->write(info.id);
|
|
response->write(info.value);
|
|
}
|
|
m_client->sendMessage(std::move(response));
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::OutputKeyboardGetOutputInfo:
|
|
{
|
|
auto outputKeyboard = std::dynamic_pointer_cast<OutputKeyboard>(m_handles.getItem(message.read<Handle>()));
|
|
if(outputKeyboard)
|
|
{
|
|
auto outputInfo = outputKeyboard->getOutputInfo();
|
|
auto response = Message::newResponse(message.command(), message.requestId());
|
|
response->write<uint32_t>(outputInfo.size());
|
|
for(auto& info : outputInfo)
|
|
{
|
|
response->write(info.address);
|
|
response->write(info.id);
|
|
response->write(info.value);
|
|
}
|
|
m_client->sendMessage(std::move(response));
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::OutputKeyboardSetOutputValue:
|
|
{
|
|
auto outputKeyboard = std::dynamic_pointer_cast<OutputKeyboard>(m_handles.getItem(message.read<Handle>()));
|
|
if(outputKeyboard)
|
|
{
|
|
const uint32_t address = message.read<uint32_t>();
|
|
const bool value = message.read<bool>();
|
|
outputKeyboard->setOutputValue(address, value);
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::BoardGetTileData:
|
|
{
|
|
auto board = std::dynamic_pointer_cast<Board>(m_handles.getItem(message.read<Handle>()));
|
|
if(board)
|
|
{
|
|
auto response = Message::newResponse(message.command(), message.requestId());
|
|
for(const auto& it : board->tileMap())
|
|
{
|
|
const Tile& tile = *(it.second);
|
|
if(it.first != tile.location()) // only tiles at origin
|
|
continue;
|
|
response->write(tile.location());
|
|
response->write(tile.data());
|
|
assert(tile.data().isActive() == isActive(tile.data().id()));
|
|
if(tile.data().isActive())
|
|
writeObject(*response, it.second);
|
|
}
|
|
m_client->sendMessage(std::move(response));
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::BoardGetTileInfo:
|
|
{
|
|
auto response = Message::newResponse(message.command(), message.requestId());
|
|
const auto& info = Tiles::getInfo();
|
|
response->write<uint32_t>(info.size());
|
|
for(const auto& item : info)
|
|
{
|
|
response->writeBlock();
|
|
response->write(item.classId);
|
|
response->write(item.tileId);
|
|
response->write(item.rotates);
|
|
response->write(item.menu);
|
|
response->writeBlockEnd();
|
|
}
|
|
m_client->sendMessage(std::move(response));
|
|
return true;
|
|
}
|
|
case Message::Command::OutputMapGetItems:
|
|
{
|
|
if(auto outputMap = std::dynamic_pointer_cast<OutputMap>(m_handles.getItem(message.read<Handle>())))
|
|
{
|
|
auto response = Message::newResponse(message.command(), message.requestId());
|
|
for(auto& item : outputMap->items())
|
|
writeObject(*response, item);
|
|
m_client->sendMessage(std::move(response));
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::OutputMapGetOutputs:
|
|
{
|
|
if(auto outputMap = std::dynamic_pointer_cast<OutputMap>(m_handles.getItem(message.read<Handle>())))
|
|
{
|
|
auto response = Message::newResponse(message.command(), message.requestId());
|
|
for(const auto& item : outputMap->outputs())
|
|
writeObject(*response, item);
|
|
m_client->sendMessage(std::move(response));
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case Message::Command::ServerLog:
|
|
if(message.read<bool>())
|
|
{
|
|
if(auto* logger = Log::getMemoryLogger())
|
|
{
|
|
m_memoryLoggerChanged = logger->changed.connect(std::bind(&Session::memoryLoggerChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
|
memoryLoggerChanged(*logger, logger->size(), 0);
|
|
}
|
|
}
|
|
else // disable
|
|
m_memoryLoggerChanged.disconnect();
|
|
break;
|
|
|
|
case Message::Command::ImportWorld:
|
|
if(message.isRequest())
|
|
{
|
|
std::vector<std::byte> worldData;
|
|
message.read(worldData);
|
|
if(Traintastic::instance->importWorld(worldData))
|
|
m_client->sendMessage(Message::newResponse(message.command(), message.requestId()));
|
|
else
|
|
m_client->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), Message::ErrorCode::Failed));
|
|
}
|
|
break;
|
|
|
|
case Message::Command::ExportWorld:
|
|
if(message.isRequest())
|
|
{
|
|
if(Traintastic::instance->world)
|
|
{
|
|
std::vector<std::byte> worldData;
|
|
if(Traintastic::instance->world->export_(worldData))
|
|
{
|
|
auto response = Message::newResponse(message.command(), message.requestId());
|
|
response->write(worldData);
|
|
m_client->sendMessage(std::move(response));
|
|
return true;
|
|
}
|
|
}
|
|
m_client->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), Message::ErrorCode::Failed));
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Session::writeObject(Message& message, const ObjectPtr& object)
|
|
{
|
|
message.writeBlock(); // object
|
|
|
|
auto handle = m_handles.getHandle(object);
|
|
if(handle == Handles::invalidHandle)
|
|
{
|
|
using namespace std::placeholders;
|
|
|
|
handle = m_handles.addItem(object);
|
|
|
|
m_objectSignals.emplace(handle, object->onDestroying.connect(std::bind(&Session::objectDestroying, this, std::placeholders::_1)));
|
|
m_objectSignals.emplace(handle, object->propertyChanged.connect(std::bind(&Session::objectPropertyChanged, this, std::placeholders::_1)));
|
|
m_objectSignals.emplace(handle, object->attributeChanged.connect(std::bind(&Session::objectAttributeChanged, this, std::placeholders::_1)));
|
|
|
|
if(auto* inputMonitor = dynamic_cast<InputMonitor*>(object.get()))
|
|
{
|
|
m_objectSignals.emplace(handle, inputMonitor->inputIdChanged.connect(std::bind(&Session::inputMonitorInputIdChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
|
|
m_objectSignals.emplace(handle, inputMonitor->inputValueChanged.connect(std::bind(&Session::inputMonitorInputValueChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
|
|
}
|
|
else if(auto* outputKeyboard = dynamic_cast<OutputKeyboard*>(object.get()))
|
|
{
|
|
m_objectSignals.emplace(handle, outputKeyboard->outputIdChanged.connect(std::bind(&Session::outputKeyboardOutputIdChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
|
|
m_objectSignals.emplace(handle, outputKeyboard->outputValueChanged.connect(std::bind(&Session::outputKeyboardOutputValueChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
|
|
}
|
|
else if(auto* board = dynamic_cast<Board*>(object.get()))
|
|
{
|
|
m_objectSignals.emplace(handle, board->tileDataChanged.connect(std::bind(&Session::boardTileDataChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
|
|
}
|
|
else if(auto* outputMap = dynamic_cast<OutputMap*>(object.get()))
|
|
{
|
|
m_objectSignals.emplace(handle, outputMap->outputsChanged.connect(std::bind(&Session::outputMapOutputsChanged, this, std::placeholders::_1)));
|
|
}
|
|
|
|
bool hasPublicEvents = false;
|
|
|
|
message.write(handle);
|
|
message.write(object->getClassId());
|
|
|
|
message.writeBlock(); // items
|
|
const InterfaceItems& interfaceItems = object->interfaceItems();
|
|
for(const auto& name : interfaceItems.names())
|
|
{
|
|
InterfaceItem& item = interfaceItems[name];
|
|
|
|
if(item.isInternal())
|
|
continue;
|
|
|
|
message.writeBlock(); // item
|
|
message.write(name);
|
|
|
|
if(BaseProperty* baseProperty = dynamic_cast<BaseProperty*>(&item))
|
|
{
|
|
AbstractProperty* property = nullptr;
|
|
AbstractUnitProperty* unitProperty = nullptr;
|
|
AbstractVectorProperty* vectorProperty = nullptr;
|
|
|
|
if((property = dynamic_cast<AbstractProperty*>(baseProperty)))
|
|
{
|
|
if((unitProperty = dynamic_cast<AbstractUnitProperty*>(property)))
|
|
message.write(InterfaceItemType::UnitProperty);
|
|
else
|
|
message.write(InterfaceItemType::Property);
|
|
}
|
|
else if((vectorProperty = dynamic_cast<AbstractVectorProperty*>(baseProperty)))
|
|
message.write(InterfaceItemType::VectorProperty);
|
|
else
|
|
{
|
|
assert(false);
|
|
continue;
|
|
}
|
|
|
|
message.write(baseProperty->flags());
|
|
message.write(baseProperty->type());
|
|
|
|
if(baseProperty->type() == ValueType::Enum)
|
|
message.write(baseProperty->enumName());
|
|
else if(baseProperty->type() == ValueType::Set)
|
|
message.write(baseProperty->setName());
|
|
|
|
if(property)
|
|
{
|
|
writePropertyValue(message, *property);
|
|
|
|
if(unitProperty)
|
|
{
|
|
message.write(unitProperty->unitName());
|
|
message.write(unitProperty->unitValue());
|
|
}
|
|
}
|
|
else if(vectorProperty)
|
|
writeVectorPropertyValue(message, *vectorProperty);
|
|
else
|
|
assert(false);
|
|
}
|
|
else if(const AbstractMethod* method = dynamic_cast<const AbstractMethod*>(&item))
|
|
{
|
|
message.write(InterfaceItemType::Method);
|
|
message.write(method->resultTypeInfo().type);
|
|
message.write(static_cast<uint8_t>(method->argumentTypeInfo().size()));
|
|
for(const auto& info : method->argumentTypeInfo())
|
|
message.write(info.type);
|
|
}
|
|
else if(const auto* event = dynamic_cast<const AbstractEvent*>(&item))
|
|
{
|
|
hasPublicEvents = true;
|
|
|
|
message.write(InterfaceItemType::Event);
|
|
message.write(static_cast<uint8_t>(event->argumentTypeInfo().size()));
|
|
for(const auto& typeInfo : event->argumentTypeInfo())
|
|
writeTypeInfo(message, typeInfo);
|
|
}
|
|
else
|
|
assert(false);
|
|
|
|
message.writeBlock(); // attributes
|
|
|
|
for(const auto& it : item.attributes())
|
|
{
|
|
const AbstractAttribute& attribute = *it.second;
|
|
message.writeBlock(); // attribute
|
|
writeAttribute(message, attribute);
|
|
message.writeBlockEnd(); // end attribute
|
|
}
|
|
|
|
message.writeBlockEnd(); // end attributes
|
|
message.writeBlockEnd(); // end item
|
|
}
|
|
message.writeBlockEnd(); // end items
|
|
|
|
if(hasPublicEvents)
|
|
m_objectSignals.emplace(handle, object->onEventFired.connect(std::bind(&Session::objectEventFired, this, std::placeholders::_1, std::placeholders::_2)));
|
|
}
|
|
else
|
|
message.write(handle);
|
|
|
|
message.writeBlockEnd(); // end object
|
|
}
|
|
|
|
void Session::writeTableModel(Message& message, const TableModelPtr& model)
|
|
{
|
|
message.writeBlock(); // model
|
|
assert(m_handles.getHandle(std::dynamic_pointer_cast<Object>(model)) == Handles::invalidHandle);
|
|
auto handle = m_handles.addItem(std::dynamic_pointer_cast<Object>(model));
|
|
message.write(handle);
|
|
message.write(model->getClassId());
|
|
message.write(model->columnCount());
|
|
for(const auto& text : model->columnHeaders())
|
|
message.write(text);
|
|
message.write(model->rowCount());
|
|
message.writeBlockEnd(); // end model
|
|
}
|
|
|
|
void Session::memoryLoggerChanged(const MemoryLogger& logger, const uint32_t added, const uint32_t removed)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::ServerLog);
|
|
event->write(removed);
|
|
event->write(added);
|
|
|
|
const uint32_t size = logger.size();
|
|
for(uint32_t i = size - added; i < size; i++)
|
|
{
|
|
const auto& log = logger[i];
|
|
event->write((std::chrono::duration_cast<std::chrono::microseconds>(log.time.time_since_epoch())).count());
|
|
event->write(log.objectId);
|
|
event->write(log.message);
|
|
const size_t argc = log.args ? std::min<size_t>(log.args->size(), std::numeric_limits<uint8_t>::max()) : 0;
|
|
event->write(static_cast<uint8_t>(argc));
|
|
for(size_t j = 0; j < argc; j++)
|
|
event->write(log.args->at(j));
|
|
}
|
|
|
|
m_client->sendMessage(std::move(event));
|
|
}
|
|
|
|
void Session::objectDestroying(Object& object)
|
|
{
|
|
const auto handle = m_handles.getHandle(object.shared_from_this());
|
|
m_handles.removeHandle(handle);
|
|
m_objectSignals.erase(handle);
|
|
|
|
auto event = Message::newEvent(Message::Command::ObjectDestroyed, sizeof(Handle));
|
|
event->write(handle);
|
|
m_client->sendMessage(std::move(event));
|
|
}
|
|
|
|
void Session::objectPropertyChanged(BaseProperty& baseProperty)
|
|
{
|
|
if(baseProperty.isInternal())
|
|
return;
|
|
|
|
auto event = Message::newEvent(Message::Command::ObjectPropertyChanged);
|
|
event->write(m_handles.getHandle(baseProperty.object().shared_from_this()));
|
|
event->write(baseProperty.name());
|
|
event->write(baseProperty.type());
|
|
if(AbstractProperty* property = dynamic_cast<AbstractProperty*>(&baseProperty))
|
|
{
|
|
writePropertyValue(*event, *property);
|
|
|
|
if(AbstractUnitProperty* unitProperty = dynamic_cast<AbstractUnitProperty*>(property))
|
|
event->write(unitProperty->unitValue());
|
|
}
|
|
else if(AbstractVectorProperty* vectorProperty = dynamic_cast<AbstractVectorProperty*>(&baseProperty))
|
|
writeVectorPropertyValue(*event, *vectorProperty);
|
|
else
|
|
assert(false);
|
|
|
|
m_client->sendMessage(std::move(event));
|
|
}
|
|
|
|
void Session::writePropertyValue(Message& message , const AbstractProperty& property)
|
|
{
|
|
switch(property.type())
|
|
{
|
|
case ValueType::Boolean:
|
|
message.write(property.toBool());
|
|
break;
|
|
|
|
case ValueType::Enum:
|
|
case ValueType::Integer:
|
|
case ValueType::Set:
|
|
message.write(property.toInt64());
|
|
break;
|
|
|
|
case ValueType::Float:
|
|
message.write(property.toDouble());
|
|
break;
|
|
|
|
case ValueType::String:
|
|
message.write(property.toString());
|
|
break;
|
|
|
|
case ValueType::Object:
|
|
if(ObjectPtr obj = property.toObject())
|
|
message.write(obj->getObjectId());
|
|
else
|
|
message.write<std::string_view>("");
|
|
break;
|
|
|
|
case ValueType::Invalid:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Session::writeVectorPropertyValue(Message& message , const AbstractVectorProperty& vectorProperty)
|
|
{
|
|
const size_t size = vectorProperty.size();
|
|
message.write(static_cast<uint32_t>(size));
|
|
|
|
switch(vectorProperty.type())
|
|
{
|
|
case ValueType::Boolean:
|
|
for(size_t i = 0; i < size; i++)
|
|
message.write(vectorProperty.getBool(i));
|
|
break;
|
|
|
|
case ValueType::Enum:
|
|
case ValueType::Integer:
|
|
case ValueType::Set:
|
|
for(size_t i = 0; i < size; i++)
|
|
message.write(vectorProperty.getInt64(i));
|
|
break;
|
|
|
|
case ValueType::Float:
|
|
for(size_t i = 0; i < size; i++)
|
|
message.write(vectorProperty.getDouble(i));
|
|
break;
|
|
|
|
case ValueType::String:
|
|
for(size_t i = 0; i < size; i++)
|
|
message.write(vectorProperty.getString(i));
|
|
break;
|
|
|
|
case ValueType::Object:
|
|
for(size_t i = 0; i < size; i++)
|
|
if(ObjectPtr obj = vectorProperty.getObject(i))
|
|
message.write(obj->getObjectId());
|
|
else
|
|
message.write<std::string>("");
|
|
break;
|
|
|
|
case ValueType::Invalid:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Session::objectAttributeChanged(AbstractAttribute& attribute)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::ObjectAttributeChanged);
|
|
event->write(m_handles.getHandle(attribute.item().object().shared_from_this()));
|
|
event->write(attribute.item().name());
|
|
writeAttribute(*event, attribute);
|
|
m_client->sendMessage(std::move(event));
|
|
}
|
|
|
|
void Session::objectEventFired(const AbstractEvent& event, const Arguments& arguments)
|
|
{
|
|
auto message = Message::newEvent(Message::Command::ObjectEventFired);
|
|
message->write(m_handles.getHandle(event.object().shared_from_this()));
|
|
message->write(event.name());
|
|
message->write(static_cast<uint32_t>(arguments.size()));
|
|
size_t i = 0;
|
|
for(const auto& typeInfo : event.argumentTypeInfo())
|
|
{
|
|
switch(typeInfo.type)
|
|
{
|
|
case ValueType::Boolean:
|
|
message->write(std::get<bool>(arguments[i]));
|
|
break;
|
|
|
|
case ValueType::Enum:
|
|
case ValueType::Integer:
|
|
case ValueType::Set:
|
|
message->write(std::get<int64_t>(arguments[i]));
|
|
break;
|
|
|
|
case ValueType::Float:
|
|
message->write(std::get<double>(arguments[i]));
|
|
break;
|
|
|
|
case ValueType::String:
|
|
message->write(std::get<std::string>(arguments[i]));
|
|
break;
|
|
|
|
case ValueType::Object:
|
|
if(ObjectPtr obj = std::get<ObjectPtr>(arguments[i]))
|
|
message->write(obj->getObjectId());
|
|
else
|
|
message->write(std::string_view{});
|
|
break;
|
|
|
|
case ValueType::Invalid:
|
|
assert(false);
|
|
return;
|
|
}
|
|
i++;
|
|
}
|
|
m_client->sendMessage(std::move(message));
|
|
}
|
|
|
|
void Session::writeAttribute(Message& message , const AbstractAttribute& attribute)
|
|
{
|
|
message.write(attribute.name());
|
|
message.write(attribute.type());
|
|
if(const AbstractValueAttribute* valueAttribute = dynamic_cast<const AbstractValueAttribute*>(&attribute))
|
|
{
|
|
message.write(AttributeType::Value);
|
|
switch(attribute.type())
|
|
{
|
|
case ValueType::Boolean:
|
|
message.write(valueAttribute->toBool());
|
|
break;
|
|
|
|
case ValueType::Enum:
|
|
case ValueType::Integer:
|
|
message.write(valueAttribute->toInt64());
|
|
break;
|
|
|
|
case ValueType::Float:
|
|
message.write(valueAttribute->toDouble());
|
|
break;
|
|
|
|
case ValueType::String:
|
|
message.write(valueAttribute->toString());
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
else if(const AbstractValuesAttribute* valuesAttributes = dynamic_cast<const AbstractValuesAttribute*>(&attribute))
|
|
{
|
|
const uint32_t length = valuesAttributes->length();
|
|
message.write(AttributeType::Values);
|
|
message.write(length);
|
|
switch(attribute.type())
|
|
{
|
|
case ValueType::Boolean:
|
|
for(uint32_t i = 0; i < length; i++)
|
|
message.write(valuesAttributes->getBool(i));
|
|
break;
|
|
|
|
case ValueType::Enum:
|
|
case ValueType::Integer:
|
|
for(uint32_t i = 0; i < length; i++)
|
|
message.write(valuesAttributes->getInt64(i));
|
|
break;
|
|
|
|
case ValueType::Float:
|
|
for(uint32_t i = 0; i < length; i++)
|
|
message.write(valuesAttributes->getDouble(i));
|
|
break;
|
|
|
|
case ValueType::String:
|
|
for(uint32_t i = 0; i < length; i++)
|
|
message.write(valuesAttributes->getString(i));
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
assert(false);
|
|
}
|
|
|
|
void Session::writeTypeInfo(Message& message, const TypeInfo& typeInfo)
|
|
{
|
|
assert((typeInfo.type == ValueType::Enum) != typeInfo.enumName.empty());
|
|
assert((typeInfo.type == ValueType::Set) != typeInfo.setName.empty());
|
|
|
|
message.write(typeInfo.type);
|
|
if(typeInfo.type == ValueType::Enum)
|
|
message.write(typeInfo.enumName);
|
|
else if(typeInfo.type == ValueType::Set)
|
|
message.write(typeInfo.setName);
|
|
}
|
|
|
|
void Session::inputMonitorInputIdChanged(InputMonitor& inputMonitor, const uint32_t address, std::string_view id)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::InputMonitorInputIdChanged);
|
|
event->write(m_handles.getHandle(inputMonitor.shared_from_this()));
|
|
event->write(address);
|
|
event->write(id);
|
|
m_client->sendMessage(std::move(event));
|
|
}
|
|
|
|
void Session::inputMonitorInputValueChanged(InputMonitor& inputMonitor, const uint32_t address, const TriState value)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::InputMonitorInputValueChanged);
|
|
event->write(m_handles.getHandle(inputMonitor.shared_from_this()));
|
|
event->write(address);
|
|
event->write(value);
|
|
m_client->sendMessage(std::move(event));
|
|
}
|
|
|
|
void Session::outputKeyboardOutputIdChanged(OutputKeyboard& outputKeyboard, const uint32_t address, std::string_view id)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::OutputKeyboardOutputIdChanged);
|
|
event->write(m_handles.getHandle(outputKeyboard.shared_from_this()));
|
|
event->write(address);
|
|
event->write(id);
|
|
m_client->sendMessage(std::move(event));
|
|
}
|
|
|
|
void Session::outputKeyboardOutputValueChanged(OutputKeyboard& outputKeyboard, const uint32_t address, const TriState value)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::OutputKeyboardOutputValueChanged);
|
|
event->write(m_handles.getHandle(outputKeyboard.shared_from_this()));
|
|
event->write(address);
|
|
event->write(value);
|
|
m_client->sendMessage(std::move(event));
|
|
}
|
|
|
|
void Session::boardTileDataChanged(Board& board, const TileLocation& location, const TileData& data)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::BoardTileDataChanged);
|
|
event->write(m_handles.getHandle(board.shared_from_this()));
|
|
event->write(location);
|
|
event->write(data);
|
|
assert(data.isActive() == isActive(data.id()));
|
|
if(data.isActive())
|
|
{
|
|
auto tile = board.getTile(location);
|
|
assert(tile);
|
|
writeObject(*event, tile);
|
|
}
|
|
m_client->sendMessage(std::move(event));
|
|
}
|
|
|
|
void Session::outputMapOutputsChanged(OutputMap& outputMap)
|
|
{
|
|
auto event = Message::newEvent(Message::Command::OutputMapOutputsChanged);
|
|
event->write(m_handles.getHandle(outputMap.shared_from_this()));
|
|
for(const auto& item : outputMap.outputs())
|
|
writeObject(*event, item);
|
|
m_client->sendMessage(std::move(event));
|
|
}
|