Skip to content

Bridge Internals

Deep dive into the C++ framework that powers libqsbridge.so — the shared library injected into game server processes.


Overview

The bridge is a C++17 shared library (8.6 MB) loaded via LD_PRELOAD into game server processes. It contains 225+ files and 17,200+ lines of code organised into 5 build layers.

graph TD
    LIB[libqsbridge.so]
    LIB --> L1[Layer 1 — bsatn_core<br/>BSATN binary codec, 18 headers]
    LIB --> L2[Layer 2 — stdb_client<br/>SpacetimeDB WebSocket client]
    LIB --> L3[Layer 3 — bridge_core<br/>RPC dispatch, message queue, class registry]
    LIB --> L4[Layer 4 — engine_ue4<br/>UE4 4.27 engine adapter]
    LIB --> L5[Layer 5 — game_humanitz<br/>HumanitZ game module hooks]

CMake Build System

# 5-layer build → single shared library
bsatn_core       STATIC library (codec only, no dependencies)
stdb_client       STATIC library (depends on bsatn_core + libwebsockets)
bridge_core       STATIC library (depends on stdb_client)
engine_ue4        STATIC library (depends on bridge_core)
game_humanitz     STATIC library (depends on engine_ue4)

libqsbridge.so   SHARED library (--whole-archive all 5 layers)

Core Components

Engine Adapter (engine_adapter.h, 164 lines)

Abstract interface that all engine implementations must satisfy:

class EngineAdapter {
public:
    virtual ~EngineAdapter() = default;

    // Lifecycle
    virtual bool init() = 0;
    virtual void shutdown() = 0;
    virtual void tick(float delta_time) = 0;

    // Identity
    virtual std::string get_engine_name() const = 0;
    virtual std::string get_engine_version() const = 0;

    // Hook management
    virtual bool hook_function(const char* name, void* callback) = 0;
    virtual bool unhook_function(const char* name) = 0;

    // Player tracking
    virtual std::vector<PlayerInfo> get_connected_players() const = 0;
    virtual std::string extract_player_id(void* player_controller) const = 0;
};

STDB Connection (stdb_connection.h, 242 lines)

WebSocket client using libwebsockets with BSATN binary framing:

  • Auto-reconnect with exponential backoff
  • SpacetimeDB v2 binary protocol (v2.bsatn.spacetimedb)
  • Subscribe, CallReducer, ReducerResult, TransactionUpdate handling
  • Thread-safe outbound message queue

RPC Dispatcher (rpc_dispatcher.h)

Routes engine RPC calls to registered handler functions:

class RpcDispatcher {
    std::unordered_map<std::string, RpcHandler> dispatch_table_;
public:
    void register_handler(const char* rpc_name, RpcHandler handler);
    bool dispatch(const char* rpc_name, void* params, void* player);
};

Game Interface (game_interface.h, 142 lines)

Base class for game modules:

class GameInterface {
public:
    virtual const char* module_name() = 0;
    virtual void on_init(StdbConnection* conn) = 0;
    virtual void on_tick(float delta_time) = 0;
    virtual void on_player_join(const char* player_id) = 0;
    virtual void on_player_leave(const char* player_id) = 0;
    virtual void register_hooks(EngineAdapter* adapter) = 0;
};

Message Queue (message_queue.h)

Thread-safe MPSC (Multiple Producer, Single Consumer) queue for cross-thread communication between worker threads and the main bridge thread.

Class Registry (class_registry.h)

Tracks player controllers, actors, and their SpacetimeDB identities for the game module.

Game Action (game_action.h)

Async action queue with 5 variant types for deferred game state mutations.

File Inventory

Directory Files Purpose
include/qsbridge/ 11 Public headers (framework API)
include/qsbridge/bsatn/ 18 BSATN codec (header-only)
include/qsbridge/engines/ 4 Engine-specific headers
lib/core/ 6 Engine-agnostic sources
lib/engines/ue4/ 4 UE4 adapter sources
games/humanitz/include/ 2 Game module headers
games/humanitz/src/ 7 Game module sources
games/humanitz/schema/ 218 Codegen'd BSATN types
tests/ 7 Test suites (860+ assertions)
tools/ 1 codegen.py

Entry Point

The bridge loads via LD_PRELOAD using GCC's constructor attribute:

// lib/engines/ue4/entry.cpp
__attribute__((constructor))
void qs_bridge_init() {
    // 1. Detect game engine
    // 2. Create engine adapter
    // 3. Create STDB connection
    // 4. Create game module
    // 5. Register hooks
    // 6. Start worker threads
    // 7. Connect to SpacetimeDB
}