Subscriptions & Real-Time Data¶
How game hooks and the admin panel receive live data from SpacetimeDB.
Overview¶
SpacetimeDB uses SQL subscriptions over WebSocket connections. Clients declare queries; the database pushes incremental changes (onInsert, onUpdate, onDelete callbacks) whenever matching rows change. There is no polling.
This is the key architectural advantage: SpacetimeDB subscriptions scale as O(changed_rows × log(subscriptions)), not O(total_rows × total_clients). This enables real-time dashboards with thousands of rows and dozens of connected panel sessions without performance degradation.
Subscription Architecture¶
graph TD
subgraph STDB["SpacetimeDB Instance"]
subgraph Tables["Platform Tables (12)"]
SR["ServerRegistry (all servers)"]
BE["BanEntry (all bans)"]
AR["AdminRole (all admins)"]
SC["ServerConfig (per server_id)"]
PSP["PlayerServerPresence (all)"]
PS["PanelSession (all)"]
PAL["PanelAuditLog (all)"]
end
end
Tables --> HookA
Tables --> Panel
Tables --> HookB
subgraph HookA["Game Hook A (Bridge)"]
A1["ServerConfig WHERE s_id=A"]
A2["BanEntry (full table)"]
A3["AdminRole (full table)"]
A4["WhitelistEntry (full table)"]
end
subgraph Panel["React Panel"]
P1["ServerRegistry (full table)"]
P2["BanEntry"]
P3["AdminRole"]
P4["WhitelistEntry"]
P5["PanelAuditLog"]
P6["PanelSession"]
P7["PlayerPresence"]
end
subgraph HookB["Game Hook B (Bridge)"]
B1["ServerConfig WHERE s_id=B"]
B2["BanEntry (full table)"]
B3["AdminRole (full table)"]
B4["WhitelistEntry (full table)"]
end
Game Hook Subscriptions¶
Each libqsbridge.so instance subscribes to platform tables on startup. These subscriptions inform the bridge about bans, config changes, and admin actions.
| Table | Query | Purpose |
|---|---|---|
ServerConfig |
WHERE server_id = '{self.server_id}' |
Local server configuration — MOTD, max players, whitelist status |
BanEntry |
Full table (no filter) | Cluster-wide bans — bridge checks on player connect |
AdminRole |
Full table (no filter) | Admin permissions — bridge enforces admin commands |
WhitelistEntry |
Full table (no filter) | Whitelist — bridge enforces on player connect |
Lifecycle hooks:
- On init_module: register in ServerRegistry, start 30-second heartbeat timer
- On on_connect: upsert PlayerServerPresence, check BanEntry, check dual-login
- On on_disconnect: remove PlayerServerPresence, update ServerRegistry.online_count
Game module tables are subscribed separately in the game module's own sub-database. The platform bridge only manages platform subscriptions.
Panel Subscriptions¶
The React panel connects to SpacetimeDB over WebSocket for read-only live data. All admin mutations go through the API Gateway, never directly from the browser.
| Table | Query | Panel Usage |
|---|---|---|
ServerRegistry |
Full table | Cluster dashboard — server grid with status, player counts, regions |
BanEntry |
Full table | Players page — ban status indicators, moderation actions |
WhitelistEntry |
Full table | Players page — whitelist status |
AdminRole |
Full table | Users page — role management, permission display |
PanelAuditLog |
Full table | Audit Log page — chronological admin action timeline |
PanelSession |
Full table | Sidebar — active session count, connection status |
PlayerServerPresence |
Full table | Player presence across cluster |
React Implementation¶
The panel uses the useTable<T> hook to wrap SpacetimeDB subscription callbacks into React state:
// hooks/use-table.ts
function useTable<T>(tableName: string): T[] {
const [rows, setRows] = useState<T[]>([]);
useEffect(() => {
const table = connection.db.getTable(tableName);
// Initial rows from SubscribeApplied
setRows([...table]);
// Incremental updates
table.onInsert((row) => setRows(prev => [...prev, row]));
table.onUpdate((oldRow, newRow) =>
setRows(prev => prev.map(r => r === oldRow ? newRow : r)));
table.onDelete((row) =>
setRows(prev => prev.filter(r => r !== row)));
}, [tableName]);
return rows;
}
Subscription Lifecycle¶
1. Browser opens WebSocket to SpacetimeDB
2. Client sends Subscribe message with SQL queries
3. STDB responds with SubscribeApplied + initial rows (BSATN binary)
4. Client populates local state from initial rows
5. On any table mutation, STDB pushes TransactionUpdate
with only the changed rows (inserts + deletes)
6. Client applies incremental updates to local state
7. React re-renders affected components
No polling. The panel holds no server state itself — SpacetimeDB subscriptions act as a real-time client-side cache. After the initial page load, only incremental callbacks arrive.
Caching Architecture¶
The panel uses React Query alongside STDB subscriptions for optimal performance:
| Layer | Technology | Role |
|---|---|---|
| STDB Subscriptions | SpacetimeDB WebSocket | Real-time table data (bans, admins, audit log) |
| React Query | useClusterState() hook |
Cached cluster state with 10s stale time, 30s polling |
| Component State | React useState |
UI state (selected tabs, expanded rows, filters) |
This three-layer approach ensures: - Live data arrives instantly via subscriptions - Expensive queries are cached and shared across components - UI interactions remain responsive regardless of data volume
Related Pages¶
- Platform Overview — table classification
- Real-Time Panel Subscriptions — panel-specific subscription details
- Multi-Server Clusters — how subscriptions work across servers