Configuration
Construction-Time vs Runtime Settings
Configuration comes in two tiers.
Construction options are baked when the client is created and stay fixed for its lifetime.
The network tunables can also be read back and adjusted later on the client instance: through getter and setter pairs in C++ and C, and through the properties of client.config in JavaScript.
The C API takes only the App Id and the App Version at creation, so it reaches the tunables through its setters alone.
Where a setting exists in both tiers, each cell names the construction option first and the runtime accessor second.
| Setting | C++ | C | JavaScript | Takes Effect |
|---|---|---|---|---|
| App id and version | AppId, AppVersion |
realtime_create() arguments |
appId, appVersion |
At construction. |
| Transport protocol | Protocol |
— | protocol |
At construction. |
| Alternative ports | UseAlternativePorts |
— | useAlternativePorts |
At construction. |
| Region selection mode | RegionSelection |
— | regionSelection |
At construction. |
| Lobby statistics push | AutoLobbyStats |
— | autoLobbyStats |
At construction. |
| Disconnect timeout | DisconnectTimeoutMs, SetDisconnectTimeout() |
realtime_set_disconnect_timeout() |
disconnectTimeoutMs, config.disconnectTimeoutMs |
Immediately, also while connected. |
| Ping interval | PingIntervalMs, SetPingInterval() |
realtime_set_ping_interval() |
pingIntervalMs, config.pingIntervalMs |
Immediately, also while connected. |
| Sent count allowance | SentCountAllowance, SetSentCountAllowance() |
— | sentCountAllowance, config.sentCountAllowance |
Immediately, also while connected. |
| Quick resend attempts | QuickResendAttempts, SetQuickResendAttempts() |
realtime_set_quick_resend_attempts() |
quickResendAttempts, config.quickResendAttempts |
Immediately, also while connected. |
| CRC checksums | EnableCrc, SetCrcEnabled() |
realtime_set_crc_enabled() |
enableCrc, config.crcEnabled |
Only while disconnected. |
| Unreliable command limit | LimitOfUnreliableCommands, SetLimitOfUnreliableCommands() |
realtime_set_limit_of_unreliable_commands() |
limitOfUnreliableCommands, config.limitOfUnreliableCommands |
Immediately, also while connected. |
| Auto-join lobby | SetAutoJoinLobby() |
realtime_set_auto_join_lobby() |
setAutoJoinLobby() |
On the next connect. |
| Traffic statistics | SetTrafficStatsEnabled() |
realtime_set_traffic_stats_enabled() |
setTrafficStatsEnabled() |
Immediately. |
Network Tunables
Six tunables shape the transport behavior. The disconnect timeout sets how long the connection may stay unresponsive before it counts as lost, the ping interval controls the keep-alive rate, the sent count allowance is the resend budget for reliable commands, quick resend attempts accelerate early resends, CRC checksums add payload checksums on UDP and the unreliable command limit caps the incoming unreliable command queue.
CRC checksums must be decided before the connection is established. Enable them in the construction options, or through the runtime setter while disconnected; while connected the setter is rejected and the value stays unchanged.
The other five tunables take effect immediately, even on a live connection. The peer reads the current values on every service tick, so raising the disconnect timeout mid-session, for example, applies to the very next timeout check.
Three of the tunables are UDP-specific: the sent count allowance, quick resend attempts and the unreliable command limit have no effect on TCP or WebSocket connections.
The disconnect timeout and the ping interval apply on every transport, and quick resend attempts are clamped to a maximum of 4.
A JavaScript client always connects over WebSocket, so of the six tunables only the disconnect timeout and the ping interval affect its connection.
Set the tunables you know up front, when the client is created:
C++
ClientConstructOptions options;
options.AppId = PHOTON_STR("your-app-id");
options.AppVersion = PHOTON_STR("1.0");
options.DisconnectTimeoutMs = 10000;
options.PingIntervalMs = 1000;
options.EnableCrc = true;
RealtimeClient client(options);
C
/* The construction-time tunables are not on the ABI, so the same values are
applied through the runtime setters right after creating the client. */
RealtimeHandle client = realtime_create("your-app-id", "1.0", queue);
realtime_set_disconnect_timeout(client, 10000);
realtime_set_ping_interval(client, 1000);
realtime_set_crc_enabled(client, 1); /* only accepted while disconnected */
/* Setting CRC right after realtime_create is safe because the client is
still disconnected at that point, which is the one window the setter
accepts. realtime_get_* counterparts read every value back.
SetSentCountAllowance is the one tunable without a C entry point, so the
resend budget keeps its SDK default over the ABI. */
JavaScript
import { createRealtimeClient } from '@photonengine/realtime-core';
const client = await createRealtimeClient({
appId: 'your-app-id',
appVersion: '1.0',
disconnectTimeoutMs: 10000,
pingIntervalMs: 1000,
});
client.config reads every tunable back and changes it at runtime, for example client.config.pingIntervalMs = 500.
Transport Protocols
The protocol construction option selects the transport.
In C++, ClientConstructOptions.Protocol is UDP (the recommended default), TCP, WS or WSS, and ConnectionProtocol::Default resolves per platform: WebSocket on WASM builds, where raw UDP is unavailable, and UDP everywhere else.
The C API has no protocol setting, so a C client uses that default.
In JavaScript, the protocol option is 'wss' (the default) or 'ws'; use 'wss' from an https:// page.
Two related knobs sit next to the protocol choice.
The alternative ports option (UseAlternativePorts in C++, useAlternativePorts in JavaScript) is set at construction and switches to Photon's alternative port range, which helps when restrictive firewalls block the standard ports.
The datagram encryption connect option (ConnectOptions.TryUseDatagramEncryption in C++, tryUseDatagramEncryption in JavaScript) enables DTLS encryption on UDP connections.
Server Time
The server time is the synced Photon server clock in milliseconds: GetServerTime() in C++, realtime_server_time() in C and client.serverTime in JavaScript.
It is the value to use whenever clients need to agree on a point in time, such as round starts or ability cooldowns.
It is meaningful only after a successful connect.
The value is a 32-bit millisecond counter that wraps around roughly every 24.8 days. Compare timestamps with wrap-aware arithmetic instead of a plain less-than when sessions can run long.
Fetching a server timestamp (FetchServerTimestamp() in C++, realtime_fetch_server_timestamp() in C, client.fetchServerTimestamp() in JavaScript) is not a getter: it requests a fresh clock synchronization from the server, which is worth doing after a long stall before trusting the server time again.
The statistics snapshot carries the same value as ServerTimeMs in C++ and serverTimeMs in JavaScript.
The master client schedules the round start a few seconds ahead and announces it, so every client counts down against the same server-time deadline:
C++
constexpr uint8_t roundStartEvent = 42;
auto subscription = client.SubscribeEvent([](uint8_t eventCode, int /*senderId*/, std::span<const uint8_t> data) {
if (eventCode != roundStartEvent || data.size() != sizeof(int))
{
return;
}
int startTime = 0;
std::memcpy(&startTime, data.data(), sizeof(startTime));
// Start the round once GetServerTime() reaches startTime.
});
const int startTime = client.GetServerTime() + 5000;
client.SendEvent(roundStartEvent, startTime);
C
#define ROUND_START_EVENT 42
/* Receiving, in the polled batch: */
if (e->type == RealtimeEventType_Event && e->counter == ROUND_START_EVENT && e->blobOffset >= 0 && e->blobLength == (int32_t)sizeof(int32_t))
{
int32_t startTime = 0;
memcpy(&startTime, blob + e->blobOffset, sizeof(startTime));
/* Start the round once realtime_server_time() reaches startTime. */
}
/* Sending, from the master client: */
int32_t startTime = realtime_server_time(client) + 5000;
realtime_send_event(client, ROUND_START_EVENT,
(const uint8_t*)&startTime, (int32_t)sizeof(startTime),
1, 0, 0, NULL, 0, 0, 0, 0);
/* realtime_server_time is the direct counterpart of GetServerTime() and
wraps the same way, and realtime_fetch_server_timestamp requests a fresh
synchronization. The payload travels as raw bytes over the ABI, so both
sides agree on the width by using int32_t explicitly. */
JavaScript
const ROUND_START_EVENT = 42;
client.onEvent(ROUND_START_EVENT, ({ data: startTime }) => {
// The server clock is a wrapping 32-bit counter: compare through `| 0`, not with `<`.
const msLeft = (startTime - client.serverTime) | 0;
console.log(`round starts in ${msLeft} ms`);
});
if (client.room?.isMasterClient) {
client.sendEvent(ROUND_START_EVENT, (client.serverTime + 5000) | 0);
}
Last updated on
Back to top