Skip to content

PlayerServerPresence

The PlayerServerPresence table tracks which server each player is currently connected to across the entire QS-Bridge cluster. It serves as the single source of truth for player location, enabling dual-login prevention, population counting, and cross-server coordination.


Scope

🌍 Global — All servers and the admin panel can observe player presence across the cluster. This global visibility is essential for preventing a player from being connected to multiple servers simultaneously.


Schema

Column Type Constraints Description
player_id String Primary Key Platform-wide player identifier
server_id String Indexed The server_id of the server the player is connected to
connected_at Timestamp When the player connected to the current server
last_heartbeat Timestamp Most recent activity timestamp for staleness detection

Rust Definition

#[spacetimedb::table(public, name = player_server_presence)]
pub struct PlayerServerPresence {
    #[primary_key]
    pub player_id: String,
    pub server_id: String,
    #[index(btree)]
    pub connected_at: Timestamp,
    pub last_heartbeat: Timestamp,
}

Usage Patterns

Dual-Login Prevention

The primary purpose of this table is to prevent a player from connecting to two servers at the same time. Because player_id is the primary key, each player can have at most one row in the table.

When a player connects to a server, the game hook checks for an existing PlayerServerPresence row:

// Pseudocode — game hook connection flow
fn on_player_connect(player_id: &str, this_server_id: &str) {
    if let Some(existing) = PlayerServerPresence::filter_by_player_id(player_id) {
        if existing.server_id != this_server_id {
            // Player is connected elsewhere — reject or handle transfer
            reject_connection("Already connected to another server");
            return;
        }
    }

    // Register presence
    PlayerServerPresence::insert(PlayerServerPresence {
        player_id: player_id.to_string(),
        server_id: this_server_id.to_string(),
        connected_at: Timestamp::now(),
        last_heartbeat: Timestamp::now(),
    });
}

Player Disconnect

When a player disconnects, the game hook deletes their PlayerServerPresence row:

fn on_player_disconnect(player_id: &str) {
    PlayerServerPresence::delete_by_player_id(player_id);
}

Staleness Detection

If a server crashes without gracefully disconnecting its players, their PlayerServerPresence rows will remain — creating "ghost" entries that block the players from connecting elsewhere. The last_heartbeat column enables the gateway or a cleanup process to identify and remove stale entries:

-- Find presence entries with no heartbeat in the last 2 minutes
SELECT * FROM player_server_presence
WHERE last_heartbeat < NOW() - INTERVAL '2 minutes'

Population by Server

The server_id index allows efficient queries to count players per server:

-- Count players on each server
SELECT server_id, COUNT(*) as player_count
FROM player_server_presence
GROUP BY server_id

This data is used by the admin panel's population dashboard and can feed into matchmaking or load-balancing logic.

Transfer Coordination

During a server transfer (see ServerTransferQueue), the player's PlayerServerPresence row is updated to reflect the destination server once the transfer completes. The presence row acts as a lock — preventing the player from connecting to a third server during an in-flight transfer.


Player presence is typically managed by game hook logic rather than dedicated platform reducers. The server_heartbeat reducer may update aggregate counts in ServerRegistry based on presence data.

Reducer Relationship
server_heartbeat Reports online_count derived from presence data

Subscriber Query Purpose
Admin Panel SELECT * FROM player_server_presence Player location tracking, population dashboard
Gateway SELECT * FROM player_server_presence Dual-login detection, staleness cleanup
Game Hook SELECT * FROM player_server_presence WHERE server_id = '{self}' Local player roster (optional)

For full subscription architecture, see Subscriptions.