Skip to content

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

  1. User manipulates a 3D gizmo in VTK viewport:
  2. HandlerWidget calculates new matrix \(\rightarrow\) Prop3D::SyncFromVtk() calls tr->FromMatrix(mat).
  3. TRS::FromMatrix(mat) calls this->Update(), emitting Object::Updated and executing NotifyPropertiesUpdated().
  4. NotifyPropertiesUpdated() calls PropertyUpdated() on Position, Rotation, and Scale properties.
  5. PropertyUpdated() publishes payload:
  6. LocalSignalBackend::Instance().PublishChange(payload) fires subscriber callbacks.
  7. Qt Widget refreshes UI:
  8. PropertyWidgetBase receives the callback and emits propertyValueChangedExternal().
  9. 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.