property - first attempt

This commit is contained in:
AndreaRigoni
2026-03-23 12:55:09 +00:00
parent b52ae808b8
commit 94843de711
16 changed files with 482 additions and 63 deletions

View File

@@ -198,20 +198,16 @@ public:
return &bpos;
}
template <class T> Archive &operator<<(T &t) {
template <class T> Archive &operator<<(const T &t) {
// to get access you must redefine save_override by typing
// "using save_override" in archive impl
this->This()->save_override(t);
return *this->This();
}
// the & operator
template <class T> Archive &operator&(T &t) {
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
return *this->This() << const_cast<const T &>(t);
#else
template <class T> Archive &operator&(const T &t) {
return *this->This() << t;
#endif
}
// the == operator
@@ -364,7 +360,6 @@ public:
boost::serialization::hrp<T> &t) {
this->This()->load_start(t.name());
this->detail_common_iarchive::load_override(t.value());
// t.stov();
this->This()->load_end(t.name());
}
@@ -432,8 +427,7 @@ public:
#endif
::boost::serialization::hrp<T> &t) {
this->This()->save_start(t.name());
// t.vtos();
// this->detail_common_oarchive::save_override(t.const_value());
this->detail_common_oarchive::save_override(t.const_value());
this->This()->save_end(t.name());
}
@@ -467,14 +461,10 @@ public:
text_iarchive(std::istream &is, unsigned int flags = 0)
: text_iarchive_impl<Archive>(is, flags) {}
using basic_text_iarchive::load_override;
using base::load_override;
void load_override(boost::archive::object_id_type &t) {}
// class_name_type can't be handled here as it depends upon the
// char type used by the stream. So require the derived implementation.
// derived in this case is xml_iarchive_impl or base ..
using base::load_override;
void load_override(const char *str) {
StringReader sr(basic_text_iprimitive::is);
@@ -532,7 +522,7 @@ public:
hrt_iarchive(std::istream &is, unsigned int flags = 0)
: base(is, flags | boost::archive::no_header) {}
using basic_text_iarchive::load_override;
using base::load_override;
// hide all archive props //
void load_override(boost::archive::object_id_type &t) {}
@@ -544,10 +534,6 @@ public:
void load_override(boost::archive::class_name_type &t) {}
void load_override(boost::archive::tracking_type &t) {}
// class_name_type can't be handled here as it depends upon the
// char type used by the stream. So require the derived implementation.
// derived in this case is xml_iarchive_impl or base ..
using base::load_override;
void load_override(const char *str) {
StringReader sr(basic_text_iprimitive::is);
@@ -583,6 +569,13 @@ public:
void save_override(const char *str) { basic_text_oprimitive::save(str); }
template <class T>
void save_override(const boost::serialization::hrp<T> &t) {
*this << t.name() << ": ";
*this << t.const_value();
*this << "\n";
}
~hrt_oarchive() {}
};
@@ -611,7 +604,7 @@ public:
// basic_text_oprimitive::save(str);
}
template <class T> void save_override(T &t) {
template <class T> void save_override(const T &t) {
base::save_override(boost::serialization::make_nvp(NULL, t));
}
@@ -627,6 +620,10 @@ public:
base::save_override(t);
}
template <class T> void save_override(const boost::serialization::hrp<T> &t) {
base::save_override(boost::serialization::make_nvp(t.name(), t.const_value()));
}
// specific overrides for attributes - not name value pairs so we
// want to trap them before the above "fall through"
// since we don't want to see these in the output - make them no-ops.

View File

@@ -36,7 +36,7 @@
#include "SmartPointer.h"
#include <boost/any.hpp>
#include <TObject.h>
namespace uLib {

View File

@@ -35,6 +35,9 @@
#include "boost/archive/polymorphic_xml_iarchive.hpp"
#include "boost/archive/polymorphic_xml_oarchive.hpp"
#include <vector>
#include "Property.h"
namespace uLib {
const char *Version::PackageName = PACKAGE_NAME;
@@ -60,8 +63,43 @@ public:
std::string m_InstanceName;
Vector<Signal> sigv;
Vector<Slot> slov;
std::vector<PropertyBase*> m_Properties;
};
// Implementations of Property methods
void Object::RegisterProperty(PropertyBase* prop) {
if (prop) d->m_Properties.push_back(prop);
}
const std::vector<PropertyBase*>& Object::GetProperties() const {
return d->m_Properties;
}
// In Object.h, the template serialize needs to be updated to call property serialization.
// However, since Object::serialize is a template in the header, we might need a helper here.
template <class ArchiveT>
void Object::serialize(ArchiveT &ar, const unsigned int version) {
ar & boost::serialization::make_nvp("InstanceName", d->m_InstanceName);
for (auto* prop : d->m_Properties) {
prop->serialize(ar, version);
}
}
void Object::Updated() { ULIB_SIGNAL_EMIT(Object::Updated); }
template <class ArchiveT>
void Object::save_override(ArchiveT &ar, const unsigned int version) {}
// Explicitly instantiate for all uLib archives
template void Object::serialize(Archive::xml_oarchive &, const unsigned int);
template void Object::serialize(Archive::xml_iarchive &, const unsigned int);
template void Object::serialize(Archive::text_oarchive &, const unsigned int);
template void Object::serialize(Archive::text_iarchive &, const unsigned int);
template void Object::serialize(Archive::hrt_oarchive &, const unsigned int);
template void Object::serialize(Archive::hrt_iarchive &, const unsigned int);
template void Object::serialize(Archive::log_archive &, const unsigned int);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
@@ -146,8 +184,6 @@ GenericMFPtr *Object::findSlotImpl(const char *name) const {
return NULL;
}
void Object::Updated() { ULIB_SIGNAL_EMIT(Object::Updated); }
const std::string& Object::GetInstanceName() const {
return d->m_InstanceName;
}

View File

@@ -51,6 +51,8 @@ class polymorphic_oarchive;
namespace uLib {
class PropertyBase;
class Version {
public:
static const char *PackageName;
@@ -79,6 +81,12 @@ public:
const std::string& GetInstanceName() const;
void SetInstanceName(const std::string& name);
////////////////////////////////////////////////////////////////////////////
// PROPERTIES //
void RegisterProperty(PropertyBase* prop);
const std::vector<PropertyBase*>& GetProperties() const;
////////////////////////////////////////////////////////////////////////////
// PARAMETERS //
@@ -89,9 +97,9 @@ public:
// SERIALIZATION //
template <class ArchiveT>
void serialize(ArchiveT &ar, const unsigned int version) {}
void serialize(ArchiveT &ar, const unsigned int version);
template <class ArchiveT>
void save_override(ArchiveT &ar, const unsigned int version) {}
void save_override(ArchiveT &ar, const unsigned int version);
void SaveConfig(std::ostream &os, int version = 0);
void LoadConfig(std::istream &is, int version = 0);

128
src/Core/Property.h Normal file
View File

@@ -0,0 +1,128 @@
#ifndef U_CORE_PROPERTY_H
#define U_CORE_PROPERTY_H
#include <string>
#include <sstream>
#include <typeinfo>
#include <boost/serialization/nvp.hpp>
#include <boost/lexical_cast.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;
// 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 <typename T>
class Property : public PropertyBase {
public:
Property(Object* owner, const std::string& name, const T& defaultValue = T())
: m_owner(owner), m_name(name), m_value(defaultValue) {
if (m_owner) {
m_owner->RegisterProperty(this);
}
}
virtual ~Property() {}
const std::string& GetName() const override { return m_name; }
const char* GetTypeName() const override { return typeid(T).name(); }
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; }
void Set(const T& value) {
if (m_value != value) {
m_value = value;
ULIB_SIGNAL_EMIT(Property<T>::PropertyChanged);
}
}
// 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); }
private:
std::string m_name;
T m_value;
Object* m_owner;
};
/**
* @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 Macro to simplify property declaration within a class.
* Usage: ULIB_PROPERTY(float, Width, 1.0f)
*/
#define ULIB_PROPERTY(type, name, defaultValue) \
Property<type> name = Property<type>(this, #name, defaultValue);
} // namespace uLib
#endif // U_CORE_PROPERTY_H

View File

@@ -72,39 +72,33 @@ namespace serialization {
template <class T> struct access2 {};
// NON FUNZIONA ... SISTEMARE !!!! // ------------------------------------------
template <class T> class hrp : public wrapper_traits<const hrp<T>> {
template <class T>
class hrp : public boost::serialization::wrapper_traits<hrp<T>> {
const char *m_name;
T *m_value;
std::string *m_str;
T &m_value;
public:
explicit hrp(const char *name_, T &t)
: m_str(new std::string), m_name(name_), m_value(&t) {}
explicit hrp(const char *name_, T &t) : m_name(name_), m_value(t) {}
const char *name() const { return this->m_name; }
T &value() { return this->m_value; }
const T &const_value() const { return this->m_value; }
BOOST_SERIALIZATION_SPLIT_MEMBER()
template <class Archivex>
void save(Archivex &ar, const unsigned int /* file_version */) const {
//// ar.operator<<(const_value());
// std::stringstream ss;
// uLib::Archive::hrt_oarchive har(ss);
// har << make_nvp(m_name,*m_value);
// // (*m_str) = ss.str();
//// ar.operator << (make_nvp(m_name, ss.str());
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 /* file_version */) {
// ar.operator>>(value());
void load(Archivex &ar, const unsigned int /* version */) {
ar >> boost::serialization::make_nvp(m_name, m_value);
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
};
template <class T>
inline
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
const
#endif
hrp<T> make_hrp(const char *name, T &t) {
inline hrp<T> make_hrp(const char *name, T &t) {
return hrp<T>(name, t);
}

View File

@@ -139,6 +139,7 @@ typedef id_t Id_t;
typedef void *Pointer_t;
typedef bool Bool_t; // Boolean (0=false, 1=true) (bool)
//--- bit manipulation ---------------------------------------------------------
#ifndef BIT
#define BIT(n) (1ULL << (n))

View File

@@ -21,6 +21,8 @@ set( TESTS
OptionsTest
PingPongTest
VectorMetaAllocatorTest
PropertyTypesTest
HRPTest
)
set(LIBRARIES

View File

@@ -0,0 +1,49 @@
#include <iostream>
#include <sstream>
#include "Core/Serializable.h"
#include "Core/Archives.h"
using namespace uLib;
struct SimpleObject {
int value;
std::string name;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & HRP(value);
ar & HRP(name);
}
};
int main() {
SimpleObject obj;
obj.value = 42;
obj.name = "TestObject";
std::cout << "Testing HRP Serialization to Log..." << std::endl;
std::stringstream ss;
{
uLib::Archive::log_archive ar(ss);
ar << boost::serialization::make_nvp("Object", obj);
}
std::cout << ss.str() << std::endl;
std::cout << "Testing HRP Serialization to HRT..." << std::endl;
ss.str("");
{
uLib::Archive::hrt_oarchive ar(ss);
ar << obj;
}
std::cout << ss.str() << std::endl;
std::cout << "Testing HRP Serialization to XML..." << std::endl;
ss.str("");
{
uLib::Archive::xml_oarchive ar(ss);
ar << boost::serialization::make_nvp("Object", obj);
}
std::cout << ss.str() << std::endl;
return 0;
}

View File

@@ -0,0 +1,64 @@
#include <iostream>
#include <sstream>
#include "Core/Object.h"
#include "Core/Property.h"
#include <cassert>
using namespace uLib;
class TestObject : public Object {
public:
TestObject() : Object(),
IntProp(this, "IntProp", 10),
StringProp(this, "StringProp", "Initial")
{}
virtual const char* GetClassName() const override { return "TestObject"; }
Property<int> IntProp;
Property<std::string> StringProp;
};
int main() {
TestObject obj;
std::cout << "Testing Properties..." << std::endl;
// 1. Check registration
const auto& props = obj.GetProperties();
assert(props.size() == 2);
assert(props[0]->GetName() == "IntProp");
assert(props[1]->GetName() == "StringProp");
// 2. Check value access and signals
bool signalCalled = false;
uLib::Object::connect(&obj.IntProp, &Property<int>::PropertyChanged, [&signalCalled]() {
signalCalled = true;
});
assert(obj.IntProp.Get() == 10);
obj.IntProp = 20;
assert(obj.IntProp.Get() == 20);
assert(signalCalled == true);
// 3. Check serialization
std::stringstream ss;
Object::SaveXml(ss, obj);
std::string xml = ss.str();
std::cout << "Serialized XML: \n" << xml << std::endl;
assert(xml.find("<IntProp>20</IntProp>") != std::string::npos);
assert(xml.find("<StringProp>Initial</StringProp>") != std::string::npos);
// 4. Check deserialization
TestObject obj2;
std::stringstream ss2(xml);
Object::LoadXml(ss2, obj2);
assert(obj2.IntProp.Get() == 20);
assert(obj2.StringProp.Get() == "Initial");
std::cout << "All Property Tests PASSED!" << std::endl;
return 0;
}

View File

@@ -0,0 +1,68 @@
#include <iostream>
#include <sstream>
#include <cassert>
#include "Core/Object.h"
#include "Core/Property.h"
#include "Math/Dense.h"
using namespace uLib;
class TestObject : public Object {
public:
TestObject() : Object() {}
virtual const char* GetClassName() const override { return "TestObject"; }
// Use new typedefs
StringProperty StringProp = StringProperty(this, "StringProp", "Initial");
IntProperty IntProp = IntProperty(this, "IntProp", 42);
FloatProperty FloatProp = FloatProperty(this, "FloatProp", 3.14f);
BoolProperty BoolProp = BoolProperty(this, "BoolProp", true);
// Use new macro
ULIB_PROPERTY(Matrix3f, MatrixProp, Matrix3f::Identity())
// Use new Dense typedefs
Vector3fProperty Vector3fProp = Vector3fProperty(this, "Vector3fProp", Vector3f(1.1f, 2.2f, 3.3f));
Matrix4fProperty Matrix4fProp = Matrix4fProperty(this, "Matrix4fProp", Matrix4f::Identity());
};
int main() {
TestObject obj;
std::cout << "Testing Property Types..." << std::endl;
// 1. Verify string representation
std::cout << "StringProp: " << obj.StringProp.GetValueAsString() << std::endl;
assert(obj.StringProp.GetValueAsString() == "Initial");
std::cout << "IntProp: " << obj.IntProp.GetValueAsString() << std::endl;
assert(obj.IntProp.GetValueAsString() == "42");
std::cout << "FloatProp: " << obj.FloatProp.GetValueAsString() << std::endl;
// boost::lexical_cast might have different precision, but for 3.14 it should be okay or we check find
assert(obj.FloatProp.GetValueAsString().find("3.14") != std::string::npos);
std::cout << "BoolProp: " << obj.BoolProp.GetValueAsString() << std::endl;
// Bool might be "1" or "true" depending on lexical_cast/stringstream
assert(obj.BoolProp.GetValueAsString() == "1" || obj.BoolProp.GetValueAsString() == "true");
// 2. Verify Matrix/Vector string representation (uses operator<<)
std::cout << "MatrixProp: \n" << obj.MatrixProp.GetValueAsString() << std::endl;
assert(obj.MatrixProp.GetValueAsString().find("1 0 0") != std::string::npos);
std::cout << "Vector3fProp: " << obj.Vector3fProp.GetValueAsString() << std::endl;
assert(obj.Vector3fProp.GetValueAsString().find("1.1 2.2 3.3") != std::string::npos);
std::cout << "Matrix4fProp: \n" << obj.Matrix4fProp.GetValueAsString() << std::endl;
assert(obj.Matrix4fProp.GetValueAsString().find("1 0 0 0") != std::string::npos);
// 3. Verify updates and signals
obj.IntProp = 100;
assert(obj.IntProp.Get() == 100);
assert(obj.IntProp.GetValueAsString() == "100");
std::cout << "All Property Type Tests PASSED!" << std::endl;
return 0;
}