#ifndef U_CORE_PROPERTY_H #define U_CORE_PROPERTY_H #include #include #include #include #include #include #include #include #include #include #include #include "Core/Archives.h" #include "Core/Signal.h" #include "Core/Object.h" namespace uLib { namespace Archive { class property_register_archive; } /** * @brief Base class for properties to allow runtime listing and identification. */ class PropertyBase : public Object { public: virtual ~PropertyBase() {} virtual const std::string& GetName() const = 0; virtual const char* GetTypeName() const = 0; virtual std::string GetValueAsString() const = 0; virtual std::type_index GetTypeIndex() const = 0; virtual const std::string& GetUnits() const = 0; virtual void SetUnits(const std::string& units) = 0; virtual const std::vector& GetEnumLabels() const { static std::vector empty; return empty; } virtual const std::string& GetGroup() const = 0; virtual void SetGroup(const std::string& group) = 0; virtual bool HasRange() const { return false; } virtual double GetMin() const { return 0; } 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(); return GetGroup() + "." + GetName(); } // Serialization support for different uLib archives virtual void serialize(Archive::xml_oarchive & ar, const unsigned int version) override = 0; virtual void serialize(Archive::xml_iarchive & ar, const unsigned int version) override = 0; virtual void serialize(Archive::text_oarchive & ar, const unsigned int version) override = 0; virtual void serialize(Archive::text_iarchive & ar, const unsigned int version) override = 0; virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int version) override = 0; virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int version) override = 0; virtual void serialize(Archive::log_archive & ar, const unsigned int version) override = 0; virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) = 0; }; /** * @brief Template class for typed properties. */ template class Property : public PropertyBase { public: 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_ReadOnly(false) { if (m_owner) m_owner->RegisterProperty(this); } 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_ReadOnly(false) { if (m_owner) m_owner->RegisterProperty(this); } virtual ~Property() { if (m_own) delete m_value; } // Identification virtual const std::string& GetName() const override { return m_name; } virtual const char* GetTypeName() const override { return typeid(T).name(); } virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); } virtual const std::string& GetUnits() const override { return m_units; } virtual void SetUnits(const std::string& units) override { m_units = units; } virtual const std::string& GetGroup() const override { return m_group; } virtual void SetGroup(const std::string& group) override { m_group = group; } std::string GetValueAsString() const override { try { return boost::lexical_cast(*m_value); } catch (...) { std::stringstream ss; ss << *m_value; return ss.str(); } } // Accessors const T& Get() const { return *m_value; } void Set(const T& value) { if (!m_value) return; T val = value; if constexpr (std::is_arithmetic::value) { if (m_HasRange) { if (val < m_Min) val = m_Min; if (val > m_Max) val = m_Max; } } if (*m_value != val) { *m_value = val; ULIB_SIGNAL_EMIT(Property::PropertyChanged); this->Updated(); if (m_owner) m_owner->Updated(); } } 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; } virtual double GetMin() const override { return m_HasRange ? convert_to_double(m_Min) : 0.0; } virtual double GetMax() const override { return m_HasRange ? convert_to_double(m_Max) : 0.0; } const T& GetMinTyped() const { return m_Min; } const T& GetMaxTyped() const { return m_Max; } virtual bool HasDefault() const override { return m_HasDefault; } virtual std::string GetDefaultValueAsString() const override { try { return boost::lexical_cast(m_Default); } catch (...) { return ""; } } // Operators operator const T&() const { return *m_value; } Property& operator=(const T& value) { Set(value); return *this; } signals: virtual void PropertyChanged() { ULIB_SIGNAL_EMIT(Property::PropertyChanged); } private: template static double convert_to_double(const U& val) { return convert_to_double_impl(val, typename std::is_arithmetic::type()); } template static double convert_to_double_impl(const U& val, std::true_type) { return (double)val; } template static double convert_to_double_impl(const U& val, std::false_type) { return 0.0; } public: // Serialization template void serialize_helper(ArchiveT & ar, const unsigned int version) { ar & boost::serialization::make_hrp(m_name.c_str(), *m_value, m_units.c_str()); } virtual void serialize(Archive::xml_oarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } virtual void serialize(Archive::xml_iarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } virtual void serialize(Archive::text_oarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } virtual void serialize(Archive::text_iarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } virtual void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_helper(ar, v); } virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) override; protected: std::string m_name; std::string m_units; std::string m_group; T* m_value; bool m_own; Object* m_owner; bool m_HasRange; T m_Min; T m_Max; bool m_HasDefault; T m_Default; bool m_ReadOnly; }; /** * @brief Property specialized for enumerations. */ class EnumProperty : public Property { public: EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector& labels, const std::string& units = "", const std::string& group = "") : Property(owner, name, valuePtr, units, group), m_Labels(labels) {} const std::vector& GetEnumLabels() const override { return m_Labels; } const char* GetTypeName() const override { return "Enum"; } virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(EnumProperty)); } template void serialize_enum_helper(ArchiveT & ar, const unsigned int version) { ar & boost::serialization::make_hrp_enum(m_name.c_str(), *m_value, m_Labels, m_units.c_str()); } virtual void serialize(Archive::xml_oarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } virtual void serialize(Archive::xml_iarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } virtual void serialize(Archive::text_oarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } virtual void serialize(Archive::text_iarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } virtual void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) override; private: std::vector m_Labels; }; } // namespace uLib namespace uLib { namespace Archive { class property_register_archive : public boost::archive::detail::common_oarchive { protected: Object* m_Object; bool m_DisplayOnly; public: friend class boost::archive::detail::interface_oarchive; friend class boost::archive::save_access; using boost::archive::detail::common_oarchive::save_override; property_register_archive(Object* obj, bool displayOnly = false) : boost::archive::detail::common_oarchive(boost::archive::no_header), m_Object(obj), m_DisplayOnly(displayOnly) { if (obj) m_Visited.insert(dynamic_cast(obj)); } template property_register_archive &operator&(const T &t) { this->save_override(t); return *this; } template property_register_archive &operator<<(const T &t) { this->save_override(t); return *this; } std::string GetCurrentGroup() const { std::string group; for (const auto& g : m_GroupStack) { if (!group.empty()) group += "."; group += g; } return group; } template void register_property(Property& p) { save_property_impl(p.GetName().c_str(), const_cast(p.Get()), p.GetUnits().c_str(), p.HasRange(), p.GetMinTyped(), p.GetMaxTyped(), p.IsReadOnly()); } void register_enum_property(EnumProperty& p) { if (!m_Object) return; EnumProperty* newP = new EnumProperty(m_Object, p.GetName(), const_cast(&p.Get()), p.GetEnumLabels(), p.GetUnits(), GetCurrentGroup()); newP->SetReadOnly(p.IsReadOnly()); if (m_DisplayOnly) { m_Object->RegisterDisplayProperty(newP); Object* obj = m_Object; Object::connect(newP, &Object::Updated, [obj]() { obj->Updated(); }); } else { m_Object->RegisterDynamicProperty(newP); } } template void save_property_impl(const char* name, T& val, const char* units, bool hasRange, const T& minVal, const T& maxVal, bool isReadOnly) { if (m_Object) { Property* p = new Property(m_Object, name, &val, units ? units : "", GetCurrentGroup()); set_range_helper(p, hasRange, minVal, maxVal, typename std::is_arithmetic::type()); p->SetReadOnly(isReadOnly); if (m_DisplayOnly) { m_Object->RegisterDisplayProperty(p); Object* obj = m_Object; Object::connect(p, &Object::Updated, [obj]() { obj->Updated(); }); } else { m_Object->RegisterDynamicProperty(p); } } } template static void set_range_helper(Property* p, bool hasRange, const U& minVal, const U& maxVal, std::true_type) { if (hasRange) p->SetRange(minVal, maxVal); } template static void set_range_helper(Property* p, bool hasRange, const U& minVal, const U& maxVal, std::false_type) {} template void save_override(const boost::serialization::hrp &t) { // To handle T correctly without deduction issues, we assume T can be passed to save_property_impl T dummy = T(); // Ensure we can construct T save_property_impl(t.name(), const_cast&>(t).value(), t.units(), t.has_range(), t.has_range() ? t.min_val() : dummy, t.has_range() ? t.max_val() : dummy, t.is_read_only()); } template void save_override(const boost::serialization::hrp_val &t) { T dummy = T(); save_property_impl(t.name(), const_cast&>(t).value(), t.units(), t.has_range(), t.has_range() ? t.min_val() : dummy, t.has_range() ? t.max_val() : dummy, t.is_read_only()); } template 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()); if (m_DisplayOnly) { m_Object->RegisterDisplayProperty(p); Object* obj = m_Object; Object::connect(p, &Object::Updated, [obj]() { obj->Updated(); }); } else { 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()); if (m_DisplayOnly) { m_Object->RegisterDisplayProperty(p); Object* obj = m_Object; Object::connect(p, &Object::Updated, [obj]() { obj->Updated(); }); } else { m_Object->RegisterDynamicProperty(p); } } } template void save_override(const boost::serialization::nvp &t) { if (t.name()) m_GroupStack.push_back(t.name()); this->save_helper(t.const_value(), typename boost::is_class::type()); if (t.name()) m_GroupStack.pop_back(); } void save_override(const std::string &t) {} template void save_override(T * const & t) { if (!t) return; this->save_pointer_helper(t, typename boost::is_base_of::type()); } template void save_pointer_helper(T* t, boost::mpl::true_) { const void* ptr = dynamic_cast(t); if (m_Visited.find(ptr) != m_Visited.end()) return; m_Visited.insert(ptr); this->save_override(*t); } template void save_pointer_helper(T* t, boost::mpl::false_) {} template void save_override(const T &t) { this->save_helper(t, typename boost::is_class::type()); } template void save_helper(const T &t, boost::mpl::true_) { boost::serialization::serialize_adl(*this, const_cast(t), 0); } void save_helper(const std::string &t, boost::mpl::true_) {} template void save_helper(const T &t, boost::mpl::false_) {} void save_override(const boost::archive::object_id_type & t) {} void save_override(const boost::archive::object_reference_type & t) {} void save_override(const boost::archive::version_type & t) {} void save_override(const boost::archive::class_id_type & t) {} void save_override(const boost::archive::class_id_optional_type & t) {} void save_override(const boost::archive::class_id_reference_type & t) {} void save_override(const boost::archive::class_name_type & t) {} void save_override(const boost::archive::tracking_type & t) {} protected: std::vector m_GroupStack; std::set m_Visited; }; } // namespace Archive } // namespace uLib namespace uLib { template inline void Property::serialize(Archive::property_register_archive & ar, const unsigned int v) { ar.register_property(*this); } inline void EnumProperty::serialize(Archive::property_register_archive & ar, const unsigned int v) { ar.register_enum_property(*this); } namespace Archive { #define ULIB_ACTIVATE_PROPERTIES(obj) \ { uLib::Archive::property_register_archive _ar_tmp(&(obj)); _ar_tmp & (obj); } #define ULIB_DECLARE_PROPERTIES(SelfType) \ private: \ struct _PropActivator { \ _PropActivator(SelfType* self) { \ uLib::Archive::property_register_archive _ar(self); \ _ar & *self; \ } \ } _prop_activator{this}; } // namespace Archive // Convenience macro: declares a named Property member with a default value. // Usage inside a class body (requires 'this' to be available, so use in-class initializer): // ULIB_PROPERTY(int, MyProp, 42) #define ULIB_PROPERTY(type, name, defaultVal) \ ::uLib::Property name{this, #name, (type)(defaultVal)}; // Common property type aliases typedef Property BoolProperty; typedef Property IntProperty; typedef Property FloatProperty; typedef Property DoubleProperty; typedef Property StringProperty; template void serialize_properties_helper(ArchiveT &ar, const std::vector &props, unsigned int version) { for (auto* prop : props) prop->serialize(ar, version); } template void Object::serialize(ArchiveT &ar, const unsigned int version) { ar & boost::serialization::make_nvp("InstanceName", this->GetInstanceName()); serialize_properties_helper(ar, this->GetProperties(), version); } } // namespace uLib #endif // U_CORE_PROPERTY_H