Client and Service Loop
Creating a Client
RealtimeClient, in the RealtimeCore::Matchmaking namespace, is the single entry point to the SDK.
One instance represents one connection to the Photon Cloud and owns everything that belongs to it: the connection state, the current room, the callbacks and all pending operations.
Every feature described in this manual is a method or a callback member on this one class.
A client is constructed from a ClientConstructOptions struct.
AppId is the only required field, it identifies your application on the Photon Cloud and comes from the Photon Dashboard.
Setting AppVersion is recommended, because clients with different AppVersion values do not see each other during matchmaking.
The following creates a client that is ready to connect.
C++
using namespace RealtimeCore::Matchmaking;
ClientConstructOptions options;
options.AppId = PHOTON_STR("your-app-id");
options.AppVersion = PHOTON_STR("1.0");
auto client = std::make_unique<RealtimeClient>(options);
The class is final, non-copyable and non-movable.
Decide where the client lives up front, as a member of a long-lived game system or in a std::unique_ptr and hand out references to it.
Construction Options
Everything in ClientConstructOptions is baked into the client for its whole lifetime and cannot be changed after construction.
Settings that can be adjusted at runtime are covered on the Configuration page.
| Field | Type | Default | Description |
|---|---|---|---|
AppId |
StringType |
empty | The application id from the Photon Dashboard. Required. |
AppVersion |
StringType |
empty | Version string that separates clients during matchmaking. |
Protocol |
ConnectionProtocol |
Default |
Transport protocol. Default resolves to UDP, or to WebSocket on WASM builds. |
UseAlternativePorts |
bool |
false |
Use the alternative Photon Cloud port range. |
RegionSelection |
RegionSelectionMode |
Default |
How the connect region is chosen. See Connection and Regions. |
AutoLobbyStats |
bool |
false |
Have the server push lobby statistics automatically via OnLobbyStats. |
DisconnectTimeoutMs |
std::optional<int> |
SDK default | Time without acknowledgements before the connection counts as lost. |
PingIntervalMs |
std::optional<int> |
SDK default | Interval between keep-alive pings. |
EnableCrc |
std::optional<bool> |
SDK default | CRC checksums on packets. Must be decided before connecting. |
SentCountAllowance |
std::optional<int> |
SDK default | Resend attempts for a reliable command before the connection counts as lost. |
QuickResendAttempts |
std::optional<uint8_t> |
SDK default | Additional early resends for reliable commands. |
LimitOfUnreliableCommands |
std::optional<int> |
SDK default | Cap on buffered incoming unreliable commands. |
The optional network tunables in the lower half of the table fall back to the SDK's built-in values when left unset. Most of them also have runtime setters on the client; see Configuration for the full list and for which of them can change while connected.
The Service Loop
Service() is the engine of the whole SDK.
One call performs the pending network I/O, dispatches incoming data to your subscribed callbacks and resumes any coroutines that are suspended on an operation.
Nothing progresses without it: no callback fires and no Task completes between two calls to Service().
Call Service() regularly from your game loop, typically once per frame.
It is cheap when there is nothing to do, and it is safe to call while disconnected.
Long gaps between calls delay acknowledgements and event delivery and can eventually time out the connection.
The minimal pump drives Service() until a pending operation reports completion.
C++
Task<Result<void>> connectTask = client->Connect();
while (!connectTask.IsReady())
{
client->Service();
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
Result<void> connected = connectTask.Get();
Granular Pump Control
Service() bundles three steps: exchanging data with the network layer, dispatching queued incoming commands and sending queued outgoing commands.
The granular methods below give advanced control over when each step happens, for example to keep the connection alive while a level loads, or to batch sends at a lower frequency than the frame rate.
If you bypass Service(), you must call ServiceBasic() regularly yourself.
| Method | What It Does | When to Use It |
|---|---|---|
Service(dispatchIncomingCommands = true) |
The full pump: network I/O, dispatch of the queued incoming commands and sending of the queued outgoing commands. | The default; call once per frame. Pass false to queue incoming commands without dispatching them, for example while loading a level. |
ServiceBasic() |
Exchanges data with the system's network layer only; nothing is dispatched. | Call regularly whenever you replace Service() with the granular methods. |
SendOutgoingCommands() |
Transmits the queued outgoing commands, aggregating them into as few packets as possible. Returns true while more queued commands remain to be sent. |
Call at your own send frequency; fewer calls mean fewer packets but higher latency. |
SendAcksOnly() |
Sends only acknowledgements (UDP) or a ping (TCP and WebSocket). | Keeps the connection alive while deliberately pausing real data sends. |
DispatchIncomingCommands() |
Dispatches a single queued incoming command and returns true if one was dispatched. |
Call in a loop until it returns false, or budget a fixed number of dispatches per frame. |
Threading Model
The client is not thread-safe.
Every call, including Service(), must happen on the same thread.
If other threads need to interact with the client, funnel those interactions through a queue that the servicing thread drains.
Callbacks and coroutine resumptions run inside Service(), on the thread that calls it.
Handlers can therefore touch game state without locks, but they must not block: a stalled handler stalls the whole network pump.
ConnectOptions.UseBackgroundSendReceiveThread (true by default) moves only the low-level socket send and receive onto a background thread.
It does not change the single-thread rule: all API calls, callbacks and coroutine resumptions stay on the thread that calls Service().
This flag is currently only supported on the Nintendo Switch. On all other platforms it's value is ignored and the client always behaves as if it was 'false'.
Client State Queries
GetState() returns the current ConnectionState: Disconnected, Connecting, Connected, JoiningRoom, InRoom, LeavingRoom or Disconnecting.
IsConnected(), IsInRoom() and IsInLobby() are convenience shortcuts for the common checks.
State changes are also announced through callbacks such as OnRoomJoined, OnRoomLeft and OnDisconnected.
See Connection and Regions for the connection lifecycle and Callbacks and Subscriptions for how to subscribe.