#ifndef U_CORE_PROPERTY_H #define U_CORE_PROPERTY_H #include #include #include #include #include // Added #include #include #include #include #include #include #include "Core/Archives.h" #include "Core/Signal.h" #include "Core/Object.h" namespace uLib { /** * @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; // Added 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 ""; } std::string GetQualifiedName() const { if (GetGroup().empty()) return GetName(); return GetGroup() + "." + GetName(); } // Signal support signals: virtual void Updated() override { ULIB_SIGNAL_EMIT(PropertyBase::Updated); } // Serialization support for different uLib archives virtual void serialize(Archive::xml_oarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::xml_iarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::text_oarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::text_iarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::log_archive & ar, const unsigned int version) = 0; }; /** * @brief Template class for typed properties. */ template class Property : public PropertyBase { 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) { if (m_owner) { m_owner->RegisterProperty(this); } } // 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) { 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 (const boost::bad_lexical_cast&) { std::stringstream ss; ss << *m_value; return ss.str(); } } // Accessors const T& Get() const { return *m_value; } template typename std::enable_if::value, void>::type ValidateT(T& val) { if (m_HasRange) { if (val < m_Min) val = m_Min; if (val > m_Max) val = m_Max; } } template typename std::enable_if::value, void>::type ValidateT(T& val) { } void Set(const T& value) { T val = value; ValidateT(val); 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; } virtual bool HasRange() const override { return m_HasRange; } template typename std::enable_if::value, double>::type GetMinT() const { return (double)m_Min; } template typename std::enable_if::value, double>::type GetMinT() const { return 0.0; } template typename std::enable_if::value, double>::type GetMaxT() const { return (double)m_Max; } template typename std::enable_if::value, double>::type GetMaxT() const { return 0.0; } virtual double GetMin() const override { return GetMinT(); } virtual double GetMax() const override { return GetMaxT(); } virtual bool HasDefault() const override { return m_HasDefault; } virtual std::string GetDefaultValueAsString() const override { try { return boost::lexical_cast(m_Default); } catch (...) { return ""; } } // Operators for seamless usage operator const T&() const { return *m_value; } Property& operator=(const T& value) { Set(value); return *this; } // Signals signals: virtual void PropertyChanged() { ULIB_SIGNAL_EMIT(Property::PropertyChanged); } // Serialization template void serialize_impl(ArchiveT & ar, const unsigned int version) { ar & boost::serialization::make_nvp(m_name.c_str(), *m_value); } void serialize(Archive::xml_oarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } void serialize(Archive::xml_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } void serialize(Archive::text_oarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } void serialize(Archive::text_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_impl(ar, v); } virtual void Updated() override { PropertyBase::Updated(); this->PropertyChanged(); } private: 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; }; /** * @brief Conveninent typedefs for common property types. */ typedef Property StringProperty; typedef Property IntProperty; typedef Property UIntProperty; typedef Property LongProperty; typedef Property ULongProperty; typedef Property FloatProperty; typedef Property DoubleProperty; typedef Property BoolProperty; /** * @brief Property specialized for enumerations, providing labels for GUI representations. */ 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)); } private: std::vector m_Labels; }; /** * @brief Macro to simplify property declaration within a class. * Usage: ULIB_PROPERTY(float, Width, 1.0f) * It creates a raw member m_Width and a Property proxy Width. */ #define ULIB_PROPERTY(type, name, defaultValue) \ type m_##name = defaultValue; \ Property name = Property(this, #name, &m_##name); } // namespace uLib namespace uLib { namespace Archive { class property_register_archive; } // namespace Archive } // namespace uLib namespace boost { namespace archive { namespace detail { template <> class interface_oarchive : public uLib_interface_oarchive {}; } // namespace detail } // namespace archive } // namespace boost namespace uLib { namespace Archive { /** * @brief A special archive that creates and registers Property proxies * for any member it encounters wrapped in HRP(). */ class property_register_archive : public boost::archive::detail::common_oarchive { Object* m_Object; public: friend class boost::archive::detail::interface_oarchive; friend class boost::archive::save_access; typedef boost::archive::detail::common_oarchive detail_common_oarchive; property_register_archive(Object* obj) : boost::archive::detail::common_oarchive(boost::archive::no_header), m_Object(obj) {} std::string GetCurrentGroup() const { std::string group; for (const auto& g : m_GroupStack) { if (!group.empty()) group += "."; group += g; } return group; } // Core logic: encounter HRP -> Create Dynamic Property template void save_override(const boost::serialization::hrp &t) { if (m_Object) { 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()); m_Object->RegisterDynamicProperty(p); } } 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()); m_Object->RegisterDynamicProperty(p); } } // Handle standard NVPs by recursing (important for base classes) 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(); } // Recursion for nested classes, ignore primitives 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); } template void save_helper(const T &t, boost::mpl::false_) {} // Required attribute overrides for common_oarchive 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) {} private: std::vector m_GroupStack; }; /** * @brief Convenience macro to automatically activate and register all HRP members * as uLib properties. Usage: ULIB_ACTIVATE_PROPERTIES(obj) */ #define ULIB_ACTIVATE_PROPERTIES(obj) \ { uLib::Archive::property_register_archive _ar_tmp(&(obj)); (obj).serialize(_ar_tmp, 0); } } // namespace Archive } // namespace uLib #endif // U_CORE_PROPERTY_H