feat: implement serialization read_only

This commit is contained in:
AndreaRigoni
2026-04-01 11:13:28 +00:00
parent c0c25de694
commit e1bd7eb44f
5 changed files with 240 additions and 7 deletions

View File

@@ -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 <class ArchiveT>
void Object::save_override(ArchiveT &ar, const unsigned int version) {}

View File

@@ -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<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);
}
}
@@ -312,6 +336,16 @@ public:
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);
}
}

View File

@@ -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 <class Archivex>
@@ -115,11 +117,6 @@ public:
}
};
template <class T>
inline hrp<T> make_hrp(const char *name, T &t, const char* units = nullptr) {
return hrp<T>(name, t, units);
}
template <class T>
class hrp_enum : public boost::serialization::wrapper_traits<hrp_enum<T>> {
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 <class Archivex>
@@ -156,16 +155,120 @@ public:
}
};
template <class T>
class hrp_val : public boost::serialization::wrapper_traits<hrp_val<T>> {
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 <class Archivex>
void save(Archivex &ar, const unsigned int /* version */) const {
ar << boost::serialization::make_nvp(m_name, m_value);
}
template <class Archivex>
void load(Archivex &ar, const unsigned int /* version */) {
// Only for output archives
}
};
template <class T>
class hrp_enum_val : public boost::serialization::wrapper_traits<hrp_enum_val<T>> {
const char *m_name;
const char *m_units;
T m_value;
std::vector<std::string> m_labels;
bool m_has_default;
T m_default;
public:
explicit hrp_enum_val(const char *name_, T t, const std::vector<std::string>& 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<std::string>& 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 <class Archivex>
void save(Archivex &ar, const unsigned int /* version */) const {
ar << boost::serialization::make_nvp(m_name, m_value);
}
template <class Archivex>
void load(Archivex &ar, const unsigned int /* version */) {
// Only for output archives
}
};
template <class T>
inline hrp<T> make_hrp(const char *name, T &t, const char* units = nullptr) {
return hrp<T>(name, t, units);
}
// Specialization for rvalues (value-based storage)
template <class T>
inline hrp_val<T> make_hrp(const char *name, T &&t, const char* units = nullptr) {
return hrp_val<T>(name, t, units);
}
template <class T>
inline hrp_enum<T> make_hrp_enum(const char *name, T &t, const std::vector<std::string>& labels, const char* units = nullptr) {
return hrp_enum<T>(name, t, labels, units);
}
// Specialization for rvalues (value-based storage)
template <class T>
inline hrp_enum_val<T> make_hrp_enum(const char *name, T &&t, const std::vector<std::string>& labels, const char* units = nullptr) {
return hrp_enum_val<T>(name, t, labels, units);
}
template <class T>
inline hrp<T> make_nvp(const char *name, T &t, const char* units) {
return hrp<T>(name, t, units);
}
// Specialization for rvalues (value-based storage)
template <class T>
inline hrp_val<T> make_nvp(const char *name, T &&t, const char* units) {
return hrp_val<T>(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
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

View File

@@ -29,6 +29,7 @@ set( TESTS
OpenMPTest
TeamTest
AffinityTest
ReadOnlyPropertyTest
)
set(LIBRARIES

View File

@@ -0,0 +1,82 @@
#include <iostream>
#include <vector>
#include <string>
#include <cassert>
#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<class Archive>
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;
}
}