2022-05-31 23:08:00 +02:00

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));
}