Skip to main content

Custom Game Server: Pre-Ready Launch Support

This document describes the Pre-Ready Launch feature introduced in brainCloud 5.9 and explains how custom game server developers can add support for this feature to their server implementations.

Jason Liang avatar
Written by Jason Liang
Updated yesterday

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:

  1. Provisions/allocates the container

  2. Pulls the Docker image (if not cached)

  3. Starts the server process

  4. 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

preReadyLaunchEnabled

Boolean

true to enable pre-ready launch, false (default) to use traditional launch behavior

preReadyLaunchCount

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:

  1. Lobby Created - Players begin joining the lobby

  2. Member Threshold Met - When preReadyLaunchCount members have joined, brainCloud launches the server container

  3. Server Starts in "Pre-Ready" Mode - The server starts but knows the lobby isn't ready yet

  4. Server Subscribes to Lobby Status - The server connects to RTT and subscribes to lobby status updates

  5. Players Ready Up - Meanwhile, players continue to configure and ready up in the lobby

  6. Lobby Transitions to "Starting" - When all ready conditions are met, the lobby state changes

  7. Server Receives Update - The server receives the lobby status update via RTT

  8. Server Performs Initialization - The server does its normal startup routine and reports back to brainCloud to say it's ready.

  9. 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

PRE_READY_LAUNCH

String

"true" if launched in pre-ready mode, "false" otherwise

PRE_READY_LAUNCH_TIMEOUT_SECS

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 tooLateSecs + 5 from the lobby rules.

LOBBY_ID

String

The lobby instance ID (e.g., 23649:CursorPartyV2:160)

APP_ID

String

The brainCloud application ID

SERVER_HOST

String

The brainCloud S2S API host

SERVER_PORT

String

The brainCloud S2S API port

SERVER_SECRET

String

The server's S2S secret for authentication

SERVER_NAME

String

The configured server name

SERVER_ID

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

state

Lobby state: "setup", "active", "starting", "early", "pending", etc.

isRoomReady

true when the room/server is ready for players

numMembers

Current number of members in the lobby

members

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=true and PRE_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).

Did this answer your question?