Networking
| Crate | Protocol | Calls |
|---|---|---|
balaur_http | requests | http::request |
balaur_websocket | connections | websocket::connect, send, close |
balaur_webtransport | QUIC | the same Transport seam |
All three are on by default, each behind its own feature. Building without
http drops the HTTP and TLS stack; without webtransport drops QUIC and its
runtime.
Balaur for multiplayer puts this delivery model beside the fixed tick, rollback and replay.
The delivery model
- All I/O runs on background threads.
- Completions cross back over a channel and enter the simulation once per tick, at the start of the frame, recorded in a snapshot.
- Input uses the same model. The per-tick snapshot replays a networked run.
- Nothing blocks the frame. Handlers never run from an I/O thread.
Scripts never poll. There are two shapes.
The first is a handler method: a request or connection names the node that handles its results, dispatched as method calls.
pub fn init(this) {
this.request = http::request(this.node, "https://example.com");
}
pub fn on_response(this, r) {
log::info(format!("{} {}", r["status"], r["body"]));
}
The second is a sequential await: a request without a node returns a token
the script suspends on, from an async entry point.
pub async fn init(this) {
let r = task::wait(http::request(url)).await;
log::info(format!("{} {}", r["status"], r["body"]));
}
The script resumes at the start of a later tick, in arrival order. Given the same recorded arrivals the result is deterministic. An async entry point cannot be paused by the debugger.
Options and configuration
this.socket = websocket::connect(this.node, "wss://game.example/play", #{
on_event: "on_socket",
compression: true,
headers: #{ Authorization: "Bearer " + token },
});
[websocket]
compression = true # offer permessage-deflate on every connection
[http]
timeout_seconds = 10.0 # for a request that names none
- A websocket takes
compression, which offerspermessage-deflate(RFC 7692) and lets the server decide, andheadersfor the upgrade request. - An HTTP request takes
method,body,headersandtimeout. - Defaults come from each protocol's table in
project.toml. - A call's own option wins over the table.
Events instead of signals
- A script cannot register a persistent callback and be handed a payload three frames later.
- The engine calls a method the script declares by name. A script's own events work the same, scoped to an emitting node or to all of them (scripting).
- Most have a polling twin (
input::key_just_pressed,animation::just_finished). - An event is a frame-scoped snapshot, not a subscription.
For a hosted game backend (auth, storage, lobbies) see Gamend, built on the same delivery contract.
Rollback, and what is missing
Rollback is built. Two engines exchange only inputs, predict each other, roll back when a prediction was wrong, and compare a per-tick digest to catch a desync. It runs over WebSockets or QUIC. On QUIC a datagram is a true datagram: a lost input costs a misprediction, not a stall.
Replication, RPC, a browser WebTransport backend and WebRTC are missing. The plan is PLAN-networking.md, with the short form on the roadmap. Raw UDP is not planned: QUIC datagrams cover it, with encryption and a browser story.