Skip to content

Game Module SDK

The qs-bridge-module-sdk Rust crate provides compile-time guarantees, typed registration, and IDE support for building game modules.


Overview

The Game Module SDK is a Rust crate that game module authors depend on. Rather than relying on naming conventions, the SDK uses Rust traits to enforce the interface contract at compile time — preventing misconfiguration and eliminating an entire class of integration bugs.

# Cargo.toml for a new game module
[dependencies]
spacetimedb = "1.1"
qs-bridge-module-sdk = { path = "../qs-bridge-module-sdk" }

Crate Structure

qs-bridge-module-sdk/
├── Cargo.toml              # depends on spacetimedb = "1.1"
├── src/
│   ├── lib.rs              # re-exports
│   ├── traits.rs           # GameModule trait + lifecycle callbacks
│   ├── platform_types.rs   # Read-only views of platform table schemas
│   ├── registration.rs     # #[qs_game_module] proc macro
│   └── bridge.rs           # Cross-database communication helpers

Core Trait — GameModule

Every game module must implement this trait exactly once. The #[qs_game_module] macro generates the SpacetimeDB reducer stubs wired to the trait implementation.

pub trait GameModule: Send + Sync + 'static {
    /// Module identity — used in ServerRegistry.module_name
    const MODULE_NAME: &'static str;

    /// Called when a player joins this server.
    /// Platform has already validated identity, checked bans/whitelist.
    fn on_player_join(ctx: &ReducerContext, player_id: &str, server_id: &str);

    /// Called when a player disconnects (graceful or timeout).
    fn on_player_leave(ctx: &ReducerContext, player_id: &str);

    /// Called every 30s by the bridge heartbeat.
    /// Use for periodic cleanup, decay timers, scheduled spawns.
    fn on_heartbeat(ctx: &ReducerContext, server_id: &str);

    /// Called when the platform bans a player who is currently in-game.
    /// Module should force-disconnect and clean up game state.
    fn on_player_banned(ctx: &ReducerContext, player_id: &str, reason: &str) {}

    /// Called when a vault deposit arrives for this server.
    /// Module should materialise items into the player's inventory.
    fn on_vault_deposit(ctx: &ReducerContext, player_id: &str, item_data: &[u8]) {}

    /// Called when a character transfer arrives.
    /// Module should create/restore the character from the serialised blob.
    fn on_transfer_arrive(ctx: &ReducerContext, player_id: &str, char_data: &[u8]) {}
}

Callback Summary

Callback Required Trigger Typical Use
on_player_join Platform validates player Spawn character, load inventory
on_player_leave Disconnect (graceful/timeout) Save state, despawn character
on_heartbeat Every 30s from bridge Decay timers, zombie spawns, cleanup
on_player_banned Optional Mid-game ban Force disconnect, cleanup
on_vault_deposit Optional Cross-server item transfer Materialise items in inventory
on_transfer_arrive Optional Character transfer Restore character from blob

Platform Types

The SDK provides read-only Rust types matching platform table schemas, so game modules can deserialise platform data without copy-pasting structs:

// platform_types.rs — read-only views

pub struct PlayerPresence {
    pub player_id: String,
    pub server_id: String,
    pub connected_at: Timestamp,
}

pub enum BanState {
    Active,
    Expired,
    Appealed,
}

pub enum AdminLevel {
    SuperAdmin,    // level 0 — full access
    Admin,         // level 1 — moderate, configure
    Moderator,     // level 2 — moderate only
}

Proc Macro — #[qs_game_module]

The #[qs_game_module] attribute macro generates SpacetimeDB reducer stubs wired to the trait implementation:

use qs_bridge_module_sdk::prelude::*;

#[qs_game_module]
struct MyGameModule;

impl GameModule for MyGameModule {
    const MODULE_NAME: &'static str = "my-game";

    fn on_player_join(ctx: &ReducerContext, player_id: &str, server_id: &str) {
        // Create player row, spawn character, load inventory
    }

    fn on_player_leave(ctx: &ReducerContext, player_id: &str) {
        // Save state, despawn, update presence
    }

    fn on_heartbeat(ctx: &ReducerContext, server_id: &str) {
        // Periodic cleanup, spawn management
    }
}

Generated code (by the proc macro):

// Auto-generated — do not edit
#[spacetimedb::reducer]
fn __qs_on_player_join(ctx: &ReducerContext, player_id: String, server_id: String) {
    MyGameModule::on_player_join(ctx, &player_id, &server_id);
}

#[spacetimedb::reducer]
fn __qs_on_player_leave(ctx: &ReducerContext, player_id: String) {
    MyGameModule::on_player_leave(ctx, &player_id);
}

#[spacetimedb::reducer]
fn __qs_on_heartbeat(ctx: &ReducerContext, server_id: String) {
    MyGameModule::on_heartbeat(ctx, &server_id);
}
// ... and so on for each callback

Bridge Helpers

The bridge module provides helpers for game modules to communicate back to the platform:

use qs_bridge_module_sdk::bridge;

// Update player count in ServerRegistry
bridge::report_online_count(ctx, server_id, count);

// Update server status (Starting/Running/Stopping/Offline)
bridge::report_server_status(ctx, server_id, ServerStatus::Running);

Data Flow

graph LR
    subgraph Platform["Platform Module"]
        SR["ServerRegistry"]
        PP["PlayerPresence"]
        BE["BanEntry"]
        GV["GlobalVault"]
        TQ["TransferQueue"]
    end

    subgraph Game["Game Module"]
        HB["on_heartbeat()"]
        PJ["on_player_join()"]
        PB["on_player_banned()"]
        VD["on_vault_deposit()"]
        TA["on_transfer_arrive()"]
        RC["(reducer calls)"]
    end

    SR -- "heartbeat" --> HB
    PP -- "join/leave" --> PJ
    BE -- "ban sync" --> PB
    GV -- "deposit" --> VD
    TQ -- "transfer" --> TA
    RC -- "status" --> Platform

What the SDK Provides

  • platform_types.rs — typed views of the 12 platform tables
  • #[qs_game_module] proc macro — generates reducer stubs wired to trait
  • bridge::report_online_count() — update ServerRegistry from game module
  • bridge::report_server_status() — set server status from game module

What the SDK Does NOT Provide

  • ❌ Game tables, game reducers, game logic — entirely the module author's domain
  • ❌ Panel UI extensions — game-specific pages are a separate concern (via PanelExtension mods)
  • ❌ C++ bridge hooks — engine-specific, handled by libqsbridge.so