Thread-Safe Signaling & Backend Subscribers¶
Overview¶
uprops provides a dual signaling architecture:
1. Direct Signal/Slot Binding: Powered by boost::signals2::signal<void()> on individual Property<T> instances.
2. Global Decoupled Pub/Sub Messaging: Powered by LocalSignalBackend, allowing background threads, network events, or 3D viewport interactions to broadcast property modifications safely to UI subscribers without tight coupling.
sequenceDiagram
participant Source as 3D Gizmo / Script / Thread
participant Prop as Property<T> / TRS
participant LSB as LocalSignalBackend
participant QtWidget as Qt PropertyWidget (GUI Thread)
Source->>Prop: Set(newValue) / FromMatrix()
Prop->>Prop: PropertyUpdated()
Prop->>LSB: PublishChange(payload)
LSB->>QtWidget: Invoke Subscriber Callback
QtWidget->>QtWidget: emit propertyValueChangedExternal()
QtWidget->>QtWidget: onPropertyValueChanged() -> Refresh UI
PropertyChangePayload¶
When a property value changes, PropertyUpdated() constructs a PropertyChangePayload structure containing type-erased metadata:
namespace uprops {
struct PropertyChangePayload {
std::string propertyName; // Short name, e.g. "Position"
std::string qualifiedName; // Full group name, e.g. "Transform.Position"
std::string valueAsString; // Serialized value, e.g. "1.0 2.0 3.0"
std::string typeName; // C++ type name, e.g. "Vector3f"
};
} // namespace uprops
LocalSignalBackend Pub/Sub Manager¶
LocalSignalBackend.h implements a thread-safe singleton pub/sub channel for property notifications:
namespace uprops {
class UPROPS_EXPORT LocalSignalBackend : public IPropertyBackend {
public:
static LocalSignalBackend& Instance();
void PublishChange(const PropertyChangePayload& payload) override;
std::shared_ptr<IPropertySubscriber> SubscribeCallback(
std::function<void(const PropertyChangePayload&)> callback
) override;
void Unsubscribe(IPropertySubscriber* subscriber) override;
};
} // namespace uprops
GUI Real-Time View Synchronization (Qt / gcompose)¶
Qt GUI widgets in app/gcompose/src/PropertyWidgets.cpp inherit from PropertyWidgetBase. In PropertyWidgetBase, a subscriber is registered with LocalSignalBackend:
PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent)
: QWidget(parent), m_BaseProperty(prop) {
// Thread-safe Qt signal connection across threads
connect(this, &PropertyWidgetBase::propertyValueChangedExternal,
this, &PropertyWidgetBase::onPropertyValueChanged);
// Subscribe to LocalSignalBackend property change events
m_BackendSubscriber = LocalSignalBackend::Instance().SubscribeCallback([this](const PropertyChangePayload& payload) {
if (payload.qualifiedName == m_BaseProperty->GetQualifiedName() ||
payload.propertyName == m_BaseProperty->GetName()) {
emit propertyValueChangedExternal();
}
});
}
Real-Time Update Flow¶
- User manipulates a 3D gizmo in VTK viewport:
HandlerWidgetcalculates new matrix \(\rightarrow\)Prop3D::SyncFromVtk()callstr->FromMatrix(mat).TRS::FromMatrix(mat)callsthis->Update(), emittingObject::Updatedand executingNotifyPropertiesUpdated().NotifyPropertiesUpdated()callsPropertyUpdated()onPosition,Rotation, andScaleproperties.PropertyUpdated()publishes payload:LocalSignalBackend::Instance().PublishChange(payload)fires subscriber callbacks.- Qt Widget refreshes UI:
PropertyWidgetBasereceives the callback and emitspropertyValueChangedExternal().- Qt event loop executes
onPropertyValueChanged(), updating line edits and sliders in real time!
Python Integration & Remote Synchronization¶
LocalSignalBackend is also exposed to Python via Pybind11 (uLib_python.Core.LocalSignalBackend). This allows Python scripts to listen to or trigger property updates in real time, as well as synchronize across IPC socket connections. See C++ and Python Property Synchronization for full examples.