Skip to content

Reliable Datagram Commands

WebTransport connections support command datagram lanes alongside the reliable stream. When a player input must reach the server without waiting behind unrelated stream traffic, routing that command over a datagram lane avoids the head-of-line blocking that stream ordering introduces. golem exposes raw unreliable, reliable unordered, and reliable ordered datagrams for game payloads, and generates per-command helpers so you can route each command type at the right reliability level.

router := synced.NewCommandRouter(server)
router.OnMove(func(senderID int64, entity *synced.SyncedPlayer, cmd *synced.MoveCommand) {
entity.X += cmd.Dx
entity.Y += cmd.Dy
})
router.BindAllHandlers()
import {
createClient,
sendMoveReliableOrdered,
sendMoveReliableUnordered,
} from "./synced/client.js";
const client = createClient();
client.connect({
transport: "webtransport",
url: "https://example.com/wt",
serverCertificateHashes,
});
sendMoveReliableUnordered(client, playerId, dx, dy);
sendMoveReliableOrdered(client, playerId, dx, dy);
client.SendReliableUnordered(CommandBuilders.BuildMoveCommand(playerId, dx, dy));
client.SendReliableOrdered(CommandBuilders.BuildMoveCommand(playerId, dx, dy));

WebTransport connections expose three command/datagram lanes in addition to the reliable stream:

  • unreliable — lossy, fire-and-forget. Use for short-lived state hints or input previews that are superseded by the next tick anyway.
  • reliable-unordered — guaranteed delivery, no ordering constraint. Use for commands and events that must arrive but whose relative order does not matter.
  • reliable-ordered — guaranteed delivery in send order. Use for commands that must be processed in the sequence they were issued.

The reliable stream is the path for snapshots, world bootstrap, server events, automatic entity replication, and WebSocket connections. Datagram command helpers are explicit WebTransport-only sends for small command payloads.

Use the stream path when you want batching and WebSocket compatibility:

  • client: client.send(buildMoveCommand(...))
  • server: generated DispatchPacket, installed through BindStreamHandler()

Use raw unreliable datagrams when you want lossy delivery and own the payload format:

  • server hooks: OnDatagram
  • send APIs: SendUnreliable, BroadcastUnreliable

Use reliable unordered datagrams when the message must arrive but ordering does not matter:

  • server hooks: OnReliableUnordered
  • send APIs: SendReliableUnordered, BroadcastReliableUnordered
  • generated client helpers: send*ReliableUnordered(...)

Use reliable ordered datagrams when commands must be processed in send order:

  • server hooks: OnReliableOrdered
  • send APIs: SendReliableOrdered, BroadcastReliableOrdered
  • generated client helpers: send*ReliableOrdered(...)

The generated CommandRouter provides three binding helpers:

  • BindStreamHandler() — installs stream command routing on Server.OnMessage(...).
  • BindReliableUnorderedHandler() — installs datagram command routing on Server.OnReliableUnordered(...).
  • BindReliableOrderedHandler() — installs datagram command routing on Server.OnReliableOrdered(...).

BindAllHandlers() installs all three at once, which is the right default when clients may use any lane:

router := synced.NewCommandRouter(server)
router.OnAction(func(senderID int64, entity *synced.SyncedPlayer, cmd *synced.ActionCommand) {
// handle action
})
router.BindAllHandlers()

Use the narrower helpers when you want to restrict which lanes are active:

router := synced.NewCommandRouter(server)
router.BindStreamHandler()
// or:
router.BindReliableOrderedHandler()

Each binding helper replaces any previously registered callback on the same hook. If you already set Server.OnMessage, Server.OnReliableUnordered, or Server.OnReliableOrdered manually, decide whether the generated router should own that hook or whether you want to keep custom dispatch code.

golem-bake generates per-command datagram helpers alongside the standard builder functions. In JavaScript, a Move command gets:

  • sendMoveReliableUnordered(client, ...)
  • sendMoveReliableOrdered(client, ...)

In generated C# for Unity, build the command and pass it to the datagram send method:

client.SendReliableUnordered(CommandBuilders.BuildMoveCommand(playerId, dx, dy));
client.SendReliableOrdered(CommandBuilders.BuildMoveCommand(playerId, dx, dy));

These encode one ClientMessage and send it over the matching datagram lane. You can also call the JavaScript runtime directly when you prefer not to use the generated helpers:

const client = createClient();
client.sendReliableUnorderedCommand(buildChatCommand("hello"));
client.sendReliableOrderedCommand(buildMoveCommand(playerId, dx, dy));

OnDatagram receives raw unreliable application payloads only. Protocol ACK packets and reliable datagram messages are decoded internally before your gameplay hooks run, so OnDatagram handlers do not receive those messages.

Reliable datagram helpers only send when the active transport exposes datagram lanes. In practice that means WebTransport — if the client connected over WebSocket, calling a reliable datagram send method has no matching lane and will not deliver the message.

Reliable ordered datagrams preserve ordering but remain datagram-sized messages. In the current protocol, that means payload budgets below the WebTransport datagram limit: about 1178 bytes for raw unreliable payloads, 1176 bytes for reliable unordered datagrams, and 1174 bytes for reliable ordered datagrams once protocol headers are included. Use them for discrete commands.