Skip to content

Notifications

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

Notifications

Overview

serp::Notification<Args...> is a typed, multi-subscriber event broadcast primitive. When a service fires a notification, all connected subscribers are called with the provided arguments — each on their own EventLoop.

Notifications model transient events: things that happen at a point in time and carry no persistent state. For state that persists between events, use serp::Property<T> instead.

In SerpIDL, notifications are declared as broadcasts.


serp::Notification<Args...>

template<typename... Args>
class serp::Notification;

Notifications are declared in the generated service interface:

serp::Notification<std::string, int> trackChanged;
serp::Notification<> shutdownRequested;
serp::Notification<ErrorCode, std::string> errorOccurred;

Args... may be empty (for a signal with no payload), a single type, or a parameter pack of multiple types.


Publishing: fire()

The owning service calls fire() to emit the notification to all current subscribers:

trackChanged.fire("song.mp3", 42);

fire() is synchronous on the caller's thread — it enqueues the event to each subscriber's EventLoop and returns immediately. There is no blocking.


Subscribing: connect()

Any service or object may call connect() to receive the notification:

trackChanged.connect([this](std::string track, int id) {
    updateNowPlaying(track, id);
});

The lambda is called on the subscriber's EventLoop. Subscribers do not need to guard against data races on their own state.

connect() returns a connection handle. The subscription remains active as long as the handle is alive:

auto conn = trackChanged.connect([](std::string track, int id) { /* ... */ });
// Destroying conn → unsubscribes

Store the handle as a member to keep the subscription for the lifetime of the subscriber:

class DisplayService : public DisplayServiceBase {
private:
    serp::Connection m_trackConn;
};

Full Example

// Interface declaration (generated in IPlayerService)
serp::Notification<std::string, int> trackChanged;

// Publisher — PlayerService fires when the track changes
class PlayerService : public PlayerServiceBase {
    void onTrackLoaded(std::string path, int id) {
        // ... internal state update ...
        trackChanged.fire(path, id);
    }
};

// Subscriber — DisplayService listens
class DisplayService : public DisplayServiceBase {
protected:
    void onInitialize() override {
        m_trackConn = m_player.trackChanged.connect([this](std::string track, int id) {
            // Called on DisplayService's EventLoop
            showTrack(track, id);
        });
    }

private:
    serp::Connection m_trackConn;
};

Notifications vs. Properties

Feature serp::Notification<Args...> serp::Property<T>
Carries current state No Yes — get() returns current value
Late subscribers see past events No Yes (current value)
Multiple arguments Yes — variadic No — single T
Models Transient event Persistent state
SerpIDL keyword broadcast property

Rule of thumb: if a consumer needs to know "what is the current value?" use a Property. If they only need to react to "something just happened", use a Notification.


Zero-Argument Notifications

Notifications with no payload are valid and useful as pure signals:

// Declaration
serp::Notification<> shutdownRequested;

// Fire
shutdownRequested.fire();

// Subscribe
shutdownRequested.connect([this]() {
    beginGracefulShutdown();
});

Cross-Process Notifications

When the publishing service is hosted in a remote process, the proxy object exposes the same serp::Notification API. The transport layer delivers fire() calls to all remote subscribers. Subscriber code requires no changes.


Generated Interface

The SerpIDL code generator exposes notifications as typed members of the interface class:

// Generated IPlayerService
class IPlayerService {
public:
    serp::Call<PlayerService, void, std::string> play;
    serp::Property<bool> isPlaying;
    serp::Notification<std::string, int> trackChanged;   // broadcast
    serp::Notification<ErrorCode> playbackError;         // broadcast
};

The generator derives the argument types from the IDL broadcast declaration. Subscribers get full type-checked access with no manual casting.


See Also

Clone this wiki locally