algorithm def

This commit is contained in:
AndreaRigoni
2026-03-27 02:29:56 +00:00
parent e0ffeff5b7
commit f5c1e317e8
17 changed files with 874 additions and 112 deletions

234
src/Core/Algorithm.h Normal file
View File

@@ -0,0 +1,234 @@
/*//////////////////////////////////////////////////////////////////////////////
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
All rights reserved
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
------------------------------------------------------------------
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3.0 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library.
//////////////////////////////////////////////////////////////////////////////*/
#ifndef U_CORE_ALGORITHM_H
#define U_CORE_ALGORITHM_H
#include <atomic>
#include <chrono>
#include <condition_variable>
#include "Core/Object.h"
#include "Core/Monitor.h"
#include "Core/Threads.h"
#include "Core/Property.h"
namespace uLib {
////////////////////////////////////////////////////////////////////////////////
//// ALGORITHM /////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Algorithm is a template class for containing a functional that can be
* dynamically loaded as a plug-in. It derives from Object and supports
* properties for serialization and interactive parameter widgets.
*
* @tparam T_enc Encoder type: the input data type, or a chained algorithm
* whose output is compatible with this algorithm's input.
* @tparam T_dec Decoder type: the output data type, or a chained algorithm
* whose input is compatible with this algorithm's output.
*/
template <typename T_enc, typename T_dec>
class Algorithm : public Object {
public:
using EncoderType = T_enc;
using DecoderType = T_dec;
Algorithm() : Object(), m_Encoder(nullptr), m_Decoder(nullptr) {}
virtual ~Algorithm() = default;
virtual const char* GetClassName() const override { return "Algorithm"; }
/**
* @brief Process input data and produce output.
* Override this in subclasses to implement the algorithm logic.
*/
virtual T_dec Process(const T_enc& input) = 0;
/**
* @brief Operator form of Process for functional chaining.
*/
T_dec operator()(const T_enc& input) { return Process(input); }
void SetEncoder(Algorithm* enc) { m_Encoder = enc; }
Algorithm* GetEncoder() const { return m_Encoder; }
void SetDecoder(Algorithm* dec) { m_Decoder = dec; }
Algorithm* GetDecoder() const { return m_Decoder; }
signals:
virtual void Started() { ULIB_SIGNAL_EMIT(Algorithm::Started); }
virtual void Finished() { ULIB_SIGNAL_EMIT(Algorithm::Finished); }
protected:
Algorithm* m_Encoder;
Algorithm* m_Decoder;
};
////////////////////////////////////////////////////////////////////////////////
//// ALGORITHM TASK ////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* @brief AlgorithmTask manages the execution of an Algorithm within a
* scheduled context. Uses uLib::Thread for execution and uLib::Mutex for
* synchronization. Supports cyclic mode (with configurable period) and
* asynchronous mode (triggered by Object signal-slot or condition variable
* from Monitor.h).
*/
template <typename T_enc, typename T_dec>
class AlgorithmTask : public Thread {
public:
using AlgorithmType = Algorithm<T_enc, T_dec>;
enum Mode { Cyclic, Async };
AlgorithmTask()
: Thread()
, m_Algorithm(nullptr)
, m_Mode(Cyclic)
, m_CycleTime_ms(1000)
, m_StopRequested(false)
, m_Triggered(false)
{}
virtual ~AlgorithmTask() { Stop(); }
virtual const char* GetClassName() const override { return "AlgorithmTask"; }
void SetAlgorithm(AlgorithmType* alg) { m_Algorithm = alg; }
AlgorithmType* GetAlgorithm() const { return m_Algorithm; }
void SetMode(Mode mode) { m_Mode = mode; }
Mode GetMode() const { return m_Mode; }
void SetCycleTime(int milliseconds) { m_CycleTime_ms = milliseconds; }
int GetCycleTime() const { return m_CycleTime_ms; }
/**
* @brief Start the task execution in a separate thread (via Thread::Start).
* In Cyclic mode, the algorithm is executed periodically.
* In Async mode, call Notify() or connect a signal to trigger execution.
*/
void Run(const T_enc& input) {
if (IsRunning()) return;
m_StopRequested.store(false);
m_Triggered.store(false);
m_Input = input;
Start();
}
/**
* @brief Stop the task execution and join the thread.
*/
void Stop() {
m_StopRequested.store(true);
{
ULIB_MUTEX_LOCK(m_WaitMutex, -1) {
m_Condition.notify_all();
}
}
if (IsJoinable()) Join();
}
/**
* @brief Notify the task to execute one iteration (Async mode).
* Can be called from a signal-slot connection or externally.
*/
void Notify() {
m_Triggered.store(true);
ULIB_MUTEX_LOCK(m_WaitMutex, -1) {
m_Condition.notify_one();
}
}
/**
* @brief Connect an Object signal to trigger async execution.
* Usage: task.ConnectTrigger(sender, &SenderClass::SomeSignal);
*/
template <typename Func1>
Connection ConnectTrigger(typename FunctionPointer<Func1>::Object* sender, Func1 sigf) {
return Object::connect(sender, sigf, [this]() { Notify(); });
}
signals:
virtual void Stopped() { ULIB_SIGNAL_EMIT(AlgorithmTask::Stopped); }
protected:
/**
* @brief Thread entry point — dispatches to cyclic or async loop.
*/
void Run() override {
if (m_Mode == Cyclic) {
RunCyclic();
} else {
RunAsync();
}
Stopped();
}
private:
void RunCyclic() {
while (!m_StopRequested.load()) {
if (m_Algorithm) {
m_Algorithm->Process(m_Input);
}
std::unique_lock<std::timed_mutex> lock(m_WaitMutex.GetNative());
m_Condition.wait_for(lock,
std::chrono::milliseconds(m_CycleTime_ms),
[this]() { return m_StopRequested.load(); });
}
}
void RunAsync() {
while (!m_StopRequested.load()) {
std::unique_lock<std::timed_mutex> lock(m_WaitMutex.GetNative());
m_Condition.wait(lock, [this]() {
return m_StopRequested.load() || m_Triggered.load();
});
if (m_StopRequested.load()) break;
m_Triggered.store(false);
if (m_Algorithm) {
m_Algorithm->Process(m_Input);
}
}
}
AlgorithmType* m_Algorithm;
Mode m_Mode;
int m_CycleTime_ms;
T_enc m_Input;
std::atomic<bool> m_StopRequested;
std::atomic<bool> m_Triggered;
Mutex m_WaitMutex;
std::condition_variable_any m_Condition;
};
} // namespace uLib
#endif // U_CORE_ALGORITHM_H

View File

@@ -1,7 +1,8 @@
set(HEADERS
Archives.h
Array.h
set(HEADERS
Algorithm.h
Archives.h
Array.h
Collection.h
DataAllocator.h
Debug.h

View File

@@ -90,10 +90,10 @@ const std::vector<PropertyBase*>& Object::GetProperties() const {
PropertyBase* Object::GetProperty(const std::string& name) const {
for (auto* p : d->m_Properties) {
if (p->GetName() == name) return p;
if (p->GetName() == name || p->GetQualifiedName() == name) return p;
}
for (auto* p : d->m_DynamicProperties) {
if (p->GetName() == name) return p;
if (p->GetName() == name || p->GetQualifiedName() == name) return p;
}
return nullptr;
}

View File

@@ -2,11 +2,16 @@
#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"
@@ -29,6 +34,12 @@ public:
static std::vector<std::string> empty;
return empty;
}
virtual const std::string& GetGroup() const = 0;
virtual void SetGroup(const std::string& group) = 0;
std::string GetQualifiedName() const {
if (GetGroup().empty()) return GetName();
return GetGroup() + "." + GetName();
}
// Signal support
signals:
@@ -51,16 +62,16 @@ 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 = "")
: m_owner(owner), m_name(name), m_units(units), m_value(valuePtr), m_own(false) {
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) {
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 = "")
: m_owner(owner), m_name(name), m_units(units), m_value(new T(defaultValue)), m_own(true) {
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) {
if (m_owner) {
m_owner->RegisterProperty(this);
}
@@ -76,6 +87,8 @@ public:
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 {
@@ -127,6 +140,7 @@ public:
private:
std::string m_name;
std::string m_units;
std::string m_group;
T* m_value;
bool m_own;
Object* m_owner;
@@ -149,8 +163,8 @@ typedef Property<Bool_t> BoolProperty;
*/
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 = "")
: Property<int>(owner, name, valuePtr, units), m_Labels(labels) {}
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"; }
@@ -209,11 +223,20 @@ public:
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() : "");
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
m_Object->RegisterDynamicProperty(p);
}
}
@@ -221,7 +244,7 @@ public:
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() : "");
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());
m_Object->RegisterDynamicProperty(p);
}
}
@@ -229,11 +252,24 @@ public:
// Handle standard NVPs by recursing (important for base classes)
template<class T>
void save_override(const boost::serialization::nvp<T> &t) {
boost::archive::detail::common_oarchive<property_register_archive>::save_override(t.const_value());
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();
}
// Ignore everything else
template<class T> void save_override(const T &t) {}
// 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) {}
@@ -244,6 +280,9 @@ public:
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;
};
/**

View File

@@ -0,0 +1,206 @@
#include "Core/Algorithm.h"
#include <iostream>
#include <atomic>
#include <cassert>
using namespace uLib;
////////////////////////////////////////////////////////////////////////////////
// Test algorithms
class DoubleAlgorithm : public Algorithm<int, int> {
public:
const char* GetClassName() const override { return "DoubleAlgorithm"; }
int Process(const int& input) override {
m_CallCount++;
return input * 2;
}
std::atomic<int> m_CallCount{0};
};
class StringifyAlgorithm : public Algorithm<int, std::string> {
public:
const char* GetClassName() const override { return "StringifyAlgorithm"; }
std::string Process(const int& input) override {
return std::to_string(input);
}
};
// Signal source to test ConnectTrigger
class TriggerSource : public Object {
public:
const char* GetClassName() const override { return "TriggerSource"; }
signals:
virtual void DataReady() { ULIB_SIGNAL_EMIT(TriggerSource::DataReady); }
};
////////////////////////////////////////////////////////////////////////////////
// Tests
void TestBasicProcess() {
std::cout << "Testing basic Algorithm::Process..." << std::endl;
DoubleAlgorithm alg;
assert(alg.Process(5) == 10);
assert(alg.Process(-3) == -6);
assert(alg.Process(0) == 0);
std::cout << " Passed." << std::endl;
}
void TestOperatorCall() {
std::cout << "Testing Algorithm::operator()..." << std::endl;
DoubleAlgorithm alg;
assert(alg(7) == 14);
assert(alg(0) == 0);
std::cout << " Passed." << std::endl;
}
void TestEncoderDecoderChain() {
std::cout << "Testing encoder/decoder chain pointers..." << std::endl;
DoubleAlgorithm a, b;
a.SetDecoder(&b);
b.SetEncoder(&a);
assert(a.GetDecoder() == &b);
assert(b.GetEncoder() == &a);
assert(a.GetEncoder() == nullptr);
assert(b.GetDecoder() == nullptr);
std::cout << " Passed." << std::endl;
}
void TestAlgorithmSignals() {
std::cout << "Testing Algorithm signals..." << std::endl;
DoubleAlgorithm alg;
bool started = false;
bool finished = false;
Object::connect(&alg, &DoubleAlgorithm::Started, [&]() { started = true; });
Object::connect(&alg, &DoubleAlgorithm::Finished, [&]() { finished = true; });
alg.Started();
alg.Finished();
assert(started);
assert(finished);
std::cout << " Passed." << std::endl;
}
void TestCyclicTask() {
std::cout << "Testing AlgorithmTask cyclic mode (Thread-based)..." << std::endl;
DoubleAlgorithm alg;
AlgorithmTask<int, int> task;
task.SetAlgorithm(&alg);
task.SetMode(AlgorithmTask<int, int>::Cyclic);
task.SetCycleTime(50);
assert(!task.IsRunning());
task.Run(5);
// Let it run for ~200ms -> expect ~4 cycles
Thread::Sleep(220);
task.Stop();
assert(!task.IsRunning());
int count = alg.m_CallCount.load();
std::cout << " Cyclic iterations: " << count << std::endl;
assert(count >= 3 && count <= 6);
std::cout << " Passed." << std::endl;
}
void TestAsyncTask() {
std::cout << "Testing AlgorithmTask async mode (Mutex + condition_variable)..." << std::endl;
DoubleAlgorithm alg;
AlgorithmTask<int, int> task;
task.SetAlgorithm(&alg);
task.SetMode(AlgorithmTask<int, int>::Async);
task.Run(42);
Thread::Sleep(50); // let the thread start and wait
// Trigger 3 notifications
for (int i = 0; i < 3; ++i) {
task.Notify();
Thread::Sleep(30);
}
task.Stop();
int count = alg.m_CallCount.load();
std::cout << " Async invocations: " << count << std::endl;
assert(count == 3);
std::cout << " Passed." << std::endl;
}
void TestConnectTrigger() {
std::cout << "Testing AlgorithmTask::ConnectTrigger (signal-slot async)..." << std::endl;
DoubleAlgorithm alg;
AlgorithmTask<int, int> task;
task.SetAlgorithm(&alg);
task.SetMode(AlgorithmTask<int, int>::Async);
TriggerSource source;
task.ConnectTrigger(&source, &TriggerSource::DataReady);
task.Run(10);
Thread::Sleep(50);
// Emit signal 3 times
for (int i = 0; i < 3; ++i) {
source.DataReady();
Thread::Sleep(30);
}
task.Stop();
int count = alg.m_CallCount.load();
std::cout << " Signal-triggered invocations: " << count << std::endl;
assert(count == 3);
std::cout << " Passed." << std::endl;
}
void TestTaskStoppedSignal() {
std::cout << "Testing AlgorithmTask Stopped signal..." << std::endl;
DoubleAlgorithm alg;
AlgorithmTask<int, int> task;
task.SetAlgorithm(&alg);
task.SetMode(AlgorithmTask<int, int>::Cyclic);
task.SetCycleTime(20);
std::atomic<bool> stopped{false};
Object::connect(&task, &AlgorithmTask<int, int>::Stopped,
[&]() { stopped.store(true); });
task.Run(1);
Thread::Sleep(50);
task.Stop();
Thread::Sleep(50);
assert(stopped.load());
std::cout << " Passed." << std::endl;
}
void TestClassName() {
std::cout << "Testing GetClassName..." << std::endl;
DoubleAlgorithm alg;
AlgorithmTask<int, int> task;
assert(std::string(alg.GetClassName()) == "DoubleAlgorithm");
assert(std::string(task.GetClassName()) == "AlgorithmTask");
std::cout << " Passed." << std::endl;
}
void TestDifferentTypes() {
std::cout << "Testing Algorithm with different enc/dec types..." << std::endl;
StringifyAlgorithm alg;
assert(alg.Process(42) == "42");
assert(alg.Process(-1) == "-1");
assert(alg(100) == "100");
std::cout << " Passed." << std::endl;
}
int main() {
TestBasicProcess();
TestOperatorCall();
TestEncoderDecoderChain();
TestAlgorithmSignals();
TestDifferentTypes();
TestCyclicTask();
TestAsyncTask();
TestConnectTrigger();
TestTaskStoppedSignal();
TestClassName();
std::cout << "All Algorithm tests passed!" << std::endl;
return 0;
}

View File

@@ -23,11 +23,13 @@ set( TESTS
VectorMetaAllocatorTest
PropertyTypesTest
HRPTest
PropertyGroupingTest
MutexTest
ThreadsTest
OpenMPTest
TeamTest
AffinityTest
AlgorithmTest
)
set(LIBRARIES

View File

@@ -0,0 +1,78 @@
#include <iostream>
#include <vector>
#include <string>
#include <cassert>
#include "Core/Object.h"
#include "Core/Property.h"
using namespace uLib;
struct Nested {
float x = 1.0f;
float y = 2.0f;
ULIB_SERIALIZE_ACCESS
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & HRP(x);
ar & HRP(y);
}
};
class GroupObject : public Object {
uLibTypeMacro(GroupObject, Object)
public:
Nested position;
Nested orientation;
float weight = 50.0f;
ULIB_SERIALIZE_ACCESS
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("Position", position);
ar & boost::serialization::make_nvp("Orientation", orientation);
ar & HRP(weight);
}
};
int main() {
std::cout << "Testing Property Grouping..." << std::endl;
GroupObject obj;
ULIB_ACTIVATE_PROPERTIES(obj);
auto props = obj.GetProperties();
std::cout << "Registered " << props.size() << " properties." << std::endl;
for (auto* p : props) {
std::cout << "Prop: " << p->GetName()
<< " Group: " << p->GetGroup()
<< " Qualified: " << p->GetQualifiedName() << std::endl;
}
// Check if nested properties are registered
PropertyBase* p1 = obj.GetProperty("Position.x");
PropertyBase* p2 = obj.GetProperty("Position.y");
PropertyBase* p3 = obj.GetProperty("Orientation.x");
PropertyBase* p4 = obj.GetProperty("Orientation.y");
PropertyBase* p5 = obj.GetProperty("weight");
assert(p1 != nullptr && "Position.x not found");
assert(p2 != nullptr && "Position.y not found");
assert(p3 != nullptr && "Orientation.x not found");
assert(p4 != nullptr && "Orientation.y not found");
assert(p5 != nullptr && "weight not found");
assert(p1->GetGroup() == "Position");
assert(p2->GetGroup() == "Position");
assert(p3->GetGroup() == "Orientation");
assert(p4->GetGroup() == "Orientation");
assert(p5->GetGroup() == "");
assert(p1->GetQualifiedName() == "Position.x");
assert(p5->GetQualifiedName() == "weight");
std::cout << "Property Grouping Tests PASSED!" << std::endl;
return 0;
}

View File

@@ -61,6 +61,7 @@
#include "uLibVtkInterface.h"
#include "vtkHandlerWidget.h"
#include "Math/Dense.h"
#include "Core/Property.h"
@@ -96,9 +97,9 @@ public:
m_Dragable(true)
{
m_Color[0] = m_Color[1] = m_Color[2] = -1.0;
m_Position[0] = m_Position[1] = m_Position[2] = 0.0;
m_Orientation[0] = m_Orientation[1] = m_Orientation[2] = 0.0;
m_Scale[0] = m_Scale[1] = m_Scale[2] = 1.0;
m_Position = Vector3d::Zero();
m_Orientation = Vector3d::Zero();
m_Scale = Vector3d::Ones();
}
~PuppetData() {
@@ -124,9 +125,9 @@ public:
bool m_Selected;
bool m_Visibility;
bool m_Dragable;
double m_Position[3];
double m_Orientation[3];
double m_Scale[3];
Vector3d m_Position;
Vector3d m_Orientation;
Vector3d m_Scale;
void ApplyAppearance(vtkProp *p) {
p->SetVisibility(m_Visibility);
@@ -156,9 +157,9 @@ public:
// Handle transformation if it's a Prop3D
if (auto* p3d = vtkProp3D::SafeDownCast(p)) {
// NOTE: Usually managed by Puppet::Update from model, but here for direct prop manipulation
// p3d->SetPosition(m_Position);
// p3d->SetOrientation(m_Orientation);
// p3d->SetScale(m_Scale);
// p3d->SetPosition(m_Position.data());
// p3d->SetOrientation(m_Orientation.data());
// p3d->SetScale(m_Scale.data());
}
}
@@ -499,9 +500,9 @@ void Puppet::Update()
// Apply transformation if it's a Prop3D
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
p3d->SetPosition(pd->m_Position);
p3d->SetOrientation(pd->m_Orientation);
p3d->SetScale(pd->m_Scale);
p3d->SetPosition(pd->m_Position.data());
p3d->SetOrientation(pd->m_Orientation.data());
p3d->SetScale(pd->m_Scale.data());
}
}
@@ -549,9 +550,9 @@ void Puppet::SyncFromVtk()
// Update properties
for (int i=0; i<3; ++i) {
pd->m_Position[i] = pos[i];
pd->m_Orientation[i] = ori[i];
pd->m_Scale[i] = scale[i];
pd->m_Position(i) = pos[i];
pd->m_Orientation(i) = ori[i];
pd->m_Scale(i) = scale[i];
}
// Get the properties from the object
@@ -567,26 +568,37 @@ void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor)
{
}
struct TransformProxy {
PuppetData* pd;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & boost::serialization::make_hrp("Position", pd->m_Position, "mm");
ar & boost::serialization::make_hrp("Orientation", pd->m_Orientation, "deg");
ar & boost::serialization::make_hrp("Scale", pd->m_Scale, "");
}
};
struct AppearanceProxy {
PuppetData* pd;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & boost::serialization::make_hrp("ColorR", pd->m_Color[0]);
ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]);
ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]);
ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity);
ar & boost::serialization::make_hrp_enum("Representation", pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"});
ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility);
ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable);
ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable);
}
};
void Puppet::serialize_display(Archive::display_properties_archive & ar, const unsigned int version) {
ar & boost::serialization::make_hrp("ColorR", pd->m_Color[0]);
ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]);
ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]);
ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity);
ar & boost::serialization::make_hrp_enum("Representation", pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"});
ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility);
ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable);
ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable);
AppearanceProxy appearance{pd};
ar & boost::serialization::make_nvp("Appearance", appearance);
// Geometry knobs (caution: these might be overridden by internal matrices)
ar & boost::serialization::make_hrp("PosX", pd->m_Position[0], "mm");
ar & boost::serialization::make_hrp("PosY", pd->m_Position[1], "mm");
ar & boost::serialization::make_hrp("PosZ", pd->m_Position[2], "mm");
ar & boost::serialization::make_hrp("OriX", pd->m_Orientation[0], "deg");
ar & boost::serialization::make_hrp("OriY", pd->m_Orientation[1], "deg");
ar & boost::serialization::make_hrp("OriZ", pd->m_Orientation[2], "deg");
ar & boost::serialization::make_hrp("ScaleX", pd->m_Scale[0]);
ar & boost::serialization::make_hrp("ScaleY", pd->m_Scale[1]);
ar & boost::serialization::make_hrp("ScaleZ", pd->m_Scale[2]);
TransformProxy transform{pd};
ar & boost::serialization::make_nvp("Transform", transform);
}
void Puppet::serialize(Archive::xml_oarchive & ar, const unsigned int v) { }

View File

@@ -29,6 +29,9 @@
#include <iomanip>
#include <ostream>
#include <vector>
#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/serialization/serialization.hpp>
#include "Core/Object.h"
#include "Core/Property.h"
#include "Core/Monitor.h"
@@ -157,10 +160,19 @@ public:
boost::archive::detail::common_oarchive<display_properties_archive>(boost::archive::no_header),
m_Puppet(puppet) {}
std::string GetCurrentGroup() const {
std::string group;
for (const auto& g : m_GroupStack) {
if (!group.empty()) group += ".";
group += g;
}
return group;
}
template<class T>
void save_override(const boost::serialization::hrp<T> &t) {
if (m_Puppet) {
uLib::Property<T>* p = new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "");
uLib::Property<T>* p = new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
m_Puppet->RegisterDisplayProperty(p);
Vtk::Puppet* puppet = m_Puppet;
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
@@ -170,7 +182,7 @@ public:
template<class T>
void save_override(const boost::serialization::hrp_enum<T> &t) {
if (m_Puppet) {
uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "");
uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
m_Puppet->RegisterDisplayProperty(p);
Vtk::Puppet* puppet = m_Puppet;
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
@@ -178,10 +190,23 @@ public:
}
template<class T> void save_override(const boost::serialization::nvp<T> &t) {
boost::archive::detail::common_oarchive<display_properties_archive>::save_override(t.const_value());
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();
}
template<class T> void save_override(const T &t) {}
// 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_) {}
void save_override(const boost::archive::object_id_type & t) {}
void save_override(const boost::archive::object_reference_type & t) {}
@@ -194,6 +219,7 @@ public:
private:
Vtk::Puppet* m_Puppet;
std::vector<std::string> m_GroupStack;
};
} // namespace Archive