MudEngine Part 6: Multiplayer
Server Game State
The server needs to keep track of all connected players. We use global static state behind #[cfg(feature = "server")]. This module is only compiled into the server binary — the WASM client never sees it.
The state has three parts:
PLAYERS— aMutex<HashMap<String, PlayerInfo>>mapping player IDs to their infoBROADCAST— atokio::sync::broadcast::Senderthat fans out every message to all connected clientsEXITS— the room exit table for movement validation (same grid as Part 4)
Add this module after the room data statics:
// ── Server-only state ── #[cfg(feature = "server")] mod srv { use std::collections::HashMap; use std::sync::{LazyLock, Mutex}; use tokio::sync::broadcast; use super::*; /// All connected players, keyed by their unique ID. pub static PLAYERS: LazyLock<Mutex<HashMap<String, PlayerInfo>>> = LazyLock::new(|| Mutex::new(HashMap::new())); /// Broadcast channel — every connected WebSocket subscribes. /// When a player moves, anyone sends, everyone receives. pub static BROADCAST: LazyLock<broadcast::Sender<ServerMessage>> = LazyLock::new(|| { let (tx, _) = broadcast::channel(64); tx }); /// Exit table: for each room index, a list of (direction, destination) pairs. /// This mirrors the 3×3 grid from Part 4: /// 0 1 2 /// 3 4 5 /// 6 7 8 pub const EXITS: &[&[(&str, usize)]] = &[ &[("south", 3), ("east", 1)], // 0 Forest Path &[("south", 4), ("west", 0), ("east", 2)], // 1 Hilltop &[("south", 5), ("west", 1)], // 2 Abandoned Tower &[("north", 0), ("south", 6), ("east", 4)], // 3 Dark Forest &[("north", 1), ("south", 7), ("west", 3), ("east", 5)], // 4 Town Square &[("north", 2), ("south", 8), ("west", 4)], // 5 Temple Courtyard &[("north", 3), ("east", 7)], // 6 Riverbank &[("north", 4), ("west", 6), ("east", 8)], // 7 Old Bridge &[("north", 5), ("west", 7)], // 8 Graveyard ]; }
Mutex::new() is not a const function, so we cannot write static PLAYERS: Mutex<...> = Mutex::new(HashMap::new()). LazyLock initializes the value on first access — the closure runs once and the result lives for the program's lifetime.
broadcast::channel(64) creates a sender/receiver pair with a buffer of 64 messages. If a client is too slow to keep up, they miss messages (the receiver gets a Lagged error). For our 9-room MUD this is more than enough.
Col 0 Col 1 Col 2
┌──────────┬──────────┬──────────┐
│ Forest │ Hilltop │ Abandoned│ Row 0
│ Path (0) │ (1) │ Tower(2) │
├──────────┼──────────┼──────────┤
│ Dark │ Town │ Temple │ Row 1
│ Forest(3)│ Square(4)│ Crt. (5) │
├──────────┼──────────┼──────────┤
│ River- │ Old │ Grave- │ Row 2
│ bank (6) │ Bridge(7)│ yard (8) │
└──────────┴──────────┴──────────┘
Room index = row × 3 + col. All new players start at room 4 (Town Square, the center). The exit table on the server enforces that movement only works along actual connections — no walking through walls.