Skip to main content

Networking

CrateProtocolCalls
balaur_httprequestshttp::request
balaur_websocketconnectionswebsocket::connect, send, close
balaur_webtransportQUICthe 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.

For multiplayer games

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 offers permessage-deflate (RFC 7692) and lets the server decide, and headers for the upgrade request.
  • An HTTP request takes method, body, headers and timeout.
  • 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.