2.7 KiB
0010. Dedicated net crate with a confined async runtime
- Status: Accepted
- Date: 2026-07-12
Context
Our network transport uses QUIC via quinn, which is an asynchronous library built squarely on the tokio runtime and inherently requires TLS 1.3 through rustls. These are heavy dependencies that drag an entire async ecosystem into the build tree.
We have a strict mandate that the shared crate must stay incredibly lean and dependency-light. It acts as our core protocol and data layer, holding pure serde message types with absolutely no async, rendering, or engine internals. Dropping transport code straight into shared would brutally violate that mandate, forcing every single consumer of our protocol types to compile both tokio and rustls.
At the same time, our main simulation is completely synchronous. The server runs a synchronous bevy_ecs loop, and the client runs a synchronous winit event loop. Introducing an async runtime must absolutely not force those loops to become async or let tokio leak throughout the entire workspace.
Decision
Transport logic lives completely isolated in a dedicated net crate, entirely separate from shared, and the tokio runtime is strictly confined to it.
netcompletely owns thequinn,tokio, andrustlsdependencies. It handles the QUIC endpoints, the full connection lifecycle, and all wire framing.sharedremains perfectly clean, holding only the raw protocol message types (usingserde, with zero async logic).- Both
clientandserverdepend onnet. - We bridge the async runtime to the synchronous simulation using
crossbeam-channel, which aligns perfectly with the message-passing concurrency model defined inDEVELOPMENT.md. The synchronous loops never ever call.await; they simply send and receive protocol messages across the channel boundary.
Consequences
sharedstays extremely lean. Consumers that only need the protocol types do not have to compile the massive async stack.- The async surface is perfectly quarantined. Only
netactually deals withtokio, keeping both theserverandclientloops happily synchronous and completely unchanged. - The workspace now contains six crates, with
netsitting neatly betweenshared(which provides the types it carries) and the two binaries (which actively drive it). - The channel bridge acts as an explicit, hard boundary that must be maintained. Any work crossing between the async runtime and the synchronous simulation must flow purely through channels. We never pass shared async state or force the simulation to become async.
- Because
rustlsrequires a crypto provider backend, the transport code has to manually install one before building any QUIC configuration.