From aaff38c7844cdca88f9c8ab00fd8efad8b92d42d Mon Sep 17 00:00:00 2001 From: Serkyo Date: Sun, 12 Jul 2026 00:28:45 +0200 Subject: [PATCH] docs(workspace): add ADR-0010 for net crate and async runtime --- AGENTS.md | 5 +++-- docs/adr/0010-net-crate-async-runtime.md | 27 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 docs/adr/0010-net-crate-async-runtime.md diff --git a/AGENTS.md b/AGENTS.md index 49993cf..a917554 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,15 +20,16 @@ The canonical game-*design* specification (intent, world rules, gameplay behavio ## Workspace layout -Cargo workspace (resolver = "3", edition 2024) with four crates under `crates/`: +Cargo workspace (resolver = "3", edition 2024) with six crates under `crates/`: - `client`: binary. Windowed application using `winit` 0.30 (`ApplicationHandler` pattern, `ControlFlow::Poll`). Also pulls in `image`. Player-facing app titled "Synvael"; handles input, windowing, and drives the renderer. - `server`: binary. Authoritative game simulation (voxel world, combat, players). Used both for dedicated multiplayer hosts and as the simulation backend for single-player. - `renderer`: library. Voxel/scene rendering on Vulkan via `ash`, decoupled from windowing so it can be driven by `client`. - `shared`: library. Types and protocol shared between `client` and `server` (world/voxel data, network messages, combat primitives). Stays lean and dep-light; no `mlua`, no rendering, no engine internals. - `scripting`: library. Lua modding API and bindings (owns the `mlua` dependency, `UserData` wrappers around `shared` types, API table registration, mod loader). Both `client` and `server` depend on it. +- `net`: library. QUIC transport, connection lifecycle, and wire framing for the client↔server protocol; owns the async runtime (`tokio`) and the `quinn`/`rustls` dependencies. Both `client` and `server` depend on it. See [ADR-0010](docs/adr/0010-net-crate-async-runtime.md). -When adding code, keep the boundary tight: protocol/data types and game-rule primitives go in `shared`; Lua API surface and `mlua` integration in `scripting`; GPU/draw code in `renderer`; only input, windowing, and presentation glue live in `client`. Avoid growing `client` with simulation logic since it must work identically against either a local or remote `server`. +When adding code, keep the boundary tight: protocol/data types and game-rule primitives go in `shared`; Lua API surface and `mlua` integration in `scripting`; GPU/draw code in `renderer`; transport and connection code in `net` (protocol message *types* stay in `shared`); only input, windowing, and presentation glue live in `client`. Avoid growing `client` with simulation logic since it must work identically against either a local or remote `server`. ## Modding API (Lua): dogfooded diff --git a/docs/adr/0010-net-crate-async-runtime.md b/docs/adr/0010-net-crate-async-runtime.md new file mode 100644 index 0000000..593d7b1 --- /dev/null +++ b/docs/adr/0010-net-crate-async-runtime.md @@ -0,0 +1,27 @@ +# 0010. Dedicated `net` crate with a confined async runtime + +- **Status:** Accepted +- **Date:** 2026-07-12 + +## Context + +The network transport is QUIC via `quinn` which is an asynchronous library built on the `tokio` runtime and requires TLS 1.3 through `rustls`. These are heavy dependencies that pull an entire async ecosystem into the build. + +The `shared` crate is mandated to stay lean and dependency-light: it is the protocol/data layer, holding pure serde message types with no async, rendering, or engine internals. Placing transport code in `shared` would violate that mandate and force every consumer of the protocol types to compile `tokio` and `rustls`. At the same time, the simulation is synchronous: the `server` runs a synchronous `bevy_ecs` loop and the `client` runs a synchronous `winit` event loop. Introducing an async runtime must not turn those loops async or leak `tokio` throughout the workspace. + +## Decision + +Transport lives in a dedicated `net` crate, separate from `shared`, and the `tokio` runtime is confined to it. + +- `net` owns the `quinn`, `tokio`, and `rustls` dependencies, plus the QUIC endpoints, connection lifecycle, and wire framing. +- `shared` continues to hold only the protocol message *types* (serde, no async). +- Both `client` and `server` depend on `net`. +- The async runtime is bridged to the synchronous simulation over channels (`crossbeam-channel`), consistent with the message-passing concurrency model in `AGENTS.md`. The synchronous loops never `.await`; they send and receive protocol messages across the boundary. + +## Consequences + +- `shared` stays lean: consumers of the protocol types do not compile the async stack. +- The async surface is quarantined. Only `net` deals with `tokio`, keeping the `server` and `client` loops synchronous and unchanged. +- The workspace now has six crates. `net` sits between `shared` (types it carries) and the two binaries (which drive it). +- The channel bridge is an explicit boundary that must be maintained: work crossing between the async runtime and the sync simulation flows through channels, never through shared async state or by making the sim async. +- A crypto provider backend is required by `rustls`; the transport code must install one before building QUIC configuration.