Callbacks and Subscriptions
The Broadcaster Model
Operations you start return a Task, but plenty happens that you did not initiate: players join, properties change, events arrive.
These server-initiated signals are delivered through public callback members on RealtimeClient, OnPlayerJoined, OnRoomJoined, OnDisconnected and the rest, each of which is a RealtimeCore::Common::Broadcaster.
A Broadcaster<Signature> is a multicast callback list.
Subscribe(handler) registers any callable matching the signature and returns a Subscription handle. When the client broadcasts, every live, non-blocked subscriber is invoked in turn.
All callbacks fire inside Service(), on the thread that calls it.
There is no cross-thread delivery, and nothing fires between two Service() calls; see the threading model for the wider rules.
Subscribing to Client Callbacks
Subscribing takes a lambda, or any callable, matching the callback's signature.
Custom events are the one exception to the member-per-callback pattern: they are registered with SubscribeEvent(), which filters by event code and returns the same Subscription type, and are covered on the Custom Events page.
C++
RealtimeCore::Common::Subscription joinSub = client.OnPlayerJoined.Subscribe(
[](const PlayerView& player) {
std::printf("player %d joined\n", player.Number);
});
RealtimeCore::Common::Subscription eventSub = client.SubscribeEvent(
[](uint8_t eventCode, int senderId, std::span<const uint8_t> data) {
std::printf("event %d from player %d (%zu bytes)\n", eventCode, senderId, data.size());
});
Subscription Lifetime
A raw Subscription does not auto-unsubscribe when it is destroyed; the handler stays registered until you call Unsubscribe().
IsSubscribed() and operator bool report whether the handle is still attached.
Anything the handler captures must stay valid for as long as the subscription is live.
A lambda capturing this on an object that is destroyed while still subscribed is the classic dangling-callback bug, tie the subscription's lifetime to the captured object's lifetime, which is exactly what the RAII helpers are for.
ScopedSubscription
ScopedSubscription is the RAII wrapper: it unsubscribes automatically when destroyed and is the recommended default for holding a subscription.
It is move-only, constructs implicitly from a Subscription, and Release() hands the raw handle back if you need to manage it manually after all.
Held as a class member, it guarantees the handler never outlives the object it captures.
C++
class ScoreBoard
{
public:
explicit ScoreBoard(RealtimeClient& client)
: _playerJoined(client.OnPlayerJoined.Subscribe(
[this](const PlayerView& player) { _names.push_back(player.Name); }))
{
}
private:
std::vector<RealtimeCore::Common::StringType> _names;
RealtimeCore::Common::ScopedSubscription _playerJoined;
};
SubscriptionBag
SubscriptionBag collects any number of subscriptions for bulk lifetime management.
Add with +=, tear everything down with UnsubscribeAll() (or by destroying the bag) and inspect with Count() and IsEmpty().
One bag typically covers one gameplay system.
C++
RealtimeCore::Common::SubscriptionBag subscriptions;
subscriptions += client.OnRoomJoined.Subscribe([] { std::puts("room joined"); });
subscriptions += client.OnRoomLeft.Subscribe([] { std::puts("room left"); });
subscriptions += client.OnPlayerLeft.Subscribe(
[](int playerNumber, bool isInactive) {
std::printf("player %d left (inactive: %d)\n", playerNumber, isInactive);
});
// later, when the system shuts down:
subscriptions.UnsubscribeAll();
Blocking a Subscription
Block() temporarily silences a handler without unsubscribing it; Unblock() reactivates it and IsBlocked() queries the state.
This is handy for ignoring reactions to changes you are about to cause yourself, or for muting UI updates during a cutscene or loading screen without losing the registration.
Subscribing and Unsubscribing During Dispatch
Dispatch is re-entrancy safe: a handler may subscribe new handlers or unsubscribe any handler, including itself, while a broadcast is running. The change is deferred and applied once the current broadcast finishes, so the in-flight broadcast still runs against the subscriber list it started with.
Accessing Realtime from within a Callback
Calling back into the client from inside a handler is safe, you can send events, change properties or start operations such as JoinRoom() directly from a callback.
The one exception is the pump itself: never call Service(true) or the DispatchIncomingCommands() method from within a callback, because they are what is currently executing your callback and must not re-enter.
Callback Overview
| Callback | Fires When |
|---|---|
OnDisconnected |
The connection is lost or closed, with the DisconnectCause. |
OnError |
An asynchronous error occurs outside any pending operation. |
OnWarning |
The SDK reports a non-fatal warning code. |
OnRoomJoined |
The local client enters a room. |
OnRoomLeft |
The local client leaves a room. |
OnPlayerJoined |
A remote player joins the current room. |
OnPlayerLeft |
A player leaves the current room or becomes inactive. |
OnMasterClientChanged |
Mastership moves to another player. |
OnRoomPropertiesChanged |
The room's custom properties change. |
OnPlayerPropertiesChanged |
A player's custom properties change. |
OnPropertiesChangeFailed |
A property update is rejected by the server. |
OnDirectMessage |
A direct (P2P) message arrives. |
OnDirectConnectionEstablished |
A direct connection to a remote player is established. |
OnDirectConnectionFailed |
A direct connection attempt fails. |
OnRoomListUpdated |
The lobby's room list changes. |
OnLobbyStats |
Lobby statistics arrive. |
OnAppStatsUpdated |
Application statistics update. |
OnCustomAuthStep |
A custom authentication provider requests another step. |
OnCustomOperationResponse |
A custom server operation responds. |
OnCacheSliceChanged |
The room's event cache slice index changes. |
Custom events are absent from this table because they are not a Broadcaster member, register them with SubscribeEvent() instead, see Custom Events.
The full signatures are listed in the client callbacks reference.
Back to top