C / C++ tutorial — build multiplayer against the Lattice C ABI¶
A complete, hands-on guide to the Lattice C ABI — the single extern "C" header
(lattice/lattice.h) that the whole networking suite exposes. Everything an engine binding
(Unity / Unreal / Godot) does, it does by calling this surface. So this tutorial teaches the
foundation layer directly: you create a lattice_runner, register replicated types, spawn
objects, send RPCs and events, and pump the fixed-tick loop — all in plain C/C++.
This tutorial teaches the real API that actually runs, grounded in code you can read and compile
in the repository. Every function name, signature, and snippet is taken from the shipped public
header (reference/include/lattice/lattice.h) and the working, runnable test drivers and
examples (reference/tests/, examples/). Where something is a stub, an extension point, or only
partially wired, we say so plainly.
This is the layer the engine bindings wrap
The Unity binding (com.lattice.netcode), the Unreal and Godot bindings, and the WASM host are
all thin adapters over this exact C ABI. If you are writing a dedicated server module, a
headless simulation, or your own engine integration, this is the surface you target. If
you are in Unity, read the Unity tutorial instead — it wraps everything
here in idiomatic C#.
Verified against: reference/README.md (the header "is the only file a binding
includes"); reference/include/lattice/lattice.h:1-13.
This is the reference skeleton, not the production core
The liblattice.so you build here is a faithful, compilable model of the documented ABI +
wire encodings, with a working loopback transport, real UDP transport, delta replication, and
reliable RPC. It deliberately omits crypto, NAT/relay, client-side prediction/rollback, and
interest management — those are called out at their extension points. The ABI shape, the wire
codec, and the runner semantics are real and exercised by the conformance harness.
Verified against: reference/include/lattice/lattice.h:9-14; reference/README.md
("What it deliberately omits").
What you'll build¶
By the end you will have built, from the repository's own working drivers, a real UDP game server and a real UDP client that:
- start as
SERVER/CLIENTrunners over an actual127.0.0.1UDP socket, - complete a handshake (the client observes
LATTICE_CONN_CONNECTED), - register a shared replicated type and spawn + replicate an authoritative object,
- exchange RPCs and custom events in both directions, and
- shut down cleanly.
That is exactly what reference/tests/two_process.cpp (client) and
reference/tests/persistent_server.cpp (server) do — both include only <lattice/lattice.h>,
exactly like your code will. We build chapter 09 directly from them.
Verified against: reference/tests/two_process.cpp:1-27, reference/tests/persistent_server.cpp:1-11.
Chapters¶
| # | Chapter | What it covers |
|---|---|---|
| 00 | This page | Overview and orientation |
| 01 | Setup | Build liblattice.so (build.sh), include lattice.h, compile + link |
| 02 | Your first runner | lattice_runner_create → set_callbacks → start / listen / connect → the tick loop |
| 03 | Logging in | The auth HTTP service → an access token; how a C app makes the call |
| 04 | Lobbies & matchmaking | The director flow → lattice_runner_connect; verified wiring status |
| 05 | Networked objects | lattice_register_type + field specs, lattice_spawn, on_state_updated |
| 06 | RPCs | lattice_rpc + targets + the reliable flag |
| 07 | Events | lattice_send_event / on_event, the object-less message channel |
| 08 | Authority | Host-authoritative validation; lattice_request_authority / on_authority_changed |
| 09 | Full walkthrough | A minimal UDP server + client, end to end from the real drivers |
How to read this¶
Chapters build on each other. If you are new to Lattice, read them in order. If you already know a netcode C API, skim 02 and jump to 05.
The one rule that governs the whole ABI¶
Read this now; it explains the shape of every later chapter.
Callbacks fire synchronously inside
lattice_runner_tick()on the calling thread. No exception may propagate out of a callback into the core. All buffers are caller-owned; the core copies anything it retains. Pointers handed to a callback are valid only for the duration of the call.
Verified against: reference/include/lattice/lattice.h:22-26 and :290-292.
Because callbacks land inside your tick call, on your thread, there is no cross-thread marshalling
to worry about for gameplay: your on_spawned / on_state_updated / on_rpc / on_event handlers
run where you called tick. (The one exception is the optional async job pool / web fetch, whose
results are drained back onto the tick thread before your callback runs — see
chapter 03.)
Conventions¶
c/cppblocks are real or faithfully-adapted code from the header, the tests, or the examples.- Verified against: notes cite the source file (and line) each claim is grounded in.
- Where a feature is an extension point, a stub, or not wired in the reference, a warning or danger admonition calls it out.
See it applied end to end¶
Once you have worked through a few chapters, the Battleship case
study shows the whole surface used in anger by a complete,
shipped two-player game — including the parts this tutorial cannot tell you until you meet them:
that the live relay is one shared world so every payload needs a room stamp, that the reliable
flag on lattice_send_event is currently discarded so you need your own ack/resend, and how to
handle hidden per-player state on a server that runs no game logic.
Next: 01 — Setup, where you build liblattice.so and compile your first program
against the header.