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

View File

@@ -0,0 +1,7 @@
---
trigger: always_on
---
build in build directory using always micromamba "mutom" env.
build with make flag -j$(nproc).

View File

@@ -21,7 +21,7 @@ endif()
project(uLib)
# CUDA Toolkit seems to be missing locally. Toggle ON if nvcc is made available.
option(USE_CUDA "Enable CUDA support" ON)
option(USE_CUDA "Enable CUDA support" OFF)
if(USE_CUDA)
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -allow-unsupported-compiler")
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr")
@@ -115,7 +115,7 @@ set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_RUNTIME OFF)
message(STATUS "CMAKE_PREFIX_PATH is ${CMAKE_PREFIX_PATH}")
find_package(HDF5 REQUIRED CONFIG)
find_package(HDF5 REQUIRED)
find_package(Boost 1.45.0 COMPONENTS program_options serialization unit_test_framework REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})

View File

@@ -7,6 +7,7 @@
#include "Vtk/uLibVtkInterface.h"
#include "Math/Units.h"
#include "Math/Dense.h"
#include "Settings.h"
namespace uLib {
namespace Qt {
@@ -15,8 +16,21 @@ PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent)
: QWidget(parent), m_BaseProperty(prop) {
m_Layout = new QHBoxLayout(this);
m_Layout->setContentsMargins(4, 2, 4, 2);
m_Label = new QLabel(QString::fromStdString(prop->GetName()), this);
m_Label->setMinimumWidth(100);
std::string unit = prop->GetUnits();
QString labelText = QString::fromStdString(prop->GetName());
if (!unit.empty()) {
auto dim = Settings::Instance().IdentifyDimension(unit);
std::string pref = Settings::Instance().GetPreferredUnit(dim);
if (!pref.empty()) {
labelText += " [" + QString::fromStdString(pref) + "]";
} else {
labelText += " [" + QString::fromStdString(unit) + "]";
}
}
m_Label = new QLabel(labelText, this);
m_Label->setMinimumWidth(120);
m_Layout->addWidget(m_Label);
}
PropertyWidgetBase::~PropertyWidgetBase() {
@@ -115,9 +129,6 @@ void UnitLineEdit::updateText() {
s += ".0";
}
}
if (!m_Suffix.isEmpty()) {
s += " " + m_Suffix;
}
setText(s);
}
@@ -129,11 +140,12 @@ void UnitLineEdit::setIntegerOnly(bool integerOnly) {
DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Edit = new UnitLineEdit(this);
QString units = QString::fromStdString(prop->GetUnits());
if (!units.isEmpty()) {
double factor = 1.0;
parseWithUnits("1 " + units, &factor);
m_Edit->setUnits(units, factor);
std::string unit = prop->GetUnits();
if (!unit.empty()) {
auto dim = Settings::Instance().IdentifyDimension(unit);
std::string pref = Settings::Instance().GetPreferredUnit(dim);
double factor = Settings::Instance().GetUnitFactor(pref);
m_Edit->setUnits(QString::fromStdString(pref), factor);
}
m_Edit->setValue(prop->Get());
m_Layout->addWidget(m_Edit, 1);
@@ -146,11 +158,12 @@ DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* pare
FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Edit = new UnitLineEdit(this);
QString units = QString::fromStdString(prop->GetUnits());
if (!units.isEmpty()) {
double factor = 1.0;
parseWithUnits("1 " + units, &factor);
m_Edit->setUnits(units, factor);
std::string unit = prop->GetUnits();
if (!unit.empty()) {
auto dim = Settings::Instance().IdentifyDimension(unit);
std::string pref = Settings::Instance().GetPreferredUnit(dim);
double factor = Settings::Instance().GetUnitFactor(pref);
m_Edit->setUnits(QString::fromStdString(pref), factor);
}
m_Edit->setValue(prop->Get());
m_Layout->addWidget(m_Edit, 1);
@@ -164,11 +177,12 @@ IntPropertyWidget::IntPropertyWidget(Property<int>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Edit = new UnitLineEdit(this);
m_Edit->setIntegerOnly(true);
QString units = QString::fromStdString(prop->GetUnits());
if (!units.isEmpty()) {
double factor = 1.0;
parseWithUnits("1 " + units, &factor);
m_Edit->setUnits(units, factor);
std::string unit = prop->GetUnits();
if (!unit.empty()) {
auto dim = Settings::Instance().IdentifyDimension(unit);
std::string pref = Settings::Instance().GetPreferredUnit(dim);
double factor = Settings::Instance().GetUnitFactor(pref);
m_Edit->setUnits(QString::fromStdString(pref), factor);
}
m_Edit->setValue(prop->Get());
m_Layout->addWidget(m_Edit, 1);
@@ -211,6 +225,26 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
}
StringPropertyWidget::~StringPropertyWidget() {}
class GroupHeaderWidget : public QWidget {
public:
GroupHeaderWidget(const QString& name, QWidget* parent = nullptr) : QWidget(parent) {
auto* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 8, 0, 4);
auto* line = new QFrame(this);
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
line->setStyleSheet("color: #555;");
layout->addWidget(line);
auto* label = new QLabel(name, this);
QFont font = label->font();
font.setBold(true);
font.setPointSize(font.pointSize() + 1);
label->setFont(font);
label->setStyleSheet("color: #aaa; text-transform: uppercase;");
layout->addWidget(label);
}
};
class EnumPropertyWidget : public PropertyWidgetBase {
PropertyBase* m_Prop;
QComboBox* m_Combo;
@@ -305,26 +339,51 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) {
}
}
// Group properties by their group string
std::map<std::string, std::vector<::uLib::PropertyBase*>> groupedProps;
std::vector<std::string> groupOrder;
for (auto* prop : *props) {
// Priority 1: Check if it provides enum labels
if (!prop->GetEnumLabels().empty()) {
m_ContainerLayout->addWidget(new EnumPropertyWidget(prop, m_Container));
continue;
std::string group = prop->GetGroup();
if (groupedProps.find(group) == groupedProps.end()) {
groupOrder.push_back(group);
}
groupedProps[group].push_back(prop);
}
for (const auto& groupName : groupOrder) {
if (!groupName.empty()) {
m_ContainerLayout->addWidget(new GroupHeaderWidget(QString::fromStdString(groupName), m_Container));
}
// Priority 2: Standard factory lookup
auto it = m_Factories.find(prop->GetTypeIndex());
if (it != m_Factories.end()) {
QWidget* widget = it->second(prop, m_Container);
m_ContainerLayout->addWidget(widget);
} else {
// Debug info for unknown types
std::cout << "PropertyEditor: No factory for " << prop->GetName()
<< " (Type: " << prop->GetTypeName() << ")" << std::endl;
for (auto* prop : groupedProps[groupName]) {
QWidget* widget = nullptr;
// Priority 1: Check if it provides enum labels
if (!prop->GetEnumLabels().empty()) {
widget = new EnumPropertyWidget(prop, m_Container);
} else {
// Priority 2: Standard factory lookup
auto it = m_Factories.find(prop->GetTypeIndex());
if (it != m_Factories.end()) {
widget = it->second(prop, m_Container);
} else {
// Debug info for unknown types
std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName()
<< " (Type: " << prop->GetTypeName() << ")" << std::endl;
QWidget* fallback = new PropertyWidgetBase(prop, m_Container);
fallback->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
m_ContainerLayout->addWidget(fallback);
widget = new PropertyWidgetBase(prop, m_Container);
widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
}
}
if (widget) {
if (!groupName.empty()) {
// Indent grouped properties
widget->setContentsMargins(16, 0, 0, 0);
}
m_ContainerLayout->addWidget(widget);
}
}
}
m_ContainerLayout->addStretch(1);

View File

@@ -16,6 +16,7 @@
#include "Core/Object.h"
#include "Core/Signal.h"
#include "Math/Dense.h"
#include "Settings.h"
namespace uLib {
namespace Qt {
@@ -93,18 +94,24 @@ class VectorPropertyWidget : public PropertyWidgetBase {
public:
VectorPropertyWidget(Property<VecT>* prop, QWidget* parent = nullptr)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
QString units = QString::fromStdString(prop->GetUnits());
std::string unit = prop->GetUnits();
double factor = 1.0;
if (!units.isEmpty()) {
parseWithUnits("1 " + units, &factor);
QString prefSuffix;
if (!unit.empty()) {
auto dim = Settings::Instance().IdentifyDimension(unit);
std::string pref = Settings::Instance().GetPreferredUnit(dim);
factor = Settings::Instance().GetUnitFactor(pref);
prefSuffix = QString::fromStdString(pref);
}
for (int i = 0; i < Size; ++i) {
m_Edits[i] = new UnitLineEdit(this);
if (std::is_integral<typename VecT::Scalar>::value) {
m_Edits[i]->setIntegerOnly(true);
}
if (!units.isEmpty()) {
m_Edits[i]->setUnits(units, factor);
if (!prefSuffix.isEmpty()) {
m_Edits[i]->setUnits(prefSuffix, factor);
}
m_Layout->addWidget(m_Edits[i], 1);

View File

@@ -0,0 +1,75 @@
#ifndef GCOMPOSE_SETTINGS_H
#define GCOMPOSE_SETTINGS_H
#include <string>
#include <map>
#include "Math/Units.h"
namespace uLib {
namespace Qt {
class Settings {
public:
static Settings& Instance() {
static Settings instance;
return instance;
}
enum Dimension {
Length,
Angle,
Energy,
Time,
Dimensionless
};
void SetPreferredUnit(Dimension dim, const std::string& unit) {
m_PreferredUnits[dim] = unit;
}
std::string GetPreferredUnit(Dimension dim) const {
auto it = m_PreferredUnits.find(dim);
if (it != m_PreferredUnits.end()) return it->second;
switch(dim) {
case Length: return "mm";
case Angle: return "deg";
case Energy: return "MeV";
case Time: return "ns";
default: return "";
}
}
double GetUnitFactor(const std::string& unit) const {
if (unit == "m") return CLHEP::meter;
if (unit == "cm") return CLHEP::centimeter;
if (unit == "mm") return CLHEP::millimeter;
if (unit == "um") return CLHEP::micrometer;
if (unit == "deg") return CLHEP::degree;
if (unit == "rad") return CLHEP::radian;
if (unit == "ns") return CLHEP::nanosecond;
if (unit == "s") return CLHEP::second;
if (unit == "ms") return CLHEP::millisecond;
if (unit == "MeV") return CLHEP::megaelectronvolt;
if (unit == "GeV") return CLHEP::gigaelectronvolt;
if (unit == "eV") return CLHEP::electronvolt;
return 1.0;
}
Dimension IdentifyDimension(const std::string& unit) const {
if (unit == "m" || unit == "cm" || unit == "mm" || unit == "um" || unit == "nm") return Length;
if (unit == "deg" || unit == "rad") return Angle;
if (unit == "MeV" || unit == "GeV" || unit == "eV" || unit == "keV" || unit == "TeV") return Energy;
if (unit == "ns" || unit == "s" || unit == "ms" || unit == "us") return Time;
return Dimensionless;
}
private:
Settings() {}
std::map<Dimension, std::string> m_PreferredUnits;
};
} // namespace Qt
} // namespace uLib
#endif

View File

@@ -46,23 +46,25 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
m_layout->addWidget(m_titleBar);
// Main horizontal container for viewport and display panel
QWidget* mainArea = new QWidget(this);
QHBoxLayout* hLayout = new QHBoxLayout(mainArea);
hLayout->setContentsMargins(0, 0, 0, 0);
hLayout->setSpacing(0);
m_layout->addWidget(mainArea);
// Main area with splitter for viewport and display panel
m_areaSplitter = new QSplitter(Qt::Horizontal, this);
m_areaSplitter->setObjectName("ViewportAreaSplitter");
m_layout->addWidget(m_areaSplitter, 1);
// Viewport will be added here via setViewport
m_viewport = new uLib::Vtk::QViewport(mainArea);
hLayout->addWidget(m_viewport);
m_viewport = new uLib::Vtk::QViewport(m_areaSplitter);
m_areaSplitter->addWidget(m_viewport);
// Display Panel (Overlay/Slide-out)
m_displayPanel = new QFrame(mainArea);
m_displayPanel = new QFrame(m_areaSplitter);
m_displayPanel->setObjectName("DisplayPropertiesPanel");
m_displayPanel->setFixedWidth(250);
m_displayPanel->setMinimumWidth(150);
m_displayPanel->hide();
m_areaSplitter->addWidget(m_displayPanel);
m_areaSplitter->setStretchFactor(0, 1);
m_areaSplitter->setStretchFactor(1, 0);
QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel);
panelLayout->setContentsMargins(5, 5, 5, 5);
@@ -72,8 +74,6 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel);
panelLayout->addWidget(m_displayEditor);
hLayout->addWidget(m_displayPanel);
connect(m_toggleBtn, &QPushButton::toggled, this, &ViewportPane::toggleDisplayPanel);
connect(m_titleBar, &QWidget::customContextMenuRequested, this, &ViewportPane::showContextMenu);
@@ -85,7 +85,15 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
ViewportPane::~ViewportPane() {}
void ViewportPane::toggleDisplayPanel() {
m_displayPanel->setVisible(m_toggleBtn->isChecked());
bool visible = m_toggleBtn->isChecked();
m_displayPanel->setVisible(visible);
if (visible && m_areaSplitter->sizes().value(1, 0) == 0) {
QList<int> sizes = m_areaSplitter->sizes();
int total = sizes[0] + sizes[1];
sizes[1] = 250;
sizes[0] = total - 250;
m_areaSplitter->setSizes(sizes);
}
}
void ViewportPane::setObject(uLib::Object* obj) {
@@ -107,15 +115,14 @@ void ViewportPane::setObject(uLib::Object* obj) {
void ViewportPane::setViewport(QWidget* viewport, const QString& title) {
if (m_viewport) {
m_viewport->parentWidget()->layout()->removeWidget(m_viewport);
delete m_viewport;
}
m_viewport = viewport;
m_titleLabel->setText(title);
m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
auto* mainAreaLayout = static_cast<QHBoxLayout*>(m_displayPanel->parentWidget()->layout());
mainAreaLayout->insertWidget(0, m_viewport);
m_areaSplitter->insertWidget(0, m_viewport);
m_areaSplitter->setStretchFactor(0, 1);
}
void ViewportPane::addVtkViewport() {

View File

@@ -10,6 +10,7 @@ namespace uLib {
namespace Qt { class PropertyEditor; }
}
class QSplitter;
class QVBoxLayout;
class QLabel;
@@ -39,6 +40,7 @@ private:
QVBoxLayout* m_layout;
QWidget* m_titleBar;
QLabel* m_titleLabel;
QSplitter* m_areaSplitter;
QWidget* m_viewport;
// Display Properties Overlay

View File

@@ -0,0 +1,7 @@
# Aggoritm definition
An algorithm in the uLib infrastructure is a class for containing a functional that can be dynamically loaded into memory as a plug-in.
It derives from the base Object class and therefore can contain properties that define the serialization of operating parameters or the implementation of widgets for interactive parameter manipulation.
The algorithm class is designed to be inserted into a Task, i.e., a class for managing the execution of scheduled operations. A task contains a Run and Stop method to start and stop execution. Furthermore, a task can be configured to work in cyclic or asynchronous mode: in cyclic mode it will be possible to define a cycle time, while in asynchronous mode a task can be hooked to a signal-slot of the Object structure or to a condition variable defined in the monitor pattern (Monitor.h).
The algorithm in particular is defined as a template class on two types T_enc, T_dec. The encoder is a type for data input or another algorithm that is chained with this one that outputs data in the format compatible with input. The decoder is the type of data output or an algorithm compatible with it.

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