diff --git a/src/Core/Object.cpp b/src/Core/Object.cpp index dd95980..cdc4837 100644 --- a/src/Core/Object.cpp +++ b/src/Core/Object.cpp @@ -115,6 +115,7 @@ void Object::serialize(ArchiveT &ar, const unsigned int version) { } void Object::Updated() { ULIB_SIGNAL_EMIT(Object::Updated); } +void Object::PropertyUpdated() { ULIB_SIGNAL_EMIT(Object::PropertyUpdated); } template void Object::save_override(ArchiveT &ar, const unsigned int version) {} diff --git a/src/Core/Property.h b/src/Core/Property.h index fe30fbe..7f35875 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -42,6 +42,7 @@ public: virtual double GetMax() const { return 0; } virtual bool HasDefault() const { return false; } virtual std::string GetDefaultValueAsString() const { return ""; } + virtual bool IsReadOnly() const = 0; std::string GetQualifiedName() const { if (GetGroup().empty()) return GetName(); @@ -71,7 +72,7 @@ public: // PROXY: Use an existing variable as back-end storage Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "", const std::string& group = "") : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(false), - m_HasRange(false), m_HasDefault(false) { + m_HasRange(false), m_HasDefault(false), m_ReadOnly(false) { if (m_owner) { m_owner->RegisterProperty(this); } @@ -80,7 +81,7 @@ public: // MANAGED: Create and own internal storage Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "", const std::string& group = "") : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(new T(defaultValue)), m_own(true), - m_HasRange(false), m_HasDefault(true), m_Default(defaultValue) { + m_HasRange(false), m_HasDefault(true), m_Default(defaultValue), m_ReadOnly(false) { if (m_owner) { m_owner->RegisterProperty(this); } @@ -139,6 +140,9 @@ public: void SetRange(const T& min, const T& max) { m_Min = min; m_Max = max; m_HasRange = true; } void SetDefault(const T& def) { m_Default = def; m_HasDefault = true; } + void SetReadOnly(bool ro) { m_ReadOnly = ro; } + + virtual bool IsReadOnly() const override { return m_ReadOnly; } virtual bool HasRange() const override { return m_HasRange; } @@ -209,6 +213,7 @@ private: T m_Max; bool m_HasDefault; T m_Default; + bool m_ReadOnly; }; /** @@ -304,6 +309,25 @@ public: Property* p = new Property(m_Object, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); if (t.has_range()) p->SetRange(t.min_val(), t.max_val()); if (t.has_default()) p->SetDefault(t.default_val()); + p->SetReadOnly(t.is_read_only()); + m_Object->RegisterDynamicProperty(p); + } + } + + template + void save_override(const boost::serialization::hrp_val &t) { + if (m_Object) { + // Note: hrp_val stores by value. Property usually points to existing data. + // But here we are registering properties from HRP wrappers. + // If it's hrp_val, it means it's an rvalue from a getter. + // The hrp_val wrapper itself owns the value. + // However, the property_register_archive is temporary. + // This is a bit tricky. Usually HRP(rvalue) is meant for read-only display. + // Let's use the address of the value in the wrapper, but mark it read-only. + Property* p = new Property(m_Object, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); + if (t.has_range()) p->SetRange(t.min_val(), t.max_val()); + if (t.has_default()) p->SetDefault(t.default_val()); + p->SetReadOnly(t.is_read_only()); m_Object->RegisterDynamicProperty(p); } } @@ -312,6 +336,16 @@ public: void save_override(const boost::serialization::hrp_enum &t) { if (m_Object) { EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); + p->SetReadOnly(t.is_read_only()); + m_Object->RegisterDynamicProperty(p); + } + } + + template + void save_override(const boost::serialization::hrp_enum_val &t) { + if (m_Object) { + EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); + p->SetReadOnly(t.is_read_only()); m_Object->RegisterDynamicProperty(p); } } diff --git a/src/Core/Serializable.h b/src/Core/Serializable.h index 8fe0930..b08542b 100644 --- a/src/Core/Serializable.h +++ b/src/Core/Serializable.h @@ -102,6 +102,8 @@ public: bool has_default() const { return m_has_default; } const T& default_val() const { return m_default; } + static constexpr bool is_read_only() { return false; } + BOOST_SERIALIZATION_SPLIT_MEMBER() template @@ -115,11 +117,6 @@ public: } }; -template -inline hrp make_hrp(const char *name, T &t, const char* units = nullptr) { - return hrp(name, t, units); -} - template class hrp_enum : public boost::serialization::wrapper_traits> { const char *m_name; @@ -143,6 +140,8 @@ public: bool has_default() const { return m_has_default; } const T& default_val() const { return m_default; } + static constexpr bool is_read_only() { return false; } + BOOST_SERIALIZATION_SPLIT_MEMBER() template @@ -156,16 +155,120 @@ public: } }; +template +class hrp_val : public boost::serialization::wrapper_traits> { + const char *m_name; + const char *m_units; + T m_value; + bool m_has_range; + T m_min, m_max; + bool m_has_default; + T m_default; + +public: + explicit hrp_val(const char *name_, T t, const char* units_ = nullptr) + : m_name(name_), m_units(units_), m_value(t), m_has_range(false), m_has_default(false) {} + + hrp_val& range(const T& min_val, const T& max_val) { m_min = min_val; m_max = max_val; m_has_range = true; return *this; } + hrp_val& set_default(const T& def_val) { m_default = def_val; m_has_default = true; return *this; } + + const char *name() const { return this->m_name; } + const char *units() const { return this->m_units; } + T &value() { return this->m_value; } + const T &const_value() const { return this->m_value; } + + bool has_range() const { return m_has_range; } + const T& min_val() const { return m_min; } + const T& max_val() const { return m_max; } + bool has_default() const { return m_has_default; } + const T& default_val() const { return m_default; } + + static constexpr bool is_read_only() { return true; } + + BOOST_SERIALIZATION_SPLIT_MEMBER() + + template + void save(Archivex &ar, const unsigned int /* version */) const { + ar << boost::serialization::make_nvp(m_name, m_value); + } + + template + void load(Archivex &ar, const unsigned int /* version */) { + // Only for output archives + } +}; + +template +class hrp_enum_val : public boost::serialization::wrapper_traits> { + const char *m_name; + const char *m_units; + T m_value; + std::vector m_labels; + bool m_has_default; + T m_default; + +public: + explicit hrp_enum_val(const char *name_, T t, const std::vector& labels, const char* units_ = nullptr) + : m_name(name_), m_units(units_), m_value(t), m_labels(labels), m_has_default(false) {} + + hrp_enum_val& set_default(const T& def_val) { m_default = def_val; m_has_default = true; return *this; } + + const char *name() const { return this->m_name; } + const char *units() const { return this->m_units; } + T &value() { return this->m_value; } + const std::vector& labels() const { return m_labels; } + + bool has_default() const { return m_has_default; } + const T& default_val() const { return m_default; } + + static constexpr bool is_read_only() { return true; } + + BOOST_SERIALIZATION_SPLIT_MEMBER() + + template + void save(Archivex &ar, const unsigned int /* version */) const { + ar << boost::serialization::make_nvp(m_name, m_value); + } + + template + void load(Archivex &ar, const unsigned int /* version */) { + // Only for output archives + } +}; + +template +inline hrp make_hrp(const char *name, T &t, const char* units = nullptr) { + return hrp(name, t, units); +} + +// Specialization for rvalues (value-based storage) +template +inline hrp_val make_hrp(const char *name, T &&t, const char* units = nullptr) { + return hrp_val(name, t, units); +} + template inline hrp_enum make_hrp_enum(const char *name, T &t, const std::vector& labels, const char* units = nullptr) { return hrp_enum(name, t, labels, units); } +// Specialization for rvalues (value-based storage) +template +inline hrp_enum_val make_hrp_enum(const char *name, T &&t, const std::vector& labels, const char* units = nullptr) { + return hrp_enum_val(name, t, labels, units); +} + template inline hrp make_nvp(const char *name, T &t, const char* units) { return hrp(name, t, units); } +// Specialization for rvalues (value-based storage) +template +inline hrp_val make_nvp(const char *name, T &&t, const char* units) { + return hrp_val(name, t, units); +} + } // namespace serialization @@ -206,8 +309,20 @@ namespace uLib { #define HRP5(name, data, units, min, max) boost::serialization::make_hrp(name, data, units).range(min, max) #define HRP6(name, data, units, default, min, max) boost::serialization::make_hrp(name, data, units).set_default(default).range(min, max) +// LEFT FOR BACKWARD COMPATIBILITY #define HRPU(name, units) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name, units) + + + + +namespace serialization { +using boost::serialization::make_nvp; +using boost::serialization::make_hrp; +using boost::serialization::make_hrp_enum; +} // serialization + + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Core/testing/CMakeLists.txt b/src/Core/testing/CMakeLists.txt index a3e3283..75ae59a 100644 --- a/src/Core/testing/CMakeLists.txt +++ b/src/Core/testing/CMakeLists.txt @@ -29,6 +29,7 @@ set( TESTS OpenMPTest TeamTest AffinityTest + ReadOnlyPropertyTest ) set(LIBRARIES diff --git a/src/Core/testing/ReadOnlyPropertyTest.cpp b/src/Core/testing/ReadOnlyPropertyTest.cpp new file mode 100644 index 0000000..b9200ae --- /dev/null +++ b/src/Core/testing/ReadOnlyPropertyTest.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include "Core/Object.h" +#include "Core/Property.h" +#include "Core/Serializable.h" + +#include "Core/Serializable.h" + +using namespace uLib; + +class ReadOnlyTestObject : public Object { +public: + int m_value; + int getValue() const { return m_value; } + + enum State { State1, State2 }; + State m_state; + State getState() const { return m_state; } + + ReadOnlyTestObject() : m_value(10), m_state(State1) { + ULIB_ACTIVATE_PROPERTIES(*this); + } + + template + void serialize(Archive & ar, const unsigned int version) { + // Lvalue reference - should be NOT read-only + ar & HRP("lvalue_prop", m_value); + + // Rvalue from getter - should be read-only + ar & HRP("rvalue_prop", getValue()); + + // Enum lvalue - should be NOT read-only + ar & boost::serialization::make_hrp_enum("lvalue_enum", (int&)m_state, {"State1", "State2"}); + + // Enum rvalue - should be read-only + ar & boost::serialization::make_hrp_enum("rvalue_enum", (int)getState(), {"State1", "State2"}); + } +}; + +int main() { + std::cout << "Testing Read-Only Property Feature..." << std::endl; + + ReadOnlyTestObject obj; + const auto& props = obj.GetProperties(); + + std::cout << "Registered Properties in ReadOnlyTestObject:" << std::endl; + bool found_lvalue = false; + bool found_rvalue = false; + bool found_lvalue_enum = false; + bool found_rvalue_enum = false; + + for (auto* p : props) { + bool ro = p->IsReadOnly(); + std::cout << " - Name: " << p->GetName() + << " | Type: " << p->GetTypeName() + << " | ReadOnly: " << (ro ? "YES" : "NO") << std::endl; + + if (p->GetName() == "lvalue_prop") { + if (ro) { std::cerr << "FAIL: lvalue_prop should NOT be read-only" << std::endl; return 1; } + found_lvalue = true; + } else if (p->GetName() == "rvalue_prop") { + if (!ro) { std::cerr << "FAIL: rvalue_prop SHOULD be read-only" << std::endl; return 1; } + found_rvalue = true; + } else if (p->GetName() == "lvalue_enum") { + if (ro) { std::cerr << "FAIL: lvalue_enum should NOT be read-only" << std::endl; return 1; } + found_lvalue_enum = true; + } else if (p->GetName() == "rvalue_enum") { + if (!ro) { std::cerr << "FAIL: rvalue_enum SHOULD be read-only" << std::endl; return 1; } + found_rvalue_enum = true; + } + } + + if (found_lvalue && found_rvalue && found_lvalue_enum && found_rvalue_enum) { + std::cout << "TEST PASSED SUCCESSFULLY!" << std::endl; + return 0; + } else { + std::cerr << "TEST FAILED: Some properties were not found!" << std::endl; + return 1; + } +}