440 lines
15 KiB
C++
440 lines
15 KiB
C++
#ifndef U_CORE_PROPERTY_H
|
|
#define U_CORE_PROPERTY_H
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <sstream>
|
|
#include <typeinfo>
|
|
#include <typeindex> // Added
|
|
#include <boost/serialization/nvp.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <vector>
|
|
#include <boost/type_traits/is_class.hpp>
|
|
#include <boost/mpl/bool.hpp>
|
|
#include <boost/serialization/serialization.hpp>
|
|
#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<std::string>& GetEnumLabels() const {
|
|
static std::vector<std::string> 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();
|
|
}
|
|
|
|
// 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) 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;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* @brief Template class for typed properties.
|
|
*/
|
|
template <typename T>
|
|
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), m_ReadOnly(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), 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<std::string>(*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 U = T>
|
|
typename std::enable_if<std::is_arithmetic<U>::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 U = T>
|
|
typename std::enable_if<!std::is_arithmetic<U>::value, void>::type
|
|
ValidateT(T& val) {
|
|
}
|
|
|
|
void Set(const T& value) {
|
|
T val = value;
|
|
ValidateT<T>(val);
|
|
if (*m_value != val) {
|
|
*m_value = val;
|
|
ULIB_SIGNAL_EMIT(Property<T>::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; }
|
|
|
|
template<typename U = T>
|
|
typename std::enable_if<std::is_arithmetic<U>::value, double>::type
|
|
GetMinT() const { return (double)m_Min; }
|
|
|
|
template<typename U = T>
|
|
typename std::enable_if<!std::is_arithmetic<U>::value, double>::type
|
|
GetMinT() const { return 0.0; }
|
|
|
|
template<typename U = T>
|
|
typename std::enable_if<std::is_arithmetic<U>::value, double>::type
|
|
GetMaxT() const { return (double)m_Max; }
|
|
|
|
template<typename U = T>
|
|
typename std::enable_if<!std::is_arithmetic<U>::value, double>::type
|
|
GetMaxT() const { return 0.0; }
|
|
|
|
virtual double GetMin() const override { return GetMinT<T>(); }
|
|
virtual double GetMax() const override { return GetMaxT<T>(); }
|
|
|
|
virtual bool HasDefault() const override { return m_HasDefault; }
|
|
virtual std::string GetDefaultValueAsString() const override {
|
|
try { return boost::lexical_cast<std::string>(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<T>::PropertyChanged); }
|
|
|
|
// Serialization
|
|
template <class ArchiveT>
|
|
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;
|
|
bool m_ReadOnly;
|
|
};
|
|
|
|
/**
|
|
* @brief Conveninent typedefs for common property types.
|
|
*/
|
|
typedef Property<std::string> StringProperty;
|
|
typedef Property<int> IntProperty;
|
|
typedef Property<unsigned int> UIntProperty;
|
|
typedef Property<long> LongProperty;
|
|
typedef Property<unsigned long> ULongProperty;
|
|
typedef Property<float> FloatProperty;
|
|
typedef Property<double> DoubleProperty;
|
|
typedef Property<Bool_t> BoolProperty;
|
|
|
|
|
|
/**
|
|
* @brief Property specialized for enumerations, providing labels for GUI representations.
|
|
*/
|
|
class EnumProperty : public Property<int> {
|
|
public:
|
|
EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector<std::string>& labels, const std::string& units = "", const std::string& group = "")
|
|
: Property<int>(owner, name, valuePtr, units, group), m_Labels(labels) {}
|
|
|
|
const std::vector<std::string>& 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<std::string> 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<type> name = Property<type>(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<uLib::Archive::property_register_archive>
|
|
: public uLib_interface_oarchive<uLib::Archive::property_register_archive> {};
|
|
} // 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<property_register_archive>
|
|
{
|
|
Object* m_Object;
|
|
public:
|
|
friend class boost::archive::detail::interface_oarchive<property_register_archive>;
|
|
friend class boost::archive::save_access;
|
|
|
|
typedef boost::archive::detail::common_oarchive<property_register_archive> detail_common_oarchive;
|
|
|
|
property_register_archive(Object* obj) :
|
|
boost::archive::detail::common_oarchive<property_register_archive>(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<class T>
|
|
void save_override(const boost::serialization::hrp<T> &t) {
|
|
if (m_Object) {
|
|
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(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<class T>
|
|
void save_override(const boost::serialization::hrp_val<T> &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<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp_val<T>&>(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<class T>
|
|
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
|
if (m_Object) {
|
|
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
|
p->SetReadOnly(t.is_read_only());
|
|
m_Object->RegisterDynamicProperty(p);
|
|
}
|
|
}
|
|
|
|
template<class T>
|
|
void save_override(const boost::serialization::hrp_enum_val<T> &t) {
|
|
if (m_Object) {
|
|
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum_val<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
|
p->SetReadOnly(t.is_read_only());
|
|
m_Object->RegisterDynamicProperty(p);
|
|
}
|
|
}
|
|
|
|
// Handle standard NVPs by recursing (important for base classes)
|
|
template<class T>
|
|
void save_override(const boost::serialization::nvp<T> &t) {
|
|
if (t.name()) m_GroupStack.push_back(t.name());
|
|
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
|
|
if (t.name()) m_GroupStack.pop_back();
|
|
}
|
|
|
|
// Recursion for nested classes, ignore primitives
|
|
template<class T>
|
|
void save_override(const T &t) {
|
|
this->save_helper(t, typename boost::is_class<T>::type());
|
|
}
|
|
|
|
template<class T>
|
|
void save_helper(const T &t, boost::mpl::true_) {
|
|
boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0);
|
|
}
|
|
|
|
template<class T>
|
|
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<std::string> 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)); _ar_tmp & (obj); }
|
|
|
|
/**
|
|
* @brief Declares a private member that automatically calls ULIB_ACTIVATE_PROPERTIES
|
|
* in every constructor of the class. Place this macro as the last declaration
|
|
* inside the class body (before the closing brace).
|
|
*
|
|
* Usage: ULIB_DECLARE_PROPERTIES(ClassName)
|
|
*
|
|
* This replaces per-constructor ULIB_ACTIVATE_PROPERTIES(*this) calls.
|
|
* RegisterDynamicProperty deduplicates by qualified name, so re-registration
|
|
* from inherited activators in a hierarchy is safe.
|
|
*/
|
|
#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
|
|
} // namespace uLib
|
|
|
|
#endif // U_CORE_PROPERTY_H
|