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++
C
JavaScript

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++
C
JavaScript

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