Skip to main content

Lobby server

This page documents the purpose and design of a lobby server in the Celte architecture, explains how the provided Go example implements a lobby, and describes how to write a generic lobby that interacts with the Master API.

High-level purpose

  • The lobby is the public-facing HTTP service for your game. Clients talk to the lobby to join the game, authenticate, and request to be placed in a running game instance.
  • The lobby is responsible for session lifecycle (creating/cleaning sessions on the master), matchmaking/spawner selection, and requesting the master to create or link clients to server nodes.
  • The master manages cluster-level primitives (namespaces, nodes, redis keys, process orchestration) and exposes small JSON endpoints; the lobby implements game-specific policies and calls the master when cluster-level actions are required.

Why have a separate lobby?

  • Separation of concerns: the lobby contains game-specific logic (auth, matchmaking rules, player inventories, persistence), while the master handles cluster primitives and system-level orchestration.
  • Security: the lobby can validate and authenticate clients before allowing them to interact with the master or cluster.
  • Scalability: you can scale lobbies independently from master nodes and server nodes, and run multiple lobby instances behind a load balancer.

What the example Go lobby does (summary)

  • Listens on port 5002 and exposes two client endpoints:
    • POST /client/connect — client asks to connect to the cluster; returns cluster host/port and a SessionId.
    • POST /client/link — client asks the lobby to link it to a server node (the lobby chooses a spawner and calls the Master API to link the client to the node responsible for that spawner).
  • Reads configuration from a YAML file (~/.celte.yaml by default) and flattens keys into a Config struct (keys used: CELTE_MASTER_HOST, CELTE_MASTER_PORT, CELTE_PULSAR_HOST, CELTE_PULSAR_PORT).
  • Generates a random SessionId per lobby run and calls the Master API to create a session and create initial root nodes (SetupGameSession).
  • Uses helper functions that call the master endpoints:
    • server/create to create nodes (used during session initialization)
    • server/create_session to create the Pulsar namespace
    • client/link to tell the master to connect a client to the appropriate node
    • redis/clear and server/cleanup_session on shutdown

Files to inspect in the example

  • main.go — startup, config loading, session initialization and graceful shutdown.
  • config.go — YAML parsing and Config struct (generates a random SessionId).
  • router.go — HTTP routes registration using Gorilla Mux.
  • handlers.go — request parsing and the two client endpoints (connect, link).
  • celteMasterAPI.go — small helpers that issue HTTP POSTs to the Master API (server/create, client/link, server/create_session, redis/clear, server/cleanup_session).
  • gameSession.go — builds an initial set of nodes by calling the Master API.

API contract (example lobby endpoints)

  1. POST /client/connect

Purpose: initial handshake for a client. The lobby can authenticate the client, then respond with cluster connection information.

Request body (JSON)

{ "clientId": "<client runtime id>" }

Response (200)

{
"clusterHost": "<pulsar or cluster host>",
"clusterPort": "<pulsar or cluster port>",
"SessionId": "<session id generated by lobby>"
}

Notes: the example lobby returns CELTE_PULSAR_HOST and port values read from the YAML config so clients know how to reach the messaging layer.

  1. POST /client/link

Purpose: request the lobby to assign the client to a game node (spawner). The lobby chooses the spawner following its configured policy and calls the master (client/link) to actually connect the client to the node.

Request body (JSON)

{ "clientId": "<client runtime id>" }

Response (200)

{ "status": "ok", "message": "Client <id> linked to node <spawner>" }

Notes: the example uses a simple round-robin over availableSpawners := ["DefaultSpawner","Spawner1","Spawner2"]. Replace this with a real strategy (least-loaded, region-aware, user affinities, party placement) as needed.

How the lobby interacts with the master

  • Lifecycle actions (done once per lobby run or per game session):
    • Call POST /server/create_session to create a Pulsar namespace for the session. This is implemented in CelteMasterAPICreateSession.
    • Create root server nodes for the session by calling POST /server/create with parentId entries (see SetupGameSession in gameSession.go). Each server/create call includes SessionId added by the helper.
  • Client linking:
    • When a client wants to play the lobby chooses a spawnerId and calls the master POST /client/link (via CelteMasterAPILinkClient). The master then routes the client to the active node responsible for that spawner (or fails if the lookup fails).
  • Teardown:
    • On shutdown the lobby calls POST /redis/clear to remove session keys and POST /server/cleanup_session to cleanup processes and delete the Pulsar namespace.

Designing a generic lobby (guidelines)

  1. Keep game logic in the lobby, orchestration in the master
  • The lobby should implement authentication, matchmaking, party handling, and policy decisions. The lobby should not embed cluster orchestration logic — call the master for creating nodes, namespaces and making cross-process connections.
  1. Stateliness and sessionId
  • The example generates a new SessionId per lobby process run. You can change this to maintain long-lived sessions (persistent world) or generate per-match session ids for ephemeral matches. Put SessionId in the payloads when calling the master so all cluster state is properly namespaced.
  1. Spawner selection strategies
  • Round-robin: simple, demonstrated in the example.
  • Least-loaded: query a telemetry endpoint or keep in-memory counters of players per spawner.
  • Affinity and parties: keep party members together by storing party/spawner mappings.
  • Region-aware: pick a spawner close to the player's region.
  1. Fault handling and retries
  • Master calls can fail; wrap them with retries and circuit-breaker logic. The example prints responses and proceeds; production requires robust error handling.
  1. Health checks and liveness
  • Provide health endpoints for load balancers. Monitor master responses and degrade gracefully when master is unreachable.
  1. Security
  • Authenticate clients in the lobby (tokens, OAuth, session cookies). Only allow trusted lobbies or admin tooling to call master-level APIs if applicable.
  1. Observability and metrics
  • Export metrics (requests, latencies, errors) and logs so you can observe matchmaking and master interaction success rates.

Implementation checklist (for a new lobby)

  • Read YAML config for master host/port and messaging hosts (or use environment variables).
  • Implement /client/connect to authenticate and return cluster connection info + SessionId.
  • Decide and implement a spawner selection policy for /client/link.
  • Implement master helpers (create_session, create node, link client, clear redis, cleanup_session).
  • Call create_session and create initial nodes when starting a match or lobby run.
  • Add graceful shutdown logic to call redis/clear and cleanup_session to avoid stale resources.
  • Add retries and error handling around master calls.
  • Add health checks, metrics, and logging.

Example: mapping the example code to these responsibilities

  • main.go — startup, config loading, call to SetupGameSession, graceful shutdown that calls CelteMasterAPIClearRedis and CelteMasterAPICleanupSession.
  • config.go — flattens YAML to a Config struct with CelteMasterHost, CelteMasterPort, CeltePulsarHost, CeltePulsarPort and generates a random SessionId.
  • gameSession.go — calls CelteMasterAPICreateSession and then CelteMasterAPICreateServer for a set of root nodes.
  • handlers.goconnectClientToCelteCluster returns cluster host/port and session id; linkClientToNode selects a spawner and calls CelteMasterAPILinkClient.
  • celteMasterAPI.go — shows small helpers that perform POST requests to the master API endpoints with SessionId added to the JSON body.

Further suggestions

  • Replace the static availableSpawners with a dynamic registry (in-memory or persistent) that can be updated as game features change.
  • Add authentication hooks in connectClientToCelteCluster and an ACL for master-level operations.
  • Implement a small integration test that starts the master (or a mock) and the lobby, then runs through connect->link flows.