From 4497d75e7ad21be2e0bc76100efbf24f7d4ddfda Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Mon, 22 Jan 2024 22:33:00 +0100 Subject: [PATCH] added write support for VectorProperty --- client/src/network/abstractvectorproperty.hpp | 8 +- client/src/network/vectorproperty.cpp | 84 +++++++++++ client/src/network/vectorproperty.hpp | 8 +- server/src/core/vectorproperty.hpp | 130 +++++++++++++++++- server/src/network/session.cpp | 61 +++++++- shared/src/traintastic/network/message.hpp | 3 +- 6 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 client/src/network/vectorproperty.cpp diff --git a/client/src/network/abstractvectorproperty.hpp b/client/src/network/abstractvectorproperty.hpp index 087b9383..b4baa8c4 100644 --- a/client/src/network/abstractvectorproperty.hpp +++ b/client/src/network/abstractvectorproperty.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021,2024 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -60,6 +60,12 @@ class AbstractVectorProperty : public BaseProperty static_assert(is_set_v); return static_cast(getInt64(index)); } + + virtual void setBool(int /*index*/, bool /*value*/) { Q_ASSERT(false); } + virtual void setInt(int /*index*/, int /*value*/) { Q_ASSERT(false); } + virtual void setInt64(int /*index*/, qint64 /*value*/) { Q_ASSERT(false); } + virtual void setDouble(int /*index*/, double /*value*/) { Q_ASSERT(false); } + virtual void setString(int /*index*/, const QString& /*value*/) { Q_ASSERT(false); } }; #endif diff --git a/client/src/network/vectorproperty.cpp b/client/src/network/vectorproperty.cpp new file mode 100644 index 00000000..e982f7da --- /dev/null +++ b/client/src/network/vectorproperty.cpp @@ -0,0 +1,84 @@ +/** + * client/src/network/vectorproperty.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2024 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 "vectorproperty.hpp" +#include "connection.hpp" +#include "object.hpp" + +template +static void setVectorPropertyValue(VectorProperty& property, int index, const T& value) +{ + auto event = Message::newEvent(Message::Command::ObjectSetVectorProperty); + event->write(static_cast(property.parent())->handle()); + event->write(property.name().toLatin1()); + event->write(index); + + if constexpr(std::is_same_v) + { + event->write(ValueType::Boolean); + event->write(value); + } + else if constexpr(std::is_integral_v) + { + event->write(ValueType::Integer); + event->write(value); + } + else if constexpr(std::is_floating_point_v) + { + event->write(ValueType::Float); + event->write(value); + } + else if constexpr(std::is_same_v) + { + event->write(ValueType::String); + event->write(value.toUtf8()); + } + else + static_assert(sizeof(T) != sizeof(T)); + + property.object().connection()->send(event); +} + +void VectorProperty::setBool(int index, bool value) +{ + setVectorPropertyValue(*this, index, value); +} + +void VectorProperty::setInt(int index, int value) +{ + setVectorPropertyValue(*this, index, value); +} + +void VectorProperty::setInt64(int index, qint64 value) +{ + setVectorPropertyValue(*this, index, value); +} + +void VectorProperty::setDouble(int index, double value) +{ + setVectorPropertyValue(*this, index, value); +} + +void VectorProperty::setString(int index, const QString& value) +{ + setVectorPropertyValue(*this, index, value); +} diff --git a/client/src/network/vectorproperty.hpp b/client/src/network/vectorproperty.hpp index cfbd304f..a03d3f3b 100644 --- a/client/src/network/vectorproperty.hpp +++ b/client/src/network/vectorproperty.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021,2024 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -99,6 +99,12 @@ class VectorProperty : public AbstractVectorProperty Q_ASSERT(index >= 0 && index < size()); return m_values[index]; } + + void setBool(int index, bool value) final; + void setInt(int index, int value) final; + void setInt64(int index, qint64 value) final; + void setDouble(int index, double value) final; + void setString(int index, const QString& value) final; }; #endif diff --git a/server/src/core/vectorproperty.hpp b/server/src/core/vectorproperty.hpp index 54cc5dd4..96b3ddf6 100644 --- a/server/src/core/vectorproperty.hpp +++ b/server/src/core/vectorproperty.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021,2023 Reinder Feenstra + * Copyright (C) 2021,2023-2024 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -30,18 +30,48 @@ template class VectorProperty : public AbstractVectorProperty { + public: + using OnChanged = std::function(), const T&, T> value)>; + using OnSet = std::function; + private: std::vector m_values; + OnChanged m_onChanged; + OnSet m_onSet; public: using const_iterator = typename std::vector::const_iterator; + VectorProperty(Object& object, std::string_view name, PropertyFlags flags) + : AbstractVectorProperty(object, name, value_type::value, flags) + { + } + VectorProperty(Object& object, std::string_view name, std::initializer_list values, PropertyFlags flags) : AbstractVectorProperty(object, name, value_type::value, flags), m_values{values} { } + VectorProperty(Object& object, std::string_view name, std::initializer_list values, PropertyFlags flags, OnChanged onChanged) + : VectorProperty(object, name, values, flags) + { + m_onChanged = std::move(onChanged); + } + + VectorProperty(Object& object, std::string_view name, std::initializer_list values, PropertyFlags flags, OnChanged onChanged, OnSet onSet) + : VectorProperty(object, name, values, flags) + { + m_onChanged = std::move(onChanged); + m_onSet = std::move(onSet); + } + + VectorProperty(Object& object, std::string_view name, std::initializer_list values, PropertyFlags flags, std::nullptr_t, OnSet onSet) + : VectorProperty(object, name, values, flags) + { + m_onSet = std::move(onSet); + } + inline const_iterator begin() const { return m_values.begin(); } inline const_iterator end() const { return m_values.end(); } @@ -63,11 +93,81 @@ class VectorProperty : public AbstractVectorProperty return; else if(!isWriteable()) throw not_writable_error(); - else + + if constexpr(std::is_integral_v || std::is_floating_point_v) + { + if(auto it = m_attributes.find(AttributeName::Min); it != m_attributes.end()) + { + const T min = static_cast&>(*it->second).value(); + + if(value < min) + { + if constexpr(std::is_floating_point_v) + { + if(value > min - std::numeric_limits::epsilon() * 100) + value = min; + else + throw out_of_range_error(); + } + else + throw out_of_range_error(); + } + } + + if(auto it = m_attributes.find(AttributeName::Max); it != m_attributes.end()) + { + const T max = static_cast&>(*it->second).value(); + + if(value > max) + { + if constexpr(std::is_floating_point_v) + { + if(value < max + std::numeric_limits::epsilon() * 100) + value = max; + else + throw out_of_range_error(); + } + else + throw out_of_range_error(); + } + } + } + + if constexpr(std::is_enum_v && !is_set_v) + { + if(auto it = m_attributes.find(AttributeName::Values); it != m_attributes.end()) + { + if(auto* span = dynamic_cast*>(it->second.get())) + { + const auto values = span->values(); + if(std::find(values.begin(), values.end(), value) == values.end()) + throw out_of_range_error(); + } + else if(auto* vectorRef = dynamic_cast*>(it->second.get())) + { + const auto* values = vectorRef->values(); + if(!values || std::find(values->begin(), values->end(), value) == values->end()) + throw out_of_range_error(); + } + else if(auto* vector = dynamic_cast*>(it->second.get())) + { + const auto& values = vector->values(); + if(std::find(values.begin(), values.end(), value) == values.end()) + throw out_of_range_error(); + } + } + } + + if(!m_onSet || m_onSet(index, value)) { m_values[index] = value; + if(m_onChanged) + m_onChanged(index, m_values[index]); changed(); + return; } + + throw invalid_value_error(); } void setValuesInternal(std::vector values) @@ -112,6 +212,12 @@ class VectorProperty : public AbstractVectorProperty changed(); } + void clearInternal() + { + m_values.clear(); + changed(); + } + std::string_view enumName() const final { if constexpr(std::is_enum_v && !is_set_v) @@ -130,6 +236,26 @@ class VectorProperty : public AbstractVectorProperty return ""; } + inline const T& front() const + { + return m_values.front(); + } + + inline T& front() + { + return m_values.front(); + } + + inline const T& back() const + { + return m_values.back(); + } + + inline T& back() + { + return m_values.back(); + } + inline size_t size() const final { return m_values.size(); diff --git a/server/src/network/session.cpp b/server/src/network/session.cpp index 0be6d826..1672c924 100644 --- a/server/src/network/session.cpp +++ b/server/src/network/session.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2023 Reinder Feenstra + * Copyright (C) 2019-2024 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -195,6 +195,65 @@ bool Session::processMessage(const Message& message) } return true; } + case Message::Command::ObjectSetVectorProperty: + { + if(message.isRequest() || message.isEvent()) + { + if(ObjectPtr object = m_handles.getItem(message.read())) + { + if(AbstractVectorProperty* property = object->getVectorProperty(message.read()); property && !property->isInternal()) + { + try + { + const size_t index = message.read(); + + switch(message.read()) + { + case ValueType::Boolean: + property->setBool(index, message.read()); + break; + + case ValueType::Integer: + property->setInt64(index, message.read()); + break; + + case ValueType::Float: + property->setDouble(index, message.read()); + break; + + case ValueType::String: + property->setString(index, message.read()); + break; + + default: + throw std::runtime_error("invalid value type"); + } + } + catch(const std::exception& e) // set property failed + { + if(message.isRequest()) // send error response + { + m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1018_EXCEPTION_X, e.what())); + } + else // send changed event with current value: + objectPropertyChanged(*property); + } + + if(message.isRequest()) // send success response + m_connection->sendMessage(Message::newResponse(message.command(), message.requestId())); + } + else if(message.isRequest()) // send error response + { + m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1016_UNKNOWN_PROPERTY)); + } + } + else if(message.isRequest()) // send error response + { + m_connection->sendMessage(Message::newErrorResponse(message.command(), message.requestId(), LogMessage::C1015_UNKNOWN_OBJECT)); + } + } + return true; + } case Message::Command::ObjectSetUnitPropertyUnit: { if(ObjectPtr object = m_handles.getItem(message.read())) diff --git a/shared/src/traintastic/network/message.hpp b/shared/src/traintastic/network/message.hpp index f3da5507..d1520fb6 100644 --- a/shared/src/traintastic/network/message.hpp +++ b/shared/src/traintastic/network/message.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2023 Reinder Feenstra + * Copyright (C) 2019-2024 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -88,6 +88,7 @@ class Message ObjectGetObjectPropertyObject = 44, ObjectGetObjectVectorPropertyObject = 45, + ObjectSetVectorProperty = 46, Discover = 255, };