158 lines
5.5 KiB
Markdown
158 lines
5.5 KiB
Markdown
# WebSocket Networking: First Iteration
|
|
|
|
## Scope
|
|
|
|
The first networking iteration adds a Windows-only WebSocket transport using
|
|
Boost.Asio and Boost.Beast.
|
|
|
|
The goal of this iteration is connection and replication stability. It does not
|
|
yet implement multiplayer commands, authentication, per-connection players,
|
|
state deltas, or presentation-event replication.
|
|
|
|
## Runtime Architecture
|
|
|
|
### Server
|
|
|
|
`WebsocketGameServer` wraps the headless `InternalGameServer`.
|
|
|
|
- It accepts any WebSocket connection.
|
|
- It sends the current complete game state immediately after connection.
|
|
- It broadcasts the current complete state every five seconds.
|
|
- Incoming WebSocket messages are read and discarded.
|
|
- The authoritative simulation remains on the main server thread.
|
|
- Network sessions only receive serialized state and never access
|
|
`InternalGameServer` directly.
|
|
|
|
The standalone server creates the existing fixed player ID `"player"` and does
|
|
not enable the battle-zone test bot.
|
|
|
|
### Client
|
|
|
|
`WebsocketGameClient` implements `IGameServer`.
|
|
|
|
- It connects asynchronously to `ws://127.0.0.1:8080/`.
|
|
- Received messages are queued by the network thread and applied on the game
|
|
thread from `update()`.
|
|
- It maintains an internal `InternalGameServer` replica so existing snapshot
|
|
consumers and transitional runtime-state bindings continue to work.
|
|
- Camera orientation lives in host-owned `LocationLocalState`, so incoming
|
|
authoritative snapshots cannot overwrite camera movement.
|
|
- `submit()` intentionally discards all commands.
|
|
- `drainEvents()` returns no events.
|
|
- Client-side save and load calls are ignored because persistence remains
|
|
server-side.
|
|
|
|
`RemoteGameServerHost` prepares local presentation resources and runtime
|
|
locations against the replicated state. It does not advance authoritative
|
|
simulation or write Lua/dialogue auxiliary state back to the server.
|
|
|
|
## Campaign Routing
|
|
|
|
The graphical game uses stable router objects for `IGameServer` and
|
|
`IGameServerHost`, allowing existing UI and runtime references to remain valid.
|
|
|
|
When `NETWORK` is defined:
|
|
|
|
- `battle_zone_multiplayer` uses `WebsocketGameClient` and `RemoteGameServerHost`.
|
|
- `main`, `battle_zone`, and every other campaign use `InternalGameServer` and
|
|
`LocalGameServerHost`.
|
|
|
|
The WebSocket client is created only when `battle_zone_multiplayer` is selected
|
|
and is destroyed when that campaign is unloaded. The single-player and
|
|
multiplayer Battle Zone entries share location content and the start-state path,
|
|
so the state can be regenerated through the single-player entry. At startup, the
|
|
standalone server loads the authoritative half of that shared save and rewrites
|
|
its campaign ID to `battle_zone_multiplayer`; each remote client restores only
|
|
the local presentation half.
|
|
|
|
If DNS resolution, TCP connection, the WebSocket handshake, or the active socket
|
|
fails, the client unloads the remote campaign and returns to the campaign
|
|
selection screen.
|
|
|
|
## JSON Protocol
|
|
|
|
Messages use a versioned JSON envelope:
|
|
|
|
```json
|
|
{
|
|
"protocolVersion": 1,
|
|
"type": "state",
|
|
"payload": {
|
|
"schemaVersion": 2
|
|
}
|
|
}
|
|
```
|
|
|
|
The payload uses the authoritative `GameState::save()` representation and excludes
|
|
the client-local location map, including camera orientation.
|
|
The client applies it through `InternalGameServer::load()`.
|
|
|
|
Malformed JSON, unsupported protocol versions, unsupported message types, and
|
|
invalid state payloads are logged and ignored.
|
|
|
|
Single-player persistence uses a separate envelope that combines this authoritative
|
|
document with client-local location state. The temporary full-state network format
|
|
is still expected to be replaced by a dedicated replication schema or deltas as
|
|
command handling and production replication are implemented.
|
|
|
|
## Build Configuration
|
|
|
|
Networking is enabled only for Windows:
|
|
|
|
- `proj-windows/CMakeLists.txt` defines `NETWORK`, compiles the WebSocket client
|
|
and remote host, and links `ws2_32` and `mswsock`.
|
|
- `server/CMakeLists.txt` defines `NETWORK` for the Windows `GameServer`, compiles
|
|
the WebSocket server, and links the Windows socket libraries.
|
|
- Linux, Android, and Web builds do not define `NETWORK` and do not compile the
|
|
network transport.
|
|
|
|
The previous Web-build `NETWORK` definition was removed.
|
|
|
|
## Running
|
|
|
|
Build the server:
|
|
|
|
```powershell
|
|
cmake -S server -B server/build
|
|
cmake --build server/build --config Debug --target GameServer
|
|
```
|
|
|
|
Run it from the repository root:
|
|
|
|
```powershell
|
|
.\server\build\Debug\GameServer.exe --campaign battle_zone_multiplayer --location uni_exterior
|
|
```
|
|
|
|
The listener defaults to `0.0.0.0:8080`. It can be changed with:
|
|
|
|
```text
|
|
--address IP
|
|
--port N
|
|
```
|
|
|
|
Launch the Windows game and select `battle_zone_multiplayer`. The client connects to
|
|
`127.0.0.1:8080`. Selecting another campaign continues to use the local server.
|
|
|
|
## Verification
|
|
|
|
The implementation was verified with:
|
|
|
|
- A successful Windows graphical-game build.
|
|
- A successful Windows headless-server build.
|
|
- Server state and protocol tests.
|
|
- A WebSocket smoke test receiving the initial state and the next five-second
|
|
broadcast.
|
|
- A check that the shared `"player"` exists and the battle-zone test bot is not
|
|
created for the network server.
|
|
|
|
## Current Limitations
|
|
|
|
- All clients share the fixed player ID `"player"`.
|
|
- Client commands are not sent to the server.
|
|
- The server does not process commands.
|
|
- State is sent as a complete snapshot rather than a delta.
|
|
- There is no authentication, authorization, reconnect loop, or session identity.
|
|
- Presentation events are not replicated.
|
|
- The endpoint is hardcoded in the graphical client.
|
|
- Multiplayer Lua and dialogue behavior remains unsupported.
|