Skip to content

Properties

Oleksandr Geronime edited this page Jun 27, 2026 · 2 revisions

Properties

Overview

serp::Property<T> is a typed, observable state variable. It holds a single value of type T and notifies subscribers whenever that value changes. Properties model persistent state — unlike notifications, subscribers can always read the current value.

Properties are declared in the generated service interface and managed by the owning service. Cross-service and cross-process access uses the same API.


serp::Property<T>

template<typename T>
class serp::Property;

Properties are typically declared as members of the generated IService interface class:

serp::Property<double> temperature;
serp::Property<bool> isConnected;
serp::Property<std::vector<std::string>> playlist;

Reading the Current Value

get() returns the current value synchronously. It is safe to call from any thread:

double current = temperature.get();

For complex types, prefer reading the value inside a subscription callback to avoid stale data.


Writing: mutate()

Only the owning service may write to a property. Use mutate() to set a new value:

temperature.mutate(22.5);

mutate() dispatches the write to the owning service's EventLoop and then notifies all subscribers. If called from the owner's own thread, the dispatch is immediate. If called from another thread, the write is queued.

Subscribers are notified in the order they connected. Each subscriber receives the callback on its own EventLoop.


Subscribing to Changes: connect()

Use connect() to register a lambda that is called whenever the property value changes:

temperature.connect([this](double val) {
    updateDisplay(val);
});

The lambda is called on the subscriber's EventLoop, not the owner's. This means:

  • No cross-thread races in the subscriber's handler.
  • The callback may be delayed if the subscriber's EventLoop is busy.

connect() returns a connection handle. Destroying the handle disconnects the subscription:

auto conn = temperature.connect([](double val) { /* ... */ });
// conn goes out of scope → subscription is removed

To keep a subscription alive for the lifetime of the service, store the handle as a member.


Readonly Properties

Properties declared as readonly in SerpIDL can only be written by the owning service. External callers can read and subscribe but cannot call mutate(). The generated interface enforces this at compile time:

// In IMyService (generated):
serp::Property<double> temperature;   // readwrite — callers may mutate
serp::ReadonlyProperty<int> errorCount; // readonly — only owner writes

Full Example

// Interface (generated in IMyService)
serp::Property<double> temperature;

// Owner service — writing
class ThermostatService : public ThermostatServiceBase {
protected:
    void onInitialize() override {
        m_pollTimer = serp::Timer::create(eventLoop(), std::chrono::seconds(5), [this]() {
            double reading = m_sensor.read();
            temperature.mutate(reading);   // dispatch write on owner's EventLoop
        });
        m_pollTimer->start();
    }
};

// Consumer service — reading and subscribing
class DisplayService : public DisplayServiceBase {
protected:
    void onInitialize() override {
        // Read current value immediately
        double initial = m_thermostat.temperature.get();
        showTemperature(initial);

        // Subscribe to future changes
        m_tempConn = m_thermostat.temperature.connect([this](double val) {
            // Called on DisplayService's EventLoop
            showTemperature(val);
        });
    }

private:
    serp::Connection m_tempConn;
    std::shared_ptr<serp::Timer> m_pollTimer;
};

Properties vs. Notifications

Feature serp::Property<T> serp::Notification<Args...>
Holds current value Yes — get() always works No — event only
Subscriber gets current value on connect Implementation-defined No
Can fire multiple times Yes, on each change Yes, each fire() call
Models Persistent state Transient event

Use a Property when consumers need to know the current state at any time. Use a Notification for events that have no persistent representation.


Cross-Process Properties

When a service is hosted remotely, the proxy object exposes the same serp::Property<T> API. The transport layer synchronizes the value and delivers change notifications across the process boundary. No changes to consumer code are required.


See Also

Clone this wiki locally