Overview
The Problem
In the traditional lobby-based matchmaking flow, brainCloud waits for the lobby to satisfy all ready-status rules (i.e., all players have "readied up") before launching the hosted server container. This approach can result in players waiting over 60 seconds after readying up while the server:
Provisions/allocates the container
Pulls the Docker image (if not cached)
Starts the server process
Establishes the S2S connection
The Solution: Pre-Ready Launch
The Pre-Ready Launch feature allows brainCloud to launch the server container before all players have readied up. Specifically, the container can be launched as soon as a configurable number of players have joined the lobby.
This means that while players are still configuring their game settings or waiting for teammates to ready up, the server is already spinning up in the background. By the time everyone is ready, the server is likely already running and waiting for connections - dramatically reducing the perceived wait time.
Benefits:
Significantly reduced "lobby to game" transition time
Better player experience, especially for games with longer server startup times
No changes required to the client-side implementation (other than some changes in terms of launch progress notifications)
How It Works
Configuration
The Pre-Ready Launch feature is configured per lobby type in the brainCloud Design Portal. Two new fields have been added to the lobby type "Rules" configuration:
Field | Type | Description |
| Boolean |
|
| Integer | Number of members that must join the lobby before triggering the server launch (1 to max participants) |
These configuration values are set via the “RULES” tab of the Lobby Type Configuration screen:
Server Lifecycle with Pre-Ready Launch
When Pre-Ready Launch is enabled, the server lifecycle changes as follows:
Lobby Created - Players begin joining the lobby
Member Threshold Met - When preReadyLaunchCount members have joined, brainCloud launches the server container
Server Starts in "Pre-Ready" Mode - The server starts but knows the lobby isn't ready yet
Server Subscribes to Lobby Status - The server connects to RTT and subscribes to lobby status updates
Players Ready Up - Meanwhile, players continue to configure and ready up in the lobby
Lobby Transitions to "Starting" - When all ready conditions are met, the lobby state changes
Server Receives Update - The server receives the lobby status update via RTT
Server Performs Initialization - The server does its normal startup routine and reports back to brainCloud to say it's ready.
Game Begins - Players connect to the already-running server
Environment Variables
When a server is launched in Pre-Ready mode, the following environment variables are provided:
Variable | Type | Description |
| String |
|
| String | Maximum time (in seconds) to wait for the lobby to reach the "starting" state. If exceeded, the server should shut down gracefully. This is set to |
| String | The lobby instance ID (e.g., |
| String | The brainCloud application ID |
| String | The brainCloud S2S API host |
| String | The brainCloud S2S API port |
| String | The server's S2S secret for authentication |
| String | The configured server name |
| String | Unique identifier for this server instance |
Implementation Guide
To support Pre-Ready Launch in your custom game server, follow these steps. These examples assume you are using the brainCloud S2S C++ library.
Step 1: Detect Pre-Ready Launch Mode
On startup, check the PRE_READY_LAUNCH environment variable to determine if the server was launched in pre-ready mode:
bool isPreReadyLaunch = false;
const char* preReadyLaunchEnv = getenv("PRE_READY_LAUNCH");
if (preReadyLaunchEnv != nullptr && strcmp(preReadyLaunchEnv, "true") == 0) {
isPreReadyLaunch = true;
}
If PRE_READY_LAUNCH is "false" or not set, proceed with your normal server initialization flow.
Step 2: Initialize and Authenticate the S2S Context
If your server is in pre-ready launch mode, you need to establish an S2S connection to brainCloud. The brainCloud S2S library handles authentication automatically when you create an S2SContext.
First, retrieve the necessary credentials from environment variables:
#include <brainclouds2s.h>
#include <json/json.h>
using namespace BrainCloud;
// Retrieve credentials from environment variables
std::string appId = getenv("APP_ID");
std::string serverName = getenv("SERVER_NAME");
std::string serverSecret = getenv("SERVER_SECRET");
std::string serverHost = getenv("SERVER_HOST");
std::string serverPort = getenv("SERVER_PORT");
// Build the S2S URL
std::string s2sUrl = "https://" + serverHost + ":" + serverPort + "/s2sdispatcher";
// Create the S2S context - this handles authentication automatically
auto s2s = S2SContext::create(appId, serverName, serverSecret, s2sUrl, true);
s2s->setLogEnabled(true);
The S2SContext::create() method establishes a session with brainCloud and handles authentication internally. Once created, you can make S2S requests using this context.
Step 3: Connect to RTT and Register for Chat and Lobby Events
Use the S2SContext to establish an RTT session.
// Ensure S2S is authenticated (has sessionId) before enabling RTT
// RTT requires a valid sessionId to connect
if (s2s->getSessionId().empty())
{
log_info("RTT: S2S not authenticated yet, authenticating...");
// Authenticate with callback loop pattern (similar to s2sRequestSync)
bool authProcessed = false;
std::string authResult;
s2s->authenticate([&authProcessed, &authResult](const std::string &result)
{
authResult = result;
authProcessed = true;
});
// Wait for auth to complete by checking sessionId
int maxAuthAttempts = 100; // 10 second timeout
int authAttempts = 0;
while (s2s->getSessionId().empty() && authAttempts < maxAuthAttempts)
{
s2s->runCallbacks(100);
authAttempts++;
}
if (s2s->getSessionId().empty())
{
log_warn("RTT: S2S authentication timed out (no sessionId received)");
return 0;
}
log_info("RTT: S2S authenticated successfully [sessionId: {}]", s2s->getSessionId());
}
// Get the RTT service from the S2S context
BrainCloudRTT* pRTTService = s2s->getRTTService();
if (!pRTTService)
{
log_error("RTT: Failed to get RTT service from S2S context");
return 0;
}
// Create callback handlers (they will be owned by the component)
auto pConnectCallback = new RelayServerRTTConnectCallback();
auto pEventCallback = new RelayServerRTTEventCallback();
// Enable RTT synchronously
log_info("RTT: Enabling RTT connection...");
pRTTService->enableRTT(pConnectCallback, true);
// Wait for connection to complete (similar to S2S authentication pattern)
// Add timeout to prevent hanging indefinitely
int maxAttempts = 100; // 10 seconds timeout (100ms * 100)
int attempts = 0;
while (!pConnectCallback->isProcessed() && attempts < maxAttempts)
{
s2s->runCallbacks(100);
attempts++;
// Log progress every 2 seconds
if (attempts % 20 == 0)
{
log_debug("RTT: Still waiting for connection... ({} secs)", attempts / 10);
}
}
if (!pConnectCallback->isProcessed())
{
log_warn("RTT: Connection timeout after {} secs - continuing without RTT", maxAttempts / 10);
delete pConnectCallback;
delete pEventCallback;
return 0;
}
if (!pConnectCallback->isConnected())
{
log_warn("RTT: Failed to connect - {} - continuing without RTT", pConnectCallback->getErrorMessage());
delete pConnectCallback;
delete pEventCallback;
return 0;
}
log_info("RTT: Connection established [ID: {}]", pRTTService->getRTTConnectionId());
// Register event callbacks
log_info("RTT: Registering event callbacks...");
pRTTService->registerRTTCallback(ServiceName::Chat, pEventCallback);
pRTTService->registerRTTCallback(ServiceName::Lobby, pEventCallback);
log_info("RTT: Event callbacks registered");
Step 4: Subscribe to Lobby Status Channel
Subscribe to the lobby status system channel to receive updates when the lobby state changes. The channel ID format is:
<APP_ID>:sy:_lobbystatus_<LOBBY_ID_WITHOUT_APP_PREFIX>
For example, if LOBBY_ID is 23649:CursorPartyV2:160, the channel ID would be:
23649:sy:_lobbystatus_CursorPartyV2:160
Send the subscription request via S2S:
// Get the lobby ID from environment
std::string lobbyId = getenv("LOBBY_ID");
// Extract the lobby ID portion without the app prefix
// lobbyId format: "23649:CursorPartyV2:160" -> need "CursorPartyV2:160"
size_t firstColon = lobbyId.find(':');
std::string lobbyIdWithoutApp = lobbyId.substr(firstColon + 1);
// Build the channel ID
std::string channelId = appId + ":sy:_lobbystatus_" + lobbyIdWithoutApp;
// Build the SYS_CHANNEL_CONNECT request
Json::Value subscribeJson;
subscribeJson["service"] = "chat";
subscribeJson["operation"] = "SYS_CHANNEL_CONNECT";
subscribeJson["data"]["channelId"] = channelId;
subscribeJson["data"]["maxReturn"] = 0;
std::stringstream ss;
ss << subscribeJson;
std::string subscribeRequest = ss.str();
// Make the request
std::string response = s2s->requestSync(subscribeRequest);
NOTE: This example shows a synchronous call however the same can be accomplished via an asynchronous call as well.
Step 5: Handle Lobby Status Updates
Once subscribed, you will receive RTT events when the lobby status changes. The events arrive as chat messages:
void RelayServerRTTEventCallback::rttCallback(const std::string& jsonData)
{
// Parse the JSON data
Json::Value root;
Json::Reader reader;
if (!reader.parse(jsonData, root))
{
log_error("RTT: Failed to parse RTT event JSON: {}", jsonData);
return;
}
// Extract service and operation from the event
std::string service = root.get("service", "").asString();
std::string operation = root.get("operation", "").asString();
const Json::Value& data = root["data"];
// Log the event with compact JSON data
Json::StreamWriterBuilder builder;
builder["indentation"] = "";
std::string dataStr = Json::writeString(builder, data);
log_info("RTT [R] event [service:{} operation:{} data:{}]", service, operation, dataStr);
// Handle chat events
if (service == "chat")
{
if (operation == "INCOMING")
{
// Handle incoming messages on subscribed channels
std::string channelId = data.get("chId", "").asString();
// Check if this is a lobby status channel message
if (channelId.find("_lobbystatus_") != std::string::npos)
{
const Json::Value& content = data["content"];
if (content.isMember("state"))
{
std::string state = content["state"].asString();
log_info("RTT: Lobby status update [state:{}]", state);
// Do something with lobby state and content
updateLobbyState(state, content);
}
else {
log_warn("RTT: Lobby status missing 'state'");
}
}
else
{
log_warn("RTT: Incoming message - Channel: {}", channelId);
}
}
}
else
{
log_warn("RTT: Unhandled event service: {}", service);
}
}
Key fields in content:
Field | Description |
| Lobby state: |
|
|
| Current number of members in the lobby |
| Array of member details |
Step 6: Wait for "Starting" State
Continue processing lobby status updates until you receive one where state is "starting". This indicates that all ready conditions have been met and players are ready to connect.
void updateLobbyState(const LobbyStatus& status, const Json::Value& content) {
if (status.state == "starting") {
// Lobby is ready!
// Do final server initialization.
// Players will begin connecting once we report back with SYS_ROOM_READY.
onLobbyReady(content);
}
}
Step 7: Implement Timeout Handling
If the lobby never reaches the "starting" state (e.g., players abandon the lobby), your server should shut down gracefully after the timeout period:
int timeoutSecs = 120; // Default fallback
const char* timeoutEnv = getenv("PRE_READY_LAUNCH_TIMEOUT_SECS");
if (timeoutEnv != nullptr) {
timeoutSecs = atoi(timeoutEnv);
}
// Start timeout timer when entering pre-ready mode
startTimeoutTimer(timeoutSecs, []() {
log("Pre-ready launch timeout expired. Shutting down.");
shutdownServer();
});
// Cancel timer when lobby reaches "starting" state
void onLobbyReady() {
cancelTimeoutTimer();
// Proceed with normal game flow
}
Complete Flow Diagram
Server Launch (PRE_READY_LAUNCH=true)
│
▼
Create S2SContext (auto-authenticates)
│
▼
Connect to RTT (WebSocket)
│
▼
Register for RTT events
│
▼
Subscribe to lobby system channel
│
▼
Start Timeout Timer
│
├─────────────────────────────┐
│ │
▼ ▼
Receive Lobby Updates Timeout Expires
(via RTT Events) │
│ ▼
│ Shutdown Server
│
▼
state == "starting"?
│
┌────┴────┐
│ No │ Yes
│ │
▼ ▼
Continue Cancel Timer
Waiting Proceed with Game
Sample Log Output
Here's an example of what your server logs might look like with Pre-Ready Launch enabled:
[15:26:11.254] (I) Pre-ready launch mode: Initializing RTT for lobby state updates
[15:26:11.254] (I) Creating S2S context...
[15:26:11.254] (I) S2S: Authenticating with brainCloud...
[15:26:11.545] (I) S2S: Authenticated successfully [sessionId: f8pv5lgp3jpva7vvi70p985inm]
[15:26:11.545] (I) RTT: Requesting system connection...
[15:26:11.678] (I) RTT: Got endpoint info [host: evsweba.prod.braincloudservers.com:443]
[15:26:11.678] (I) RTT: Connecting via WebSocket...
[15:26:12.221] (I) RTT: Successfully connected to RTT service
[15:26:12.221] (I) RTT: Connection established [ID: 23649:s:f8pv5lgp3jpva7vvi70p985inm]
[15:26:12.221] (I) Subscribing to lobby status channel...
[15:26:12.356] (I) Subscribed to lobby status updates [channel: 23649:sy:_lobbystatus_CursorPartyV2:160]
[15:26:45.123] (I) Lobby status update: state=active, members=2, isRoomReady=false
[15:26:58.456] (I) Lobby status update: state=starting, members=4, isRoomReady=true
[15:26:58.456] (I) Lobby ready! Canceling timeout and accepting player connections.
Best Practices
Always implement timeout handling - Players may abandon lobbies, and your server should clean up gracefully.
Log RTT connection status - This helps with debugging connectivity issues.
Handle RTT disconnections - If the RTT connection drops, attempt to reconnect and resubscribe.
Test both modes - Ensure your server works correctly with both
PRE_READY_LAUNCH=trueandPRE_READY_LAUNCH=false.Consider fallback behavior - If RTT connection fails, you may want to fall back to polling the lobby status via S2S API calls (though this is less efficient).

