5 Commits

Author SHA1 Message Date
AndreaRigoni
a467b7385b monitor and threads 2026-03-25 11:04:37 +00:00
AndreaRigoni
0c8ef7337c threads and monitor 2026-03-25 10:40:13 +00:00
AndreaRigoni
913a1f7b3a move vtk containerbox in math 2026-03-25 10:37:38 +00:00
AndreaRigoni
5397baa50c add quit to gcompose 2026-03-24 17:46:08 +00:00
AndreaRigoni
51e6dbb4f5 add cylinder 2026-03-24 15:22:50 +00:00
42 changed files with 1687 additions and 175 deletions

View File

@@ -41,6 +41,8 @@ MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_m
auto* fileMenu = new QMenu(btnFile); auto* fileMenu = new QMenu(btnFile);
fileMenu->addAction("Open", this, &MainPanel::onOpen); fileMenu->addAction("Open", this, &MainPanel::onOpen);
fileMenu->addAction("Save", this, &MainPanel::onSave); fileMenu->addAction("Save", this, &MainPanel::onSave);
fileMenu->addAction("Save As", this, &MainPanel::onSaveAs);
fileMenu->addAction("Exit", this, &MainPanel::onExit);
btnFile->setMenu(fileMenu); btnFile->setMenu(fileMenu);
// Theme Menu Button // Theme Menu Button
@@ -52,7 +54,7 @@ MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_m
btnTheme->setMenu(themeMenu); btnTheme->setMenu(themeMenu);
// New Menu Button // New Menu Button
auto* btnNew = new QPushButton("New", menuPanel); auto* btnNew = new QPushButton("Add", menuPanel);
btnNew->setObjectName("MenuButton"); btnNew->setObjectName("MenuButton");
auto* newMenu = new QMenu(btnNew); auto* newMenu = new QMenu(btnNew);
@@ -184,6 +186,14 @@ void MainPanel::onSave() {
// Placeholder for save logic // Placeholder for save logic
} }
void MainPanel::onSaveAs() {
// Placeholder for save as logic
}
void MainPanel::onExit() {
qApp->quit();
}
void MainPanel::onDarkTheme() { void MainPanel::onDarkTheme() {
StyleManager::applyStyle(qApp, "dark"); StyleManager::applyStyle(qApp, "dark");
} }

View File

@@ -27,6 +27,9 @@ public:
private slots: private slots:
void onOpen(); void onOpen();
void onSave(); void onSave();
void onSaveAs();
void onExit();
void onDarkTheme(); void onDarkTheme();
void onBrightTheme(); void onBrightTheme();

View File

@@ -1,6 +1,10 @@
#include "PropertyWidgets.h" #include "PropertyWidgets.h"
#include <QSignalBlocker> #include <QSignalBlocker>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include "Vtk/uLibVtkInterface.h" #include "Vtk/uLibVtkInterface.h"
#include "Math/Units.h"
#include "Math/Dense.h"
namespace uLib { namespace uLib {
namespace Qt { namespace Qt {
@@ -15,57 +19,124 @@ PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent)
} }
PropertyWidgetBase::~PropertyWidgetBase() {} PropertyWidgetBase::~PropertyWidgetBase() {}
// Helper for unit parsing
static double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr) {
static QRegularExpression re("^\\s*([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)\\s*(_?[a-zA-Z]+)?\\s*$");
QRegularExpressionMatch match = re.match(text);
if (!match.hasMatch()) return 0.0;
double num = match.captured(1).toDouble();
QString unit = match.captured(3);
double factor = 1.0;
if (!unit.isEmpty()) {
QString u = unit.startsWith('_') ? unit.mid(1) : unit;
if (u == "m") factor = CLHEP::meter;
else if (u == "cm") factor = CLHEP::centimeter;
else if (u == "mm") factor = CLHEP::millimeter;
else if (u == "um") factor = CLHEP::micrometer;
else if (u == "nm") factor = CLHEP::nanometer;
else if (u == "km") factor = CLHEP::kilometer;
else if (u == "deg") factor = CLHEP::degree;
else if (u == "rad") factor = CLHEP::radian;
else if (u == "ns") factor = CLHEP::nanosecond;
else if (u == "s") factor = CLHEP::second;
else if (u == "ms") factor = CLHEP::millisecond;
else if (u == "MeV") factor = CLHEP::megaelectronvolt;
else if (u == "eV") factor = CLHEP::electronvolt;
else if (u == "keV") factor = CLHEP::kiloelectronvolt;
else if (u == "GeV") factor = CLHEP::gigaelectronvolt;
else if (u == "TeV") factor = CLHEP::teraelectronvolt;
if (suffixOut) *suffixOut = u;
} else if (suffixOut) {
// Reuse previous suffix if none provided, or empty
}
if (factorOut) *factorOut = factor;
return num * factor;
}
// UnitLineEdit implementation
UnitLineEdit::UnitLineEdit(QWidget* parent) : QLineEdit(parent), m_Value(0), m_Factor(1.0), m_Suffix("mm"), m_IsInteger(false) {
connect(this, &QLineEdit::editingFinished, this, &UnitLineEdit::onEditingFinished);
}
void UnitLineEdit::setValue(double val) {
if (m_Value != val) {
m_Value = val;
// Initial heuristic for unit if it was mm and value becomes large
if (!m_IsInteger && m_Suffix == "mm" && std::abs(val) >= 1000.0) { m_Suffix = "m"; m_Factor = CLHEP::meter; }
updateText();
}
}
void UnitLineEdit::onEditingFinished() {
double factor = m_Factor;
QString suffix = m_Suffix;
double parsedVal = parseWithUnits(text(), &factor, &suffix);
if (!suffix.isEmpty()) {
m_Suffix = suffix;
m_Factor = factor;
}
if (m_IsInteger) {
parsedVal = std::round(parsedVal);
}
if (m_Value != parsedVal) {
m_Value = parsedVal;
emit valueManualChanged(m_Value);
}
updateText();
}
void UnitLineEdit::updateText() {
QSignalBlocker blocker(this);
if (m_IsInteger) {
setText(QString::number((int)m_Value));
} else {
double displayVal = m_Value / m_Factor;
setText(QString::number(displayVal, 'g', 6) + " " + m_Suffix);
}
}
void UnitLineEdit::setIntegerOnly(bool integerOnly) {
m_IsInteger = integerOnly;
updateText();
}
DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent) DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) { : PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_SpinBox = new QDoubleSpinBox(this); m_Edit = new UnitLineEdit(this);
m_SpinBox->setRange(-1000000.0, 1000000.0); m_Layout->addWidget(m_Edit, 1);
m_SpinBox->setValue(prop->Get()); m_Edit->setValue(prop->Get());
m_Layout->addWidget(m_SpinBox, 1); connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); });
connect(m_SpinBox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
[this](double val){ if (m_Prop->Get() != val) m_Prop->Set(val); });
uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){ uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
if (m_SpinBox->value() != m_Prop->Get()) { m_Edit->setValue(m_Prop->Get());
QSignalBlocker blocker(m_SpinBox);
m_SpinBox->setValue(m_Prop->Get());
}
}); });
} }
DoublePropertyWidget::~DoublePropertyWidget() {}
FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent) FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) { : PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_SpinBox = new QDoubleSpinBox(this); m_Edit = new UnitLineEdit(this);
m_SpinBox->setRange(-1000000.0, 1000000.0); m_Layout->addWidget(m_Edit, 1);
m_SpinBox->setDecimals(3); m_Edit->setValue(prop->Get());
m_SpinBox->setValue(prop->Get()); connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); });
m_Layout->addWidget(m_SpinBox, 1);
connect(m_SpinBox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
[this](double val){ if (m_Prop->Get() != (float)val) m_Prop->Set((float)val); });
uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){ uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
if (m_SpinBox->value() != (double)m_Prop->Get()) { m_Edit->setValue((double)m_Prop->Get());
QSignalBlocker blocker(m_SpinBox);
m_SpinBox->setValue((double)m_Prop->Get());
}
}); });
} }
FloatPropertyWidget::~FloatPropertyWidget() {}
IntPropertyWidget::IntPropertyWidget(Property<int>* prop, QWidget* parent) IntPropertyWidget::IntPropertyWidget(Property<int>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) { : PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_SpinBox = new QSpinBox(this); m_Edit = new UnitLineEdit(this);
m_SpinBox->setRange(-1000000, 1000000); m_Edit->setIntegerOnly(true);
m_SpinBox->setValue(prop->Get()); m_Layout->addWidget(m_Edit, 1);
m_Layout->addWidget(m_SpinBox, 1); m_Edit->setValue(prop->Get());
connect(m_SpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); });
[this](int val){ if (m_Prop->Get() != val) m_Prop->Set(val); });
uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){ uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
if (m_SpinBox->value() != m_Prop->Get()) { m_Edit->setValue((double)m_Prop->Get());
QSignalBlocker blocker(m_SpinBox);
m_SpinBox->setValue(m_Prop->Get());
}
}); });
} }
IntPropertyWidget::~IntPropertyWidget() {}
BoolPropertyWidget::BoolPropertyWidget(Property<bool>* prop, QWidget* parent) BoolPropertyWidget::BoolPropertyWidget(Property<bool>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) { : PropertyWidgetBase(prop, parent), m_Prop(prop) {
@@ -126,6 +197,17 @@ PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(null
registerFactory<std::string>([](PropertyBase* p, QWidget* parent){ registerFactory<std::string>([](PropertyBase* p, QWidget* parent){
return new StringPropertyWidget(static_cast<Property<std::string>*>(p), parent); return new StringPropertyWidget(static_cast<Property<std::string>*>(p), parent);
}); });
// Vector Registration
registerFactory<Vector2i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2i, 2>(static_cast<Property<Vector2i>*>(p), parent); });
registerFactory<Vector2f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2f, 2>(static_cast<Property<Vector2f>*>(p), parent); });
registerFactory<Vector2d>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2d, 2>(static_cast<Property<Vector2d>*>(p), parent); });
registerFactory<Vector3i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector3i, 3>(static_cast<Property<Vector3i>*>(p), parent); });
registerFactory<Vector3f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector3f, 3>(static_cast<Property<Vector3f>*>(p), parent); });
registerFactory<Vector3d>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector3d, 3>(static_cast<Property<Vector3d>*>(p), parent); });
registerFactory<Vector4i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector4i, 4>(static_cast<Property<Vector4i>*>(p), parent); });
registerFactory<Vector4f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector4f, 4>(static_cast<Property<Vector4f>*>(p), parent); });
registerFactory<Vector4d>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector4d, 4>(static_cast<Property<Vector4d>*>(p), parent); });
} }
PropertyEditor::~PropertyEditor() {} PropertyEditor::~PropertyEditor() {}

View File

@@ -5,8 +5,6 @@
#include <QLabel> #include <QLabel>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QDoubleSpinBox>
#include <QSpinBox>
#include <QLineEdit> #include <QLineEdit>
#include <QCheckBox> #include <QCheckBox>
#include <QScrollArea> #include <QScrollArea>
@@ -16,6 +14,7 @@
#include "Core/Property.h" #include "Core/Property.h"
#include "Core/Object.h" #include "Core/Object.h"
#include "Math/Dense.h"
namespace uLib { namespace uLib {
namespace Qt { namespace Qt {
@@ -33,34 +32,90 @@ protected:
QLabel* m_Label; QLabel* m_Label;
}; };
class UnitLineEdit : public QLineEdit {
Q_OBJECT
public:
UnitLineEdit(QWidget* parent = nullptr);
void setValue(double val);
double getValue() const { return m_Value; }
void setIntegerOnly(bool b);
signals:
void valueManualChanged(double val);
private slots:
void onEditingFinished();
private:
void updateText();
double m_Value;
double m_Factor;
QString m_Suffix;
bool m_IsInteger;
};
class DoublePropertyWidget : public PropertyWidgetBase { class DoublePropertyWidget : public PropertyWidgetBase {
Q_OBJECT Q_OBJECT
public: public:
DoublePropertyWidget(Property<double>* prop, QWidget* parent = nullptr); DoublePropertyWidget(Property<double>* prop, QWidget* parent = nullptr);
virtual ~DoublePropertyWidget();
private: private:
Property<double>* m_Prop; Property<double>* m_Prop;
QDoubleSpinBox* m_SpinBox; UnitLineEdit* m_Edit;
}; };
class FloatPropertyWidget : public PropertyWidgetBase { class FloatPropertyWidget : public PropertyWidgetBase {
Q_OBJECT Q_OBJECT
public: public:
FloatPropertyWidget(Property<float>* prop, QWidget* parent = nullptr); FloatPropertyWidget(Property<float>* prop, QWidget* parent = nullptr);
virtual ~FloatPropertyWidget();
private: private:
Property<float>* m_Prop; Property<float>* m_Prop;
QDoubleSpinBox* m_SpinBox; UnitLineEdit* m_Edit;
}; };
class IntPropertyWidget : public PropertyWidgetBase { class IntPropertyWidget : public PropertyWidgetBase {
Q_OBJECT Q_OBJECT
public: public:
IntPropertyWidget(Property<int>* prop, QWidget* parent = nullptr); IntPropertyWidget(Property<int>* prop, QWidget* parent = nullptr);
virtual ~IntPropertyWidget();
private: private:
Property<int>* m_Prop; Property<int>* m_Prop;
QSpinBox* m_SpinBox; UnitLineEdit* m_Edit;
};
template <typename VecT, int Size>
class VectorPropertyWidget : public PropertyWidgetBase {
public:
VectorPropertyWidget(Property<VecT>* prop, QWidget* parent = nullptr)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
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);
}
m_Layout->addWidget(m_Edits[i], 1);
connect(m_Edits[i], &UnitLineEdit::valueManualChanged, [this, i](double val){
VecT v = m_Prop->Get();
v(i) = (typename VecT::Scalar)val;
if (m_Prop->Get() != v) m_Prop->Set(v);
});
}
updateEdits();
uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
updateEdits();
});
}
private:
void updateEdits() {
VecT v = m_Prop->Get();
for (int i = 0; i < Size; ++i) {
if (!m_Edits[i]->hasFocus()) {
m_Edits[i]->setValue((double)v(i));
}
}
}
Property<VecT>* m_Prop;
UnitLineEdit* m_Edits[Size];
}; };
class BoolPropertyWidget : public PropertyWidgetBase { class BoolPropertyWidget : public PropertyWidgetBase {

View File

@@ -9,7 +9,7 @@
#include "HEP/Detectors/DetectorChamber.h" #include "HEP/Detectors/DetectorChamber.h"
#include "Vtk/HEP/Detectors/vtkDetectorChamber.h" #include "Vtk/HEP/Detectors/vtkDetectorChamber.h"
#include <Vtk/vtkContainerBox.h> #include <Vtk/Math/vtkContainerBox.h>
#include <Vtk/vtkQViewport.h> #include <Vtk/vtkQViewport.h>
#include "Core/ObjectsContext.h" #include "Core/ObjectsContext.h"

View File

@@ -19,6 +19,8 @@ set(HEADERS
SmartPointer.h SmartPointer.h
StaticInterface.h StaticInterface.h
StringReader.h StringReader.h
Threads.h
Monitor.h
Types.h Types.h
Uuid.h Uuid.h
Vector.h Vector.h
@@ -34,9 +36,14 @@ set(SOURCES
Serializable.cpp Serializable.cpp
Signal.cpp Signal.cpp
Uuid.cpp Uuid.cpp
Threads.cpp
) )
set(LIBRARIES Boost::program_options Boost::serialization) set(LIBRARIES
Boost::program_options
Boost::serialization
OpenMP::OpenMP_CXX
)
set(libname ${PACKAGE_LIBPREFIX}Core) set(libname ${PACKAGE_LIBPREFIX}Core)
set(ULIB_SHARED_LIBRARIES ${ULIB_SHARED_LIBRARIES} ${libname} PARENT_SCOPE) set(ULIB_SHARED_LIBRARIES ${ULIB_SHARED_LIBRARIES} ${libname} PARENT_SCOPE)

215
src/Core/Monitor.h Normal file
View File

@@ -0,0 +1,215 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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_MONITOR_H
#define U_CORE_MONITOR_H
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <utility>
namespace uLib {
/**
* @brief Mutex class wraps std::timed_mutex and is used for thread synchronization.
*/
class Mutex {
public:
Mutex() = default;
~Mutex() = default;
/** @brief Locks the mutex, blocking if necessary. */
void Lock() { m_Mutex.lock(); }
/** @brief Unlocks the mutex. */
void Unlock() { m_Mutex.unlock(); }
/** @brief Tries to lock the mutex without blocking. */
bool TryLock() { return m_Mutex.try_lock(); }
/** @brief Tries to lock the mutex within a timeout in milliseconds. */
bool TryLockFor(int timeout_ms) {
if (timeout_ms < 0) { Lock(); return true; }
return m_Mutex.try_lock_for(std::chrono::milliseconds(timeout_ms));
}
/** @brief RAII helper for scoped locking. */
class ScopedLock {
public:
ScopedLock(Mutex &mutex) : m_Mutex(mutex) { m_Mutex.Lock(); }
~ScopedLock() { m_Mutex.Unlock(); }
private:
Mutex &m_Mutex;
// Non-copyable
ScopedLock(const ScopedLock&) = delete;
ScopedLock& operator=(const ScopedLock&) = delete;
};
/** @brief Returns the underlying std::timed_mutex. */
std::timed_mutex& GetNative() { return m_Mutex; }
private:
std::timed_mutex m_Mutex;
// Non-copyable
Mutex(const Mutex &) = delete;
Mutex &operator=(const Mutex &) = delete;
};
namespace detail {
/** @brief Internal implementation for the ULIB_MUTEX_LOCK macros. */
class ScopedTimedLock {
public:
ScopedTimedLock(Mutex& mutex, int timeout_ms)
: m_RawMutex(nullptr), m_MutexWrapper(&mutex), m_Locked(false) {
m_Locked = m_MutexWrapper->TryLockFor(timeout_ms);
}
ScopedTimedLock(std::timed_mutex& mutex, int timeout_ms)
: m_RawMutex(&mutex), m_MutexWrapper(nullptr), m_Locked(false) {
if (timeout_ms < 0) { m_RawMutex->lock(); m_Locked = true; }
else m_Locked = m_RawMutex->try_lock_for(std::chrono::milliseconds(timeout_ms));
}
~ScopedTimedLock() {
if (m_Locked) {
if (m_RawMutex) m_RawMutex->unlock();
else if (m_MutexWrapper) m_MutexWrapper->Unlock();
}
}
operator bool() const { return m_Locked; }
void unlock() { if (m_Locked) {
if (m_RawMutex) m_RawMutex->unlock();
else if (m_MutexWrapper) m_MutexWrapper->Unlock();
m_Locked = false;
} }
private:
std::timed_mutex* m_RawMutex = nullptr;
Mutex* m_MutexWrapper = nullptr;
bool m_Locked;
// Non-copyable/movable to be safe in the 'for' loop
ScopedTimedLock(const ScopedTimedLock&) = delete;
ScopedTimedLock& operator=(const ScopedTimedLock&) = delete;
ScopedTimedLock(ScopedTimedLock&&) = default;
};
inline ScopedTimedLock makeScopedMutexLock(Mutex& mutex, int timeout_ms) {
return ScopedTimedLock(mutex, timeout_ms);
}
inline ScopedTimedLock makeScopedMutexLock(std::timed_mutex& mutex, int timeout_ms) {
return ScopedTimedLock(mutex, timeout_ms);
}
} // namespace detail
/**
* @brief Macro for block-scoped locking of a static mutex.
* @param timeout Timeout in ms (-1 for infinite).
*/
#define ULIB_STATIC_LOCK(timeout) \
static std::timed_mutex __ulib_static_mutex; \
for (auto __ulib_lock = uLib::detail::makeScopedMutexLock(__ulib_static_mutex, timeout); \
__ulib_lock; \
__ulib_lock.unlock())
/**
* @brief Macro for block-scoped locking of a provided mutex.
* @param mutex The uLib::Mutex or std::timed_mutex to lock.
* @param timeout Timeout in ms (-1 for infinite).
*/
#define ULIB_MUTEX_LOCK(mutex, timeout) \
for (auto __ulib_lock = uLib::detail::makeScopedMutexLock(mutex, timeout); \
__ulib_lock; \
__ulib_lock.unlock())
/**
* @brief RecursiveMutex class wraps std::recursive_timed_mutex.
*/
class RecursiveMutex {
public:
RecursiveMutex() = default;
~RecursiveMutex() = default;
/** @brief Locks the mutex, blocking if necessary. */
void Lock() { m_Mutex.lock(); }
/** @brief Unlocks the mutex. */
void Unlock() { m_Mutex.unlock(); }
/** @brief Tries to lock the mutex without blocking. */
bool TryLock() { return m_Mutex.try_lock(); }
/** @brief Tries to lock the mutex within a timeout in milliseconds. */
bool TryLockFor(int timeout_ms) {
if (timeout_ms < 0) { Lock(); return true; }
return m_Mutex.try_lock_for(std::chrono::milliseconds(timeout_ms));
}
/** @brief RAII helper for scoped locking. */
class ScopedLock {
public:
ScopedLock(RecursiveMutex &mutex) : m_Mutex(mutex) { m_Mutex.Lock(); }
~ScopedLock() { m_Mutex.Unlock(); }
private:
RecursiveMutex &m_Mutex;
ScopedLock(const ScopedLock&) = delete;
ScopedLock& operator=(const ScopedLock&) = delete;
};
private:
std::recursive_timed_mutex m_Mutex;
RecursiveMutex(const RecursiveMutex &) = delete;
RecursiveMutex &operator=(const RecursiveMutex &) = delete;
};
/**
* @brief Monitor class provides a base for objects that need thread-safe access.
*/
template <typename T>
class Monitor {
protected:
T* m_Resource;
Mutex m_Mutex;
public:
Monitor(T* resource) : m_Resource(resource) {}
virtual ~Monitor() { delete m_Resource; }
/** @brief Thread-safe access to the resource through a lambda. */
template <typename F>
auto Access(F f) -> decltype(f(*m_Resource)) {
Mutex::ScopedLock lock(m_Mutex);
return f(*m_Resource);
}
};
} // namespace uLib
#endif // U_CORE_MONITOR_H

View File

@@ -65,6 +65,7 @@ public:
Vector<Slot> slov; Vector<Slot> slov;
std::vector<PropertyBase*> m_Properties; std::vector<PropertyBase*> m_Properties;
std::vector<PropertyBase*> m_DynamicProperties; std::vector<PropertyBase*> m_DynamicProperties;
bool m_SignalsBlocked;
}; };
// Implementations of Property methods // Implementations of Property methods
@@ -75,7 +76,8 @@ void Object::RegisterProperty(PropertyBase* prop) {
void Object::RegisterDynamicProperty(PropertyBase* prop) { void Object::RegisterDynamicProperty(PropertyBase* prop) {
if (prop) { if (prop) {
d->m_DynamicProperties.push_back(prop); d->m_DynamicProperties.push_back(prop);
d->m_Properties.push_back(prop); // Note: prop already added itself to m_Properties
// during its own constructor call to owner->RegisterProperty()
} }
} }
@@ -113,9 +115,15 @@ template void Object::serialize(Archive::log_archive &, const unsigned int);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// OBJECT IMPLEMENTATION // OBJECT IMPLEMENTATION
Object::Object() : d(new ObjectPrivate) {} Object::Object() : d(new ObjectPrivate) {
d->m_SignalsBlocked = false;
Object::Object(const Object &copy) : d(new ObjectPrivate(*copy.d)) {} }
Object::Object(const Object &copy) : d(new ObjectPrivate) {
if (copy.d) {
d->m_InstanceName = copy.d->m_InstanceName;
d->m_SignalsBlocked = copy.d->m_SignalsBlocked;
}
}
Object::~Object() { Object::~Object() {
for (auto* p : d->m_DynamicProperties) { for (auto* p : d->m_DynamicProperties) {
@@ -125,9 +133,11 @@ Object::~Object() {
} }
void Object::DeepCopy(const Object &copy) { void Object::DeepCopy(const Object &copy) {
// should lock to be tread safe // if (this == &copy) return;
memcpy(d, copy.d, sizeof(ObjectPrivate)); if (copy.d) d->m_InstanceName = copy.d->m_InstanceName;
// ERROR! does not copy parameters ... <<<< FIXXXXX // Note: signals, slots and properties are intentionally not copied
// to maintain instance uniquely and avoid duplicate registrations.
this->Updated();
} }
void Object::SaveXml(std::ostream &os, Object &ob) { void Object::SaveXml(std::ostream &os, Object &ob) {
@@ -206,6 +216,16 @@ void Object::SetInstanceName(const std::string& name) {
this->Updated(); this->Updated();
} }
bool Object::blockSignals(bool block) {
bool old = d->m_SignalsBlocked;
d->m_SignalsBlocked = block;
return old;
}
bool Object::signalsBlocked() const {
return d->m_SignalsBlocked;
}
// std::ostream & // std::ostream &
// operator << (std::ostream &os, uLib::Object &ob) // operator << (std::ostream &os, uLib::Object &ob)
// { // {

View File

@@ -82,6 +82,12 @@ public:
const std::string& GetInstanceName() const; const std::string& GetInstanceName() const;
void SetInstanceName(const std::string& name); void SetInstanceName(const std::string& name);
/** @brief Temporarily blocks all signal emissions from this object. Returns previous state. */
bool blockSignals(bool block);
/** @brief Checks if signals are currently blocked. */
bool signalsBlocked() const;
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// PROPERTIES // // PROPERTIES //
void RegisterProperty(PropertyBase* prop); void RegisterProperty(PropertyBase* prop);
@@ -138,24 +144,22 @@ public:
// Qt5 style connector // // Qt5 style connector //
template <typename Func1, typename Func2> template <typename Func1, typename Func2>
static bool static Connection
connect(typename FunctionPointer<Func1>::Object *sender, Func1 sigf, connect(typename FunctionPointer<Func1>::Object *sender, Func1 sigf,
typename FunctionPointer<Func2>::Object *receiver, Func2 slof) { typename FunctionPointer<Func2>::Object *receiver, Func2 slof) {
SignalBase *sigb = sender->findOrAddSignal(sigf); SignalBase *sigb = sender->findOrAddSignal(sigf);
ConnectSignal<typename FunctionPointer<Func1>::SignalSignature>(sigb, slof, return ConnectSignal<typename FunctionPointer<Func1>::SignalSignature>(sigb, slof,
receiver); receiver);
return true;
} }
// Lambda/Function object connector // // Lambda/Function object connector //
template <typename Func1, typename SlotT> template <typename Func1, typename SlotT>
static bool connect(typename FunctionPointer<Func1>::Object *sender, static Connection connect(typename FunctionPointer<Func1>::Object *sender,
Func1 sigf, SlotT slof) { Func1 sigf, SlotT slof) {
SignalBase *sigb = sender->findOrAddSignal(sigf); SignalBase *sigb = sender->findOrAddSignal(sigf);
typedef typename FunctionPointer<Func1>::SignalSignature SigSignature; typedef typename FunctionPointer<Func1>::SignalSignature SigSignature;
typedef typename Signal<SigSignature>::type SigT; typedef typename Signal<SigSignature>::type SigT;
reinterpret_cast<SigT *>(sigb)->connect(slof); return reinterpret_cast<SigT *>(sigb)->connect(slof);
return true;
} }
template <typename Func1, typename Func2> template <typename Func1, typename Func2>
@@ -167,10 +171,9 @@ public:
} }
template <typename FuncT> template <typename FuncT>
static inline bool connect(SignalBase *sigb, FuncT slof, Object *receiver) { static inline Connection connect(SignalBase *sigb, FuncT slof, Object *receiver) {
ConnectSignal<typename FunctionPointer<FuncT>::SignalSignature>(sigb, slof, return ConnectSignal<typename FunctionPointer<FuncT>::SignalSignature>(sigb, slof,
receiver); receiver);
return true;
} }
template <typename FuncT> template <typename FuncT>

View File

@@ -31,6 +31,8 @@
#include <boost/signals2/signal.hpp> #include <boost/signals2/signal.hpp>
#include <boost/signals2/signal_type.hpp> #include <boost/signals2/signal_type.hpp>
#include <boost/signals2/slot.hpp> #include <boost/signals2/slot.hpp>
#include <boost/signals2/connection.hpp>
#include <boost/signals2/shared_connection_block.hpp>
#include "Function.h" #include "Function.h"
#include <boost/bind/bind.hpp> #include <boost/bind/bind.hpp>
@@ -63,9 +65,11 @@ using namespace boost::placeholders;
#define _ULIB_DETAIL_SIGNAL_EMIT(_name, ...) \ #define _ULIB_DETAIL_SIGNAL_EMIT(_name, ...) \
do { \ do { \
BOOST_AUTO(sig, this->findOrAddSignal(&_name)); \ if (!this->signalsBlocked()) { \
if (sig) \ BOOST_AUTO(sig, this->findOrAddSignal(&_name)); \
sig->operator()(__VA_ARGS__); \ if (sig) \
sig->operator()(__VA_ARGS__); \
} \
} while (0) } while (0)
/** /**
@@ -90,6 +94,7 @@ namespace uLib {
// TODO ... // TODO ...
typedef boost::signals2::signal_base SignalBase; typedef boost::signals2::signal_base SignalBase;
typedef boost::signals2::connection Connection;
template <typename T> struct Signal { template <typename T> struct Signal {
typedef boost::signals2::signal<T> type; typedef boost::signals2::signal<T> type;
@@ -104,57 +109,57 @@ struct ConnectSignal {};
template <typename FuncT, typename SigSignature> template <typename FuncT, typename SigSignature>
struct ConnectSignal<FuncT, SigSignature, 0> { struct ConnectSignal<FuncT, SigSignature, 0> {
static void connect(SignalBase *sigb, FuncT slof, static Connection connect(SignalBase *sigb, FuncT slof,
typename FunctionPointer<FuncT>::Object *receiver) { typename FunctionPointer<FuncT>::Object *receiver) {
typedef typename Signal<SigSignature>::type SigT; typedef typename Signal<SigSignature>::type SigT;
reinterpret_cast<SigT *>(sigb)->connect(slof); return reinterpret_cast<SigT *>(sigb)->connect(slof);
} }
}; };
template <typename FuncT, typename SigSignature> template <typename FuncT, typename SigSignature>
struct ConnectSignal<FuncT, SigSignature, 1> { struct ConnectSignal<FuncT, SigSignature, 1> {
static void connect(SignalBase *sigb, FuncT slof, static Connection connect(SignalBase *sigb, FuncT slof,
typename FunctionPointer<FuncT>::Object *receiver) { typename FunctionPointer<FuncT>::Object *receiver) {
typedef typename Signal<SigSignature>::type SigT; typedef typename Signal<SigSignature>::type SigT;
reinterpret_cast<SigT *>(sigb)->connect(boost::bind(slof, receiver)); return reinterpret_cast<SigT *>(sigb)->connect(boost::bind(slof, receiver));
} }
}; };
template <typename FuncT, typename SigSignature> template <typename FuncT, typename SigSignature>
struct ConnectSignal<FuncT, SigSignature, 2> { struct ConnectSignal<FuncT, SigSignature, 2> {
static void connect(SignalBase *sigb, FuncT slof, static Connection connect(SignalBase *sigb, FuncT slof,
typename FunctionPointer<FuncT>::Object *receiver) { typename FunctionPointer<FuncT>::Object *receiver) {
typedef typename Signal<SigSignature>::type SigT; typedef typename Signal<SigSignature>::type SigT;
reinterpret_cast<SigT *>(sigb)->connect(boost::bind(slof, receiver, _1)); return reinterpret_cast<SigT *>(sigb)->connect(boost::bind(slof, receiver, _1));
} }
}; };
template <typename FuncT, typename SigSignature> template <typename FuncT, typename SigSignature>
struct ConnectSignal<FuncT, SigSignature, 3> { struct ConnectSignal<FuncT, SigSignature, 3> {
static void connect(SignalBase *sigb, FuncT slof, static Connection connect(SignalBase *sigb, FuncT slof,
typename FunctionPointer<FuncT>::Object *receiver) { typename FunctionPointer<FuncT>::Object *receiver) {
typedef typename Signal<SigSignature>::type SigT; typedef typename Signal<SigSignature>::type SigT;
reinterpret_cast<SigT *>(sigb)->connect( return reinterpret_cast<SigT *>(sigb)->connect(
boost::bind(slof, receiver, _1, _2)); boost::bind(slof, receiver, _1, _2));
} }
}; };
template <typename FuncT, typename SigSignature> template <typename FuncT, typename SigSignature>
struct ConnectSignal<FuncT, SigSignature, 4> { struct ConnectSignal<FuncT, SigSignature, 4> {
static void connect(SignalBase *sigb, FuncT slof, static Connection connect(SignalBase *sigb, FuncT slof,
typename FunctionPointer<FuncT>::Object *receiver) { typename FunctionPointer<FuncT>::Object *receiver) {
typedef typename Signal<SigSignature>::type SigT; typedef typename Signal<SigSignature>::type SigT;
reinterpret_cast<SigT *>(sigb)->connect( return reinterpret_cast<SigT *>(sigb)->connect(
boost::bind(slof, receiver, _1, _2, _3)); boost::bind(slof, receiver, _1, _2, _3));
} }
}; };
template <typename FuncT, typename SigSignature> template <typename FuncT, typename SigSignature>
struct ConnectSignal<FuncT, SigSignature, 5> { struct ConnectSignal<FuncT, SigSignature, 5> {
static void connect(SignalBase *sigb, FuncT slof, static Connection connect(SignalBase *sigb, FuncT slof,
typename FunctionPointer<FuncT>::Object *receiver) { typename FunctionPointer<FuncT>::Object *receiver) {
typedef typename Signal<SigSignature>::type SigT; typedef typename Signal<SigSignature>::type SigT;
reinterpret_cast<SigT *>(sigb)->connect( return reinterpret_cast<SigT *>(sigb)->connect(
boost::bind(slof, receiver, _1, _2, _3, _4)); boost::bind(slof, receiver, _1, _2, _3, _4));
} }
}; };
@@ -167,11 +172,11 @@ template <typename FuncT> SignalBase *NewSignal(FuncT f) {
} }
template <typename SigSignature, typename FuncT> template <typename SigSignature, typename FuncT>
void ConnectSignal(SignalBase *sigb, FuncT slof, Connection ConnectSignal(SignalBase *sigb, FuncT slof,
typename FunctionPointer<FuncT>::Object *receiver) { typename FunctionPointer<FuncT>::Object *receiver) {
detail::ConnectSignal<FuncT, SigSignature, return detail::ConnectSignal<FuncT, SigSignature,
FunctionPointer<FuncT>::arity>::connect(sigb, slof, FunctionPointer<FuncT>::arity>::connect(sigb, slof,
receiver); receiver);
} }
} // namespace uLib } // namespace uLib

202
src/Core/Threads.cpp Normal file
View File

@@ -0,0 +1,202 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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.
//////////////////////////////////////////////////////////////////////////////*/
#include "Threads.h"
#include <chrono>
#ifdef _OPENMP
#include <omp.h>
#endif
#ifdef __linux__
#include <pthread.h>
#include <sched.h>
#endif
namespace uLib {
Thread::Thread() : m_Running(false) {}
Thread::~Thread() {
if (m_Thread.joinable()) {
m_Thread.detach();
}
}
void Thread::Start() {
Mutex::ScopedLock lock(m_ThreadMutex);
if (m_Running) return;
m_Running = true;
m_Thread = std::thread(&Thread::ThreadEntryPoint, this);
}
void Thread::Join() {
if (m_Thread.joinable()) {
m_Thread.join();
}
}
void Thread::Detach() {
if (m_Thread.joinable()) {
m_Thread.detach();
}
}
bool Thread::IsJoinable() const {
return m_Thread.joinable();
}
bool Thread::IsRunning() const {
return m_Running;
}
void Thread::Run() {
// Override in subclasses
}
void Thread::ThreadEntryPoint() {
this->Run();
m_Running = false;
}
void Thread::Sleep(int milliseconds) {
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}
void Thread::Yield() {
std::this_thread::yield();
}
void Thread::SetAffinity(int cpu) {
#ifdef __linux__
if (m_Thread.joinable()) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
pthread_setaffinity_np(m_Thread.native_handle(), sizeof(cpu_set_t), &cpuset);
}
#endif
}
void Thread::SetAffinity(const std::vector<int>& cpus) {
#ifdef __linux__
if (m_Thread.joinable()) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
for (int cpu : cpus) {
CPU_SET(cpu, &cpuset);
}
pthread_setaffinity_np(m_Thread.native_handle(), sizeof(cpu_set_t), &cpuset);
}
#endif
}
void Thread::SetNumThreads(int n) {
#ifdef _OPENMP
omp_set_num_threads(n);
#endif
}
int Thread::GetNumThreads() {
#ifdef _OPENMP
return omp_get_max_threads();
#else
return 1;
#endif
}
int Thread::GetThreadNum() {
#ifdef _OPENMP
return omp_get_thread_num();
#else
return 0;
#endif
}
// Team Implementation //
Team::Team(int num_threads) : m_Size(num_threads), m_UseOpenMP(false) {
#ifdef _OPENMP
m_UseOpenMP = true;
if (m_Size > 0) omp_set_num_threads(m_Size);
else m_Size = omp_get_max_threads();
#else
if (m_Size <= 0) m_Size = 1;
#endif
}
Team::~Team() {
Wait();
}
void Team::Run(Task* task) {
if (!task) return;
#ifdef _OPENMP
if (m_UseOpenMP) {
#pragma omp task
task->Execute();
return;
}
#endif
// Fallback to synchronous execution if no OpenMP
task->Execute();
}
void Team::Wait() {
#ifdef _OPENMP
if (m_UseOpenMP) {
#pragma omp taskwait
}
#endif
}
void Team::SetSize(int n) {
m_Size = n;
#ifdef _OPENMP
if (m_UseOpenMP) omp_set_num_threads(m_Size);
#endif
}
void Team::SetAffinity(const std::vector<int>& cpus) {
if (cpus.empty()) return;
#ifdef __linux__
#ifdef _OPENMP
if (m_UseOpenMP) {
#pragma omp parallel
{
int tid = omp_get_thread_num();
int cpu = cpus[tid % cpus.size()];
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}
}
#endif
#endif
}
} // namespace uLib

147
src/Core/Threads.h Normal file
View File

@@ -0,0 +1,147 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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_THREADS_H
#define U_CORE_THREADS_H
#include <thread>
#include <functional>
#include <atomic>
#include <vector>
#include <deque>
#include "Core/Monitor.h"
#include "Core/Object.h"
namespace uLib {
/**
* @brief Thread class wraps std::thread and provides a common interface.
*/
class Thread : public Object {
public:
Thread();
virtual ~Thread();
/** @brief Starts the thread by calling Run(). */
void Start();
/** @brief Joins the thread. */
void Join();
/** @brief Detaches the thread. */
void Detach();
/** @brief Returns true if the thread is currently joinable. */
bool IsJoinable() const;
/** @brief Returns true if the thread is currently running. */
bool IsRunning() const;
/** @brief The entry point for the thread. Override this in subclasses. */
virtual void Run();
/** @brief Static helper to sleep the current thread. */
static void Sleep(int milliseconds);
/** @brief Static helper to yield the current thread. */
static void Yield();
/** @brief Returns the native handle of the thread. */
std::thread::native_handle_type GetNativeHandle() { return m_Thread.native_handle(); }
/** @brief Sets CPU affinity for the thread. (Linux only) */
void SetAffinity(int cpu);
/** @brief Sets CPU affinity for the thread using a list of CPUs. (Linux only) */
void SetAffinity(const std::vector<int>& cpus);
// OpenMP Support //
/** @brief Sets the number of threads for OpenMP parallel regions. */
static void SetNumThreads(int n);
/** @brief Returns the number of threads for OpenMP parallel regions. */
static int GetNumThreads();
/** @brief Returns the ID of the current thread in an OpenMP parallel region. */
static int GetThreadNum();
protected:
// Internal thread entry point
void ThreadEntryPoint();
std::thread m_Thread;
std::atomic<bool> m_Running;
mutable Mutex m_ThreadMutex;
};
/**
* @brief Task class wraps a function call to be executed by a Team.
*/
class Task : public Object {
public:
Task(std::function<void()> func) : m_Func(func) {}
virtual ~Task() = default;
/** @brief Executes the task. */
virtual void Execute() { if (m_Func) m_Func(); }
protected:
std::function<void()> m_Func;
};
/**
* @brief Team class manages a group of threads and can execute Tasks.
* This is designed to be compatible with OpenMP tasks and teams.
*/
class Team : public Object {
public:
Team(int num_threads = -1);
virtual ~Team();
/** @brief Runs a task within the team. Uses OpenMP task if available. */
void Run(Task* task);
/** @brief Waits for all tasks in the team to finish. */
void Wait();
/** @brief Sets the number of threads for this team. */
void SetSize(int n);
/** @brief Returns the number of threads in the team. */
int GetSize() const { return m_Size; }
/** @brief Sets CPU affinity for all threads in the team. */
void SetAffinity(const std::vector<int>& cpus);
protected:
int m_Size;
bool m_UseOpenMP;
std::vector<Thread*> m_Threads;
};
} // namespace uLib
#endif // U_CORE_THREADS_H

View File

@@ -0,0 +1,65 @@
#include "Core/Threads.h"
#include <iostream>
#include <vector>
#include <cassert>
#ifdef __linux__
#include <pthread.h>
#include <sched.h>
#endif
using namespace uLib;
void TestThreadAffinity() {
std::cout << "Testing Thread Affinity..." << std::endl;
#ifdef __linux__
Thread t;
t.Start();
t.SetAffinity(0); // Bind to CPU 0
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
pthread_getaffinity_np(t.GetNativeHandle(), sizeof(cpu_set_t), &cpuset);
assert(CPU_ISSET(0, &cpuset));
t.Join();
std::cout << " Passed (Thread bound to CPU 0)." << std::endl;
#else
std::cout << " Affinity not supported on this OS, skipping." << std::endl;
#endif
}
void TestTeamAffinity() {
std::cout << "Testing Team Affinity..." << std::endl;
#ifdef __linux__
#ifdef _OPENMP
Team team(2);
std::vector<int> cpus = {0, 1};
team.SetAffinity(cpus);
// We check affinity inside a parallel region
#pragma omp parallel
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
int tid = Thread::GetThreadNum();
int expected_cpu = cpus[tid % cpus.size()];
assert(CPU_ISSET(expected_cpu, &cpuset));
}
std::cout << " Passed (Team threads bound correctly)." << std::endl;
#endif
#else
std::cout << " Affinity not supported on this OS, skipping." << std::endl;
#endif
}
// Helper to get native handle if needed (oops, I forgot to add it to Thread class)
// I'll add GetNativeHandle() to Thread class in Threads.h
int main() {
TestThreadAffinity();
TestTeamAffinity();
std::cout << "All Affinity tests finished!" << std::endl;
return 0;
}

View File

@@ -23,6 +23,11 @@ set( TESTS
VectorMetaAllocatorTest VectorMetaAllocatorTest
PropertyTypesTest PropertyTypesTest
HRPTest HRPTest
MutexTest
ThreadsTest
OpenMPTest
TeamTest
AffinityTest
) )
set(LIBRARIES set(LIBRARIES
@@ -31,6 +36,7 @@ set(LIBRARIES
Boost::serialization Boost::serialization
Boost::program_options Boost::program_options
${ROOT_LIBRARIES} ${ROOT_LIBRARIES}
OpenMP::OpenMP_CXX
) )
uLib_add_tests(Core) uLib_add_tests(Core)

View File

@@ -0,0 +1,108 @@
#include "Core/Monitor.h"
#include <iostream>
#include <thread>
#include <vector>
#include <cassert>
using namespace uLib;
void TestBasicLock() {
std::cout << "Testing basic Mutex Lock/Unlock..." << std::endl;
Mutex m;
m.Lock();
m.Unlock();
assert(m.TryLock());
m.Unlock();
std::cout << " Passed." << std::endl;
}
void TestScopedLock() {
std::cout << "Testing Mutex::ScopedLock..." << std::endl;
Mutex m;
{
Mutex::ScopedLock lock(m);
assert(!m.TryLock());
}
assert(m.TryLock());
m.Unlock();
std::cout << " Passed." << std::endl;
}
void TestTimedLock() {
std::cout << "Testing Mutex TryLockFor..." << std::endl;
Mutex m;
m.Lock();
auto start = std::chrono::steady_clock::now();
bool locked = m.TryLockFor(100);
auto end = std::chrono::steady_clock::now();
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
assert(!locked);
assert(diff >= 100);
m.Unlock();
std::cout << " Passed (waited " << diff << "ms)." << std::endl;
}
void TestMacros() {
std::cout << "Testing ULIB_STATIC_LOCK and ULIB_MUTEX_LOCK macros..." << std::endl;
int counter = 0;
auto task = [&]() {
for(int i=0; i<500; ++i) {
ULIB_STATIC_LOCK(-1) {
counter++;
}
}
};
std::vector<std::thread> threads;
for(int i=0; i<4; ++i) threads.emplace_back(task);
for(auto& t : threads) t.join();
assert(counter == 2000);
Mutex m;
int counter2 = 0;
ULIB_MUTEX_LOCK(m, -1) {
counter2++;
}
assert(counter2 == 1);
std::cout << " Passed." << std::endl;
}
void TestMonitor() {
std::cout << "Testing Monitor pattern..." << std::endl;
struct Resource {
int value = 0;
void increment() { value++; }
};
Monitor<Resource> monitor(new Resource());
auto task = [&]() {
for(int i=0; i<1000; ++i) {
monitor.Access([](Resource& r) {
r.increment();
});
}
};
std::vector<std::thread> threads;
for(int i=0; i<5; ++i) threads.emplace_back(task);
for(auto& t : threads) t.join();
int final_value = monitor.Access([](Resource& r) { return r.value; });
assert(final_value == 5000);
std::cout << " Passed (final value: " << final_value << ")." << std::endl;
}
int main() {
TestBasicLock();
TestScopedLock();
TestTimedLock();
TestMacros();
TestMonitor();
std::cout << "All Mutex and Monitor tests passed!" << std::endl;
return 0;
}

View File

@@ -0,0 +1,47 @@
#include "Core/Threads.h"
#include <iostream>
#include <cassert>
#ifdef _OPENMP
#include <omp.h>
#endif
using namespace uLib;
class OpenMPThread : public Thread {
public:
void Run() override {
#ifdef _OPENMP
Thread::SetNumThreads(2);
int max = Thread::GetNumThreads();
std::cout << " OpenMP max threads in uLib::Thread: " << max << std::endl;
int shared_counter = 0;
#pragma omp parallel reduction(+:shared_counter)
{
shared_counter += 1;
}
std::cout << " Parallel region executed with " << shared_counter << " threads." << std::endl;
assert(shared_counter <= max);
#else
std::cout << " OpenMP not available, skipping parallel check." << std::endl;
assert(Thread::GetNumThreads() == 1);
#endif
}
};
int main() {
std::cout << "Testing OpenMP compatibility..." << std::endl;
#ifdef _OPENMP
std::cout << " OpenMP is AVAILABLE." << std::endl;
#else
std::cout << " OpenMP is NOT available." << std::endl;
#endif
OpenMPThread t;
t.Start();
t.Join();
std::cout << "OpenMP compatibility test finished!" << std::endl;
return 0;
}

View File

@@ -0,0 +1,40 @@
#include "Core/Threads.h"
#include <iostream>
#include <atomic>
#include <cassert>
#include <vector>
using namespace uLib;
void TestTaskTeam() {
std::cout << "Testing Task and Team..." << std::endl;
std::atomic<int> counter(0);
auto task_func = [&]() {
counter++;
Thread::Sleep(10);
};
Team team(4);
std::cout << " Team size: " << team.GetSize() << std::endl;
#ifdef _OPENMP
#pragma omp parallel
#pragma omp single
#endif
{
for (int i = 0; i < 20; ++i) {
team.Run(new Task(task_func));
}
team.Wait();
}
assert(counter == 20);
std::cout << " Passed (counter: " << counter << ")." << std::endl;
}
int main() {
TestTaskTeam();
std::cout << "All Team tests passed!" << std::endl;
return 0;
}

View File

@@ -0,0 +1,72 @@
#include "Core/Threads.h"
#include <iostream>
#include <atomic>
#include <cassert>
using namespace uLib;
class MyThread : public Thread {
public:
MyThread() : counter(0) {}
void Run() override {
for (int i = 0; i < 5; ++i) {
counter++;
Thread::Sleep(10);
}
}
std::atomic<int> counter;
};
void TestBasicThread() {
std::cout << "Testing basic Thread lifecycle..." << std::endl;
MyThread t;
assert(!t.IsRunning());
t.Start();
assert(t.IsRunning());
t.Join();
assert(!t.IsRunning());
assert(t.counter == 5);
std::cout << " Passed." << std::endl;
}
void TestThreadDetach() {
std::cout << "Testing Thread Detach..." << std::endl;
std::atomic<bool> done(false);
// Using a lambda or a simple subclass
class DetachedThread : public Thread {
public:
DetachedThread(std::atomic<bool>& d) : m_done(d) {}
void Run() override {
Thread::Sleep(50);
m_done = true;
}
std::atomic<bool>& m_done;
};
{
DetachedThread* t = new DetachedThread(done);
t->Start();
t->Detach();
// The thread object 't' is still alive here,
// but it will be destroyed soon if we delete it.
// For a detached thread using members, we MUST keep it alive.
int wait_count = 0;
while(!done && wait_count < 20) {
Thread::Sleep(10);
wait_count++;
}
delete t;
}
assert(done);
std::cout << " Passed." << std::endl;
}
int main() {
TestBasicThread();
TestThreadDetach();
std::cout << "All Thread tests passed!" << std::endl;
return 0;
}

View File

@@ -126,6 +126,12 @@ private:
G4Box *m_Solid; G4Box *m_Solid;
}; };
} // namespace Geant } // namespace Geant
} // namespace uLib } // namespace uLib

View File

@@ -1,5 +1,6 @@
set(HEADERS ContainerBox.h set(HEADERS ContainerBox.h
Cylinder.h
Dense.h Dense.h
Geometry.h Geometry.h
Transform.h Transform.h
@@ -70,4 +71,3 @@ if(BUILD_TESTING)
include(uLibTargetMacros) include(uLibTargetMacros)
add_subdirectory(testing) add_subdirectory(testing)
endif() endif()

View File

@@ -51,10 +51,8 @@ class ContainerBox : public AffineTransform, public Object {
public: public:
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// PROPERTIES // // PROPERTIES //
Property<float> Width; Property<Vector3f> p_Size;
Property<float> Height; Property<Vector3f> p_Origin;
Property<float> Depth;
virtual const char * GetClassName() const { return "ContainerBox"; } virtual const char * GetClassName() const { return "ContainerBox"; }
/** /**
@@ -63,12 +61,10 @@ public:
*/ */
ContainerBox() ContainerBox()
: m_LocalT(this), // BaseClass is Parent of m_LocalTransform : m_LocalT(this), // BaseClass is Parent of m_LocalTransform
Width(this, "Width", 1.0f), p_Size(this, "Size", Vector3f(1.0f, 1.0f, 1.0f)),
Height(this, "Height", 1.0f), p_Origin(this, "Origin", Vector3f(0.0f, 0.0f, 0.0f)) {
Depth(this, "Depth", 1.0f) { Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize);
Object::connect(&Width, &Property<float>::PropertyChanged, this, &ContainerBox::SyncSize); Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin);
Object::connect(&Height, &Property<float>::PropertyChanged, this, &ContainerBox::SyncSize);
Object::connect(&Depth, &Property<float>::PropertyChanged, this, &ContainerBox::SyncSize);
} }
/** /**
@@ -77,12 +73,11 @@ public:
*/ */
ContainerBox(const Vector3f &size) ContainerBox(const Vector3f &size)
: m_LocalT(this), : m_LocalT(this),
Width(this, "Width", size(0)), p_Size(this, "Size", size),
Height(this, "Height", size(1)), p_Origin(this, "Origin", Vector3f(0.0f, 0.0f, 0.0f)) {
Depth(this, "Depth", size(2)) { Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize);
Object::connect(&Width, &Property<float>::PropertyChanged, this, &ContainerBox::SyncSize); Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin);
Object::connect(&Height, &Property<float>::PropertyChanged, this, &ContainerBox::SyncSize); this->SetSize(size);
Object::connect(&Depth, &Property<float>::PropertyChanged, this, &ContainerBox::SyncSize);
} }
/** /**
@@ -92,20 +87,20 @@ public:
ContainerBox(const ContainerBox &copy) ContainerBox(const ContainerBox &copy)
: m_LocalT(this), // BaseClass is Parent of m_LocalTransform : m_LocalT(this), // BaseClass is Parent of m_LocalTransform
AffineTransform(copy), AffineTransform(copy),
Width(this, "Width", copy.Width), p_Size(this, "Size", copy.p_Size),
Height(this, "Height", copy.Height), p_Origin(this, "Origin", copy.p_Origin) {
Depth(this, "Depth", copy.Depth) { Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize);
Object::connect(&Width, &Property<float>::PropertyChanged, this, &ContainerBox::SyncSize); Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin);
Object::connect(&Height, &Property<float>::PropertyChanged, this, &ContainerBox::SyncSize);
Object::connect(&Depth, &Property<float>::PropertyChanged, this, &ContainerBox::SyncSize);
this->SetOrigin(copy.GetOrigin());
} }
/** /**
* @brief Sets the box origin relative to its coordinate system. * @brief Sets the box origin relative to its coordinate system.
* @param v The origin position vector. * @param v The origin position vector.
*/ */
void SetOrigin(const Vector3f &v) { m_LocalT.SetPosition(v); } void SetOrigin(const Vector3f &v) {
p_Origin = v;
m_LocalT.SetPosition(v);
}
/** /**
* @brief Gets the box origin relative to its coordinate system. * @brief Gets the box origin relative to its coordinate system.
@@ -119,9 +114,7 @@ public:
* @param v The size vector (width, height, depth). * @param v The size vector (width, height, depth).
*/ */
void SetSize(const Vector3f &v) { void SetSize(const Vector3f &v) {
Width = v(0); p_Size = v;
Height = v(1);
Depth = v(2);
Vector3f pos = this->GetOrigin(); Vector3f pos = this->GetOrigin();
m_LocalT = AffineTransform(this); // regenerate local transform m_LocalT = AffineTransform(this); // regenerate local transform
m_LocalT.Scale(v); m_LocalT.Scale(v);
@@ -213,11 +206,17 @@ signals:
// signal to emit when the box is updated // // signal to emit when the box is updated //
virtual void Updated() override { ULIB_SIGNAL_EMIT(ContainerBox::Updated); } virtual void Updated() override { ULIB_SIGNAL_EMIT(ContainerBox::Updated); }
private: private slots:
void SyncSize() { void SyncSize() {
this->SetSize(Vector3f(Width, Height, Depth)); this->SetSize(p_Size);
} }
void SyncOrigin() {
this->SetOrigin(p_Origin);
}
private:
AffineTransform m_LocalT; AffineTransform m_LocalT;
}; };

View File

@@ -34,32 +34,32 @@
namespace uLib { namespace uLib {
/** /**
* @brief Represents a cylindrical volume centered in the base circle. * @brief Represents a cylindrical volume.
* *
* Cylinder inherits from AffineTransform, which defines its parent * The cylinder orientation is defined by the Axis property (0=X, 1=Y, 2=Z).
* coordinate system. It contains an internal local transformation (m_LocalT) * By default, it is aligned with the Y axis (Axis=1).
* that defines the cylinder's actual volume (radius and height)
* relative to the emitter's origin (base circle center).
*/ */
class Cylinder : public AffineTransform, public Object { class Cylinder : public AffineTransform, public Object {
typedef AffineTransform BaseClass;
public: public:
uLibTypeMacro(Cylinder, Object)
virtual const char * GetClassName() const { return "Cylinder"; } virtual const char * GetClassName() const override { return "Cylinder"; }
/** /**
* @brief Default constructor. * @brief Default constructor. Aligns with Y by default.
* Initializes with radius 1 and height 1.
*/ */
Cylinder() : m_LocalT(this), m_Radius(1.0), m_Height(1.0) { Cylinder() : m_LocalT(this), Radius(1.0), Height(1.0), Axis(1) {
ULIB_ACTIVATE_PROPERTIES(*this);
UpdateLocalMatrix(); UpdateLocalMatrix();
} }
/** /**
* @brief Constructor with radius and height. * @brief Constructor with radius and height.
*/ */
Cylinder(float radius, float height) : m_LocalT(this), m_Radius(radius), m_Height(height) { Cylinder(float radius, float height, int axis = 1)
: m_LocalT(this), Radius(radius), Height(height), Axis(axis) {
ULIB_ACTIVATE_PROPERTIES(*this);
UpdateLocalMatrix(); UpdateLocalMatrix();
} }
@@ -67,75 +67,115 @@ public:
* @brief Copy constructor. * @brief Copy constructor.
*/ */
Cylinder(const Cylinder &copy) Cylinder(const Cylinder &copy)
: m_LocalT(this), AffineTransform(copy) { : m_LocalT(this), AffineTransform(copy), Radius(copy.Radius), Height(copy.Height), Axis(copy.Axis) {
this->SetRadius(copy.GetRadius()); ULIB_ACTIVATE_PROPERTIES(*this);
this->SetHeight(copy.GetHeight()); this->UpdateLocalMatrix();
}
/**
* @brief Serialization template for property registration and persistence.
*/
template <class ArchiveT>
void serialize(ArchiveT & ar, const unsigned int version) {
ar & HRP(Radius);
ar & HRP(Height);
ar & HRP(Axis);
} }
/** Sets the radius of the cylinder */ /** Sets the radius of the cylinder */
inline void SetRadius(float r) { inline void SetRadius(float r) {
m_Radius = r; Radius = r;
UpdateLocalMatrix(); UpdateLocalMatrix();
} }
/** Gets the radius of the cylinder */ /** Gets the radius of the cylinder */
inline float GetRadius() const { return m_Radius; } inline float GetRadius() const { return Radius; }
/** Sets the height of the cylinder */ /** Sets the height of the cylinder */
inline void SetHeight(float h) { inline void SetHeight(float h) {
m_Height = h; Height = h;
UpdateLocalMatrix(); UpdateLocalMatrix();
} }
/** Gets the height of the cylinder */ /** Gets the height of the cylinder */
inline float GetHeight() const { return m_Height; } inline float GetHeight() const { return Height; }
/** Sets the main axis (0=X, 1=Y, 2=Z) */
inline void SetAxis(int axis) {
Axis = axis;
UpdateLocalMatrix();
}
/** Gets the main axis */
inline int GetAxis() const { return Axis; }
/** /**
* @brief Returns the world transformation matrix of the cylinder's volume. * @brief Returns the world transformation matrix.
*/ */
Matrix4f GetWorldMatrix() const { return m_LocalT.GetWorldMatrix(); } Matrix4f GetWorldMatrix() const { return m_LocalT.GetWorldMatrix(); }
/** /**
* @brief Returns the local transformation matrix of the cylinder's volume. * @brief Returns the local transformation matrix.
*/ */
Matrix4f GetLocalMatrix() const { return m_LocalT.GetMatrix(); } Matrix4f GetLocalMatrix() const { return m_LocalT.GetMatrix(); }
/** /**
* @brief Transforms local cylindrical coordinates to world space. * @brief Transforms local cylindrical coordinates to world space.
* @param r Local radius (absolute). * @param r Local radius.
* @param theta Local angle in radians. * @param theta Local angle in radians (around main axis).
* @param z Local height (absolute, relative to base circle). * @param h Local height along main axis.
* @return Transformed point in world space.
*/ */
inline Vector4f GetWorldPoint(float r, float theta, float z) const { inline Vector4f GetWorldPoint(float r, float theta, float h) const {
return BaseClass::GetWorldMatrix() * Vector4f(r * std::cos(theta), r * std::sin(theta), z, 1.0f); Vector3f p;
if (Axis == 0) p = Vector3f(h, r * std::cos(theta), r * std::sin(theta));
else if (Axis == 1) p = Vector3f(r * std::cos(theta), h, r * std::sin(theta));
else p = Vector3f(r * std::cos(theta), r * std::sin(theta), h);
return AffineTransform::GetWorldMatrix() * Vector4f(p.x(), p.y(), p.z(), 1.0f);
} }
/** /**
* @brief Transforms a world point to cylindrical local space. * @brief Transforms a world point to cylindrical local space.
* @return Vector3f(r, theta, z) * @return Vector3f(r, theta, h)
*/ */
inline Vector3f GetCylindricalLocal(const Vector4f &world_v) const { inline Vector3f GetCylindricalLocal(const Vector4f &world_v) const {
Vector4f local_v = BaseClass::GetWorldMatrix().inverse() * world_v; Vector4f local_v = AffineTransform::GetWorldMatrix().inverse() * world_v;
float r = std::sqrt(local_v.x() * local_v.x() + local_v.y() * local_v.y()); float r, theta, h;
float theta = std::atan2(local_v.y(), local_v.x()); if (Axis == 0) {
return Vector3f(r, theta, local_v.z()); h = local_v.x();
r = std::sqrt(local_v.y() * local_v.y() + local_v.z() * local_v.z());
theta = std::atan2(local_v.z(), local_v.y());
} else if (Axis == 1) {
h = local_v.y();
r = std::sqrt(local_v.x() * local_v.x() + local_v.z() * local_v.z());
theta = std::atan2(local_v.z(), local_v.x());
} else {
h = local_v.z();
r = std::sqrt(local_v.x() * local_v.x() + local_v.y() * local_v.y());
theta = std::atan2(local_v.y(), local_v.x());
}
return Vector3f(r, theta, h);
} }
signals: signals:
/** Signal emitted when the cylinder geometry or transform is updated */ /** Signal emitted when properties change */
virtual void Updated() override { ULIB_SIGNAL_EMIT(Cylinder::Updated); } virtual void Updated() override {
this->UpdateLocalMatrix();
private: ULIB_SIGNAL_EMIT(Cylinder::Updated);
/** Recalculates the internal local matrix based on radius and height */
void UpdateLocalMatrix() {
m_LocalT = AffineTransform(this); // BaseClass is parent
m_LocalT.Scale(Vector3f(m_Radius, m_Radius, m_Height));
this->Updated();
} }
float m_Radius; private:
float m_Height; /** Recalculates the internal local matrix based on dimensions and axis */
void UpdateLocalMatrix() {
m_LocalT = AffineTransform(this);
if (Axis == 0) m_LocalT.Scale(Vector3f(Height, Radius, Radius));
else if (Axis == 1) m_LocalT.Scale(Vector3f(Radius, Height, Radius));
else m_LocalT.Scale(Vector3f(Radius, Radius, Height));
}
float Radius;
float Height;
int Axis;
AffineTransform m_LocalT; AffineTransform m_LocalT;
}; };

View File

@@ -1,6 +1,5 @@
set(HEADERS uLibVtkInterface.h set(HEADERS uLibVtkInterface.h
uLibVtkViewer.h uLibVtkViewer.h
vtkContainerBox.h
vtkHandlerWidget.h vtkHandlerWidget.h
vtkQViewport.h vtkQViewport.h
vtkViewport.h vtkViewport.h
@@ -10,7 +9,6 @@ set(HEADERS uLibVtkInterface.h
set(SOURCES uLibVtkInterface.cxx set(SOURCES uLibVtkInterface.cxx
uLibVtkViewer.cpp uLibVtkViewer.cpp
vtkContainerBox.cpp
vtkHandlerWidget.cpp vtkHandlerWidget.cpp
vtkQViewport.cpp vtkQViewport.cpp
vtkViewport.cpp vtkViewport.cpp

View File

@@ -34,7 +34,7 @@
#include "HEP/Detectors/DetectorChamber.h" #include "HEP/Detectors/DetectorChamber.h"
#include "Math/Dense.h" #include "Math/Dense.h"
#include "Vtk/uLibVtkInterface.h" #include "Vtk/uLibVtkInterface.h"
#include "Vtk/vtkContainerBox.h" #include "Vtk/Math/vtkContainerBox.h"
#include <vtkActor.h> #include <vtkActor.h>
#include <vtkBoxWidget.h> #include <vtkBoxWidget.h>
#include <vtkTransformPolyDataFilter.h> #include <vtkTransformPolyDataFilter.h>

View File

@@ -19,7 +19,7 @@
#include "Vtk/uLibVtkViewer.h" #include "Vtk/uLibVtkViewer.h"
#include "Vtk/HEP/Geant/vtkGeantEvent.h" #include "Vtk/HEP/Geant/vtkGeantEvent.h"
#include "Vtk/HEP/Geant/vtkEmitterPrimary.h" #include "Vtk/HEP/Geant/vtkEmitterPrimary.h"
#include "Vtk/vtkContainerBox.h" #include "Vtk/Math/vtkContainerBox.h"
#include <vtkSmartPointer.h> #include <vtkSmartPointer.h>
#include <vtkCallbackCommand.h> #include <vtkCallbackCommand.h>

View File

@@ -8,7 +8,7 @@
#include "Vtk/uLibVtkViewer.h" #include "Vtk/uLibVtkViewer.h"
#include "Vtk/HEP/Geant/vtkGeantEvent.h" #include "Vtk/HEP/Geant/vtkGeantEvent.h"
#include "Vtk/HEP/Geant/vtkEmitterPrimary.h" #include "Vtk/HEP/Geant/vtkEmitterPrimary.h"
#include "Vtk/vtkContainerBox.h" #include "Vtk/Math/vtkContainerBox.h"
#include "HEP/Detectors/DetectorChamber.h" #include "HEP/Detectors/DetectorChamber.h"
#include "Vtk/HEP/Detectors/vtkDetectorChamber.h" #include "Vtk/HEP/Detectors/vtkDetectorChamber.h"

View File

@@ -18,7 +18,7 @@
#include "Math/Units.h" #include "Math/Units.h"
#include "Vtk/uLibVtkViewer.h" #include "Vtk/uLibVtkViewer.h"
#include "Vtk/HEP/Geant/vtkGeantEvent.h" #include "Vtk/HEP/Geant/vtkGeantEvent.h"
#include "Vtk/vtkContainerBox.h" #include "Vtk/Math/vtkContainerBox.h"
#include <vtkSmartPointer.h> #include <vtkSmartPointer.h>
#include <vtkCallbackCommand.h> #include <vtkCallbackCommand.h>

View File

@@ -8,7 +8,7 @@
#include "Vtk/uLibVtkViewer.h" #include "Vtk/uLibVtkViewer.h"
#include "Vtk/HEP/Geant/vtkGeantEvent.h" #include "Vtk/HEP/Geant/vtkGeantEvent.h"
#include "Vtk/HEP/Geant/vtkEmitterPrimary.h" #include "Vtk/HEP/Geant/vtkEmitterPrimary.h"
#include "Vtk/vtkContainerBox.h" #include "Vtk/Math/vtkContainerBox.h"
#include "HEP/Detectors/DetectorChamber.h" #include "HEP/Detectors/DetectorChamber.h"
#include "Vtk/HEP/Detectors/vtkDetectorChamber.h" #include "Vtk/HEP/Detectors/vtkDetectorChamber.h"

View File

@@ -8,6 +8,8 @@ set(MATH_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/vtkTriangleMesh.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vtkTriangleMesh.cpp
${CMAKE_CURRENT_SOURCE_DIR}/vtkQuadMesh.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vtkQuadMesh.cpp
${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.cpp
PARENT_SCOPE) PARENT_SCOPE)
set(MATH_HEADERS set(MATH_HEADERS
@@ -16,6 +18,8 @@ set(MATH_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/vtkTriangleMesh.h ${CMAKE_CURRENT_SOURCE_DIR}/vtkTriangleMesh.h
${CMAKE_CURRENT_SOURCE_DIR}/vtkQuadMesh.h ${CMAKE_CURRENT_SOURCE_DIR}/vtkQuadMesh.h
${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.h ${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.h
${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.h
${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.h
PARENT_SCOPE) PARENT_SCOPE)
if(BUILD_TESTING) if(BUILD_TESTING)

View File

@@ -5,6 +5,7 @@ set(TESTS
vtkQuadMeshTest vtkQuadMeshTest
vtkVoxImageTest vtkVoxImageTest
vtkVoxImageInteractiveTest vtkVoxImageInteractiveTest
vtkContainerBoxTest
vtkContainerBoxTest2 vtkContainerBoxTest2
) )

View File

@@ -0,0 +1,41 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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.
//////////////////////////////////////////////////////////////////////////////*/
#include <stdio.h>
#include "Vtk/uLibVtkInterface.h"
#define BEGIN_TESTING(name) \
static int _fail = 0; \
printf("..:: Testing " #name " ::..\n");
#define TEST1(val) _fail += (val)==0
#define TEST0(val) _fail += (val)!=0
#define END_TESTING return _fail;

View File

@@ -0,0 +1,109 @@
/*//////////////////////////////////////////////////////////////////////////////
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
// All rights reserved
//////////////////////////////////////////////////////////////////////////////*/
#include "Vtk/uLibVtkViewer.h"
#include "Vtk/Math/vtkAssembly.h"
#include "Vtk/Math/vtkCylinder.h"
#include "Vtk/Math/vtkContainerBox.h"
#include "Math/Assembly.h"
#include "Math/Cylinder.h"
#include "Math/ContainerBox.h"
#include "Math/Units.h"
#include <iostream>
using namespace uLib;
/**
* @brief This test verifies that uLib::Vtk::Assembly correctly visualizes a collection
* of objects and that transformations applied to the assembly are propagated to its children.
* It also checks that the assembly appears as a bounding box of its contents.
*/
int main() {
std::cout << "Starting vtkAssemblyTest..." << std::endl;
// 1. Setup Core Geometry
std::cout << " - Creating core Assembly..." << std::endl;
uLib::Assembly* core_assembly = new uLib::Assembly();
core_assembly->SetInstanceName("MainAssembly");
// Add a box
std::cout << " - Adding ChildBox (Red)..." << std::endl;
ContainerBox* box = new ContainerBox();
box->SetInstanceName("ChildBox");
box->Translate(Vector3f(1.0, 0, 0));
core_assembly->AddObject(box);
// Add a cylinder
// std::cout << " - Adding ChildCylinder (Blue)..." << std::endl;
// Cylinder* cyl = new Cylinder(0.5, 2.0);
// cyl->SetInstanceName("ChildCylinder");
// cyl->Translate(Vector3f(-2.0, 0, 0));
// core_assembly->AddObject(cyl);
std::cout << " - Adding another box (Green)..." << std::endl;
ContainerBox* box2 = new ContainerBox();
box2->Scale(Vector3f(1.0, 1.0, 2.0));
box2->SetInstanceName("ChildBox2");
box2->Translate(Vector3f(0, 0, 1.0));
core_assembly->AddObject(box2);
// 2. Setup VTK Representation
std::cout << " - Creating VTK Assembly representation..." << std::endl;
Vtk::Assembly vtk_assembly(core_assembly);
// Find and colorized the children puppets
Vtk::Puppet* p_box = vtk_assembly.GetPuppet(box);
if (p_box) {
p_box->SetColor(1.0, 0.0, 0.0); // Red
} else {
std::cerr << "Warning: Could not find puppet for box!" << std::endl;
}
// Vtk::Puppet* p_cyl = vtk_assembly.GetPuppet(cyl);
// if (p_cyl) {
// p_cyl->SetColor(0.0, 0.0, 1.0); // Blue
// } else {
// std::cerr << "Warning: Could not find puppet for cylinder!" << std::endl;
// }
Vtk::Puppet* p_box2 = vtk_assembly.GetPuppet(box2);
if (p_box2) {
p_box2->SetColor(0.0, 1.0, 0.0); // Green
} else {
std::cerr << "Warning: Could not find puppet for box2!" << std::endl;
}
// 3. Test Transformation Propagation
std::cout << " - Rotating Assembly 45 degrees around Z..." << std::endl;
core_assembly->Rotate(45.0_deg, Vector3f::UnitZ());
std::cout << " - Translating Assembly up (+Y)..." << std::endl;
core_assembly->Translate(Vector3f(0, 2.0, 0));
// Notify all puppets of the change
core_assembly->Updated();
// 4. Run Visualization
std::cout << "Starting viewer (close to exit)..." << std::endl;
std::cout << "Expected: A white bounding box containing a red box and a blue cylinder, "
<< "all rotated and translated as a group." << std::endl;
Vtk::Viewer viewer;
viewer.AddPuppet(*p_box);
viewer.AddPuppet(*p_box2);
viewer.AddPuppet(vtk_assembly);
viewer.Start();
// Clean up
delete core_assembly;
delete box;
delete box2;
// delete cyl;
return 0;
}

View File

@@ -27,7 +27,7 @@
#include "Math/ContainerBox.h" #include "Math/ContainerBox.h"
#include "Math/Units.h" #include "Math/Units.h"
#include "Vtk/vtkContainerBox.h" #include "Vtk/Math/vtkContainerBox.h"
#include "testing-prototype.h" #include "testing-prototype.h"

View File

@@ -8,7 +8,7 @@
#include "Vtk/uLibVtkViewer.h" #include "Vtk/uLibVtkViewer.h"
#include "Math/ContainerBox.h" #include "Math/ContainerBox.h"
#include "Math/Units.h" #include "Math/Units.h"
#include "Vtk/vtkContainerBox.h" #include "Vtk/Math/vtkContainerBox.h"
#include <iostream> #include <iostream>
using namespace uLib; using namespace uLib;

View File

@@ -68,6 +68,7 @@ vtkPolyData *vtkContainerBox::GetPolyData() const {
void vtkContainerBox::contentUpdate() { void vtkContainerBox::contentUpdate() {
RecursiveMutex::ScopedLock lock(this->m_UpdateMutex);
if (!m_Content) if (!m_Content)
return; return;
@@ -86,33 +87,41 @@ void vtkContainerBox::contentUpdate() {
m_Axes->SetUserMatrix(nullptr); m_Axes->SetUserMatrix(nullptr);
Matrix4f transform = m_Content->GetMatrix(); Matrix4f transform = m_Content->GetMatrix();
for (int i = 0; i < 4; ++i) Matrix4fToVtk(transform, vmat);
for (int j = 0; j < 4; ++j) {
vmat->SetElement(i, j, transform(i, j));
}
root->Modified(); root->Modified();
m_BlockUpdate = true;
Puppet::Update(); Puppet::Update();
} }
void vtkContainerBox::Update() { void vtkContainerBox::Update() {
RecursiveMutex::ScopedLock lock(this->m_UpdateMutex);
if (!m_Content) return; if (!m_Content) return;
if (m_BlockUpdate) {
m_BlockUpdate = false;
return;
}
// Use Targeted Blocking: only block the feedback connection to this puppet
// boost::signals2::shared_connection_block block(m_Connection);
vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp());
if (!assembly) return; if (!assembly) return;
vtkMatrix4x4* vmat = assembly->GetUserMatrix(); vtkMatrix4x4* vmat = assembly->GetUserMatrix();
if (!vmat) return; if (!vmat) return;
Matrix4f transform = VtkToMatrix4f(vmat); Matrix4f transform = VtkToMatrix4f(vmat);
// Update uLib model's affine transform // Update uLib model's affine transform
if (m_Content->GetParent()) { // if (m_Content->GetParent()) {
Matrix4f localT = m_Content->GetParent()->GetWorldMatrix().inverse() * transform; // Matrix4f localT = m_Content->GetParent()->GetWorldMatrix().inverse() * transform;
m_Content->SetMatrix(localT); // m_Content->SetMatrix(localT);
} else { // } else {
m_Content->SetMatrix(transform); m_Content->SetMatrix(transform);
} // }
m_Content->Updated(); // Notify change m_Content->Updated(); // Notify change
} }

View File

@@ -30,6 +30,7 @@
#include "uLibVtkInterface.h" #include "uLibVtkInterface.h"
#include "vtkPolydata.h" #include "vtkPolydata.h"
#include <vtkActor.h> #include <vtkActor.h>
#include <boost/signals2/connection.hpp>
namespace uLib { namespace uLib {
namespace Vtk { namespace Vtk {
@@ -55,6 +56,8 @@ protected:
// vtkActor *m_Pivot; // vtkActor *m_Pivot;
Content *m_Content; Content *m_Content;
bool m_BlockUpdate = false;
// boost::signals2::connection m_Connection;
}; };
} // namespace Vtk } // namespace Vtk

View File

@@ -0,0 +1,144 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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.
//////////////////////////////////////////////////////////////////////////////*/
#include "Vtk/Math/vtkCylinder.h"
#include <vtkActor.h>
#include <vtkCylinderSource.h>
#include <vtkMatrix4x4.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkSmartPointer.h>
#include <vtkTransform.h>
#include "Math/vtkDense.h"
namespace uLib {
namespace Vtk {
vtkCylinder::vtkCylinder(vtkCylinder::Content *content)
: m_Actor(vtkActor::New()), m_Content(content) {
this->InstallPipe();
Object::connect(m_Content, &Content::Updated, this, &vtkCylinder::contentUpdate);
}
vtkCylinder::~vtkCylinder() {
m_Actor->Delete();
}
void vtkCylinder::contentUpdate() {
if (!m_Content)
return;
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (!root) return;
vtkMatrix4x4* vmat = root->GetUserMatrix();
if (!vmat) {
vtkNew<vtkMatrix4x4> mat;
root->SetUserMatrix(mat);
vmat = mat;
}
// Multiply the placement matrix by the volume scaling (Radius, Radius, Height)
Matrix4f transform = m_Content->GetMatrix() * m_Content->GetLocalMatrix();
Matrix4fToVtk(transform, vmat);
// Update internal alignment based on active axis
vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform());
if (alignment) {
alignment->Identity();
int axis = m_Content->GetAxis();
if (axis == 0) alignment->RotateZ(-90); // Y -> X
else if (axis == 1) ; // Y -> Y (identity)
else if (axis == 2) alignment->RotateX(90); // Y -> Z
// We keep it centered as per latest user preference in Step 677
// alignment->Translate(0, 0, 0); // Implicit
}
root->Modified();
Puppet::Update();
}
void vtkCylinder::Update() {
if (!m_Content) return;
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (!root) return;
vtkMatrix4x4* vmat = root->GetUserMatrix();
if (!vmat) return;
Matrix4f fullTransform = VtkToMatrix4f(vmat);
Matrix4f placementScale = m_Content->GetLocalMatrix().inverse();
Matrix4f transform = fullTransform * placementScale;
if (m_Content->GetParent()) {
Matrix4f localT = m_Content->GetParent()->GetWorldMatrix().inverse() * transform;
m_Content->SetMatrix(localT);
} else {
m_Content->SetMatrix(transform);
}
m_Content->Updated();
}
void vtkCylinder::InstallPipe() {
if (!m_Content)
return;
vtkNew<vtkCylinderSource> cylinder;
cylinder->SetRadius(1.0);
cylinder->SetHeight(1.0);
cylinder->SetResolution(32);
vtkNew<vtkTransform> alignment;
alignment->Identity();
alignment->Translate(0, 0, -0.5);
// Default to Y alignment (Identity) as per latest request
int axis = m_Content->GetAxis();
if (axis == 0) alignment->RotateZ(-90);
else if (axis == 2) alignment->RotateX(90);
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(cylinder->GetOutputPort());
m_Actor->SetMapper(mapper);
m_Actor->SetUserTransform(alignment);
m_Actor->GetProperty()->SetRepresentationToWireframe();
m_Actor->GetProperty()->SetAmbient(0.6);
this->SetProp(m_Actor);
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (root) {
vtkNew<vtkMatrix4x4> vmat;
Matrix4fToVtk(m_Content->GetMatrix() * m_Content->GetLocalMatrix(), vmat);
root->SetUserMatrix(vmat);
}
}
} // namespace Vtk
} // namespace uLib

View File

@@ -0,0 +1,67 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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_VTKCYLINDER_H
#define U_VTKCYLINDER_H
#include "Math/Cylinder.h"
#include "Vtk/uLibVtkInterface.h"
#include <vtkActor.h>
namespace uLib {
namespace Vtk {
/**
* @brief VTK representation of the uLib::Cylinder object.
*
* This class wraps a vtkCylinderSource and synchronizes it with the
* mathematical state of a Cylinder object. It manages the alignment
* between VTK's Y-centered cylinder and uLib's Z-based coordinate system.
*/
class vtkCylinder : public Puppet {
typedef Cylinder Content;
public:
vtkCylinder(Content *content);
virtual ~vtkCylinder();
/** Synchronizes the VTK actor with the uLib model matrix */
virtual void contentUpdate();
/** Synchronizes the uLib model matrix with the VTK actor (e.g., after UI manipulation) */
virtual void Update();
protected:
/** Sets up the VTK visualization pipeline */
virtual void InstallPipe();
vtkActor *m_Actor;
Content *m_Content;
};
} // namespace Vtk
} // namespace uLib
#endif // U_VTKCYLINDER_H

View File

@@ -1,7 +1,6 @@
# TESTS # TESTS
set( TESTS set(TESTS
vtkViewerTest vtkViewerTest
vtkContainerBoxTest
vtkHandlerWidget vtkHandlerWidget
PuppetPropertyTest PuppetPropertyTest
# vtkVoxImageTest # vtkVoxImageTest

View File

@@ -25,7 +25,7 @@
#include "Math/ContainerBox.h" #include "Math/ContainerBox.h"
#include "Vtk/uLibVtkViewer.h" #include "Vtk/uLibVtkViewer.h"
#include "Vtk/vtkContainerBox.h" #include "Vtk/Math/vtkContainerBox.h"
#include "Vtk/vtkHandlerWidget.h" #include "Vtk/vtkHandlerWidget.h"
#include "testing-prototype.h" #include "testing-prototype.h"

View File

@@ -31,6 +31,7 @@
#include <vector> #include <vector>
#include "Core/Object.h" #include "Core/Object.h"
#include "Core/Property.h" #include "Core/Property.h"
#include "Core/Monitor.h"
// vtk classes forward declaration // // vtk classes forward declaration //
class vtkProp; class vtkProp;
@@ -112,6 +113,7 @@ protected:
void RemoveProp(vtkProp *prop); void RemoveProp(vtkProp *prop);
std::vector<uLib::PropertyBase*> m_DisplayProperties; std::vector<uLib::PropertyBase*> m_DisplayProperties;
mutable uLib::RecursiveMutex m_UpdateMutex;
private: private:
Puppet(const Puppet&) = delete; Puppet(const Puppet&) = delete;

View File

@@ -1,5 +1,6 @@
#include "vtkObjectsContext.h" #include "vtkObjectsContext.h"
#include "vtkContainerBox.h" #include "Vtk/Math/vtkContainerBox.h"
#include "Vtk/Math/vtkCylinder.h"
#include "HEP/Detectors/vtkDetectorChamber.h" #include "HEP/Detectors/vtkDetectorChamber.h"
#include <vtkAssembly.h> #include <vtkAssembly.h>
@@ -128,6 +129,8 @@ Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) {
return new vtkContainerBox(static_cast<uLib::ContainerBox*>(obj)); return new vtkContainerBox(static_cast<uLib::ContainerBox*>(obj));
} else if (std::strcmp(className, "DetectorChamber") == 0) { } else if (std::strcmp(className, "DetectorChamber") == 0) {
return new vtkDetectorChamber(static_cast<uLib::DetectorChamber*>(obj)); return new vtkDetectorChamber(static_cast<uLib::DetectorChamber*>(obj));
} else if (std::strcmp(className, "Cylinder") == 0) {
return new vtkCylinder(static_cast<uLib::Cylinder*>(obj));
} }
// Fallback if we don't know the exact class but it might be a context itself // Fallback if we don't know the exact class but it might be a context itself