naia
Server-authoritative entity replication and typed message passing for multiplayer games in Rust, running on native (UDP) and browser (WebRTC).
What naia is
naia lets you define a shared Protocol — a compile-time list of replicated
component types, message types, and channel configurations — that both the
server and the client agree on. Given that protocol:
- The server spawns entities, attaches replicated components, assigns users
to rooms, and calls
send_all_packetsevery tick. naia diffs changed fields and delivers them to every in-scope client automatically. - The client receives entity spawn/update/despawn events and the current server-side field values with no extra bookkeeping.
- Either side can send typed messages over ordered-reliable, unordered-reliable, or unreliable channels.
- The server can delegate authority over a specific entity to a client, allowing client mutations to flow back to the server while the server retains final ownership.
naia is ECS-agnostic. Bevy and macroquad adapters are included; the core crate
works with any entity type that is Copy + Eq + Hash + Send + Sync.
The internal networking model follows the Tribes 2 Networking Model.
Crate map
| Crate | Role | Add as a dependency when… |
|---|---|---|
naia-shared |
Protocol definition, component derives, channel types | You are writing the shared protocol crate |
naia-server |
Core server | Writing a server without Bevy |
naia-client |
Core client | Writing a client without Bevy or macroquad |
naia-bevy-server |
Bevy server adapter | Using Bevy on the server |
naia-bevy-client |
Bevy client adapter | Using Bevy on the client |
Quick concepts
- Protocol — the shared type registry. Both server and client build from
the same
Protocolvalue; a hash mismatch during the handshake causes rejection. - Entity — any
Copy + Eq + Hashvalue your world allocates. naia tracks which entities are replicated and to whom, but never allocates them itself. - Room — a coarse membership group. A user and an entity must share a room before replication is possible. Think: match, zone, lobby.
- Channel — a named transport lane with configurable ordering and reliability. Messages and entity actions travel through channels.
- Tick — the server's heartbeat.
take_tick_eventsadvances the tick counter.TickBufferedchannels deliver client input at the correct server tick for prediction and rollback. - Authority delegation — a server entity can be marked
Delegated, allowing a client to request write authority. The server grants or denies and can revoke at any time.
For the full mental-model guide, see docs/CONCEPTS.md.
Getting started
Core (no ECS)
# shared/Cargo.toml
[]
= "0.24"
# server/Cargo.toml
[]
= "0.24"
= { = "../shared" }
# client/Cargo.toml
[]
= "0.24"
= { = "../shared" }
See demos/basic/ for a minimal working example.
Bevy adapter
# shared/Cargo.toml
[]
= "0.24"
# server/Cargo.toml
[]
= "0.24"
= { = "../shared" }
# client/Cargo.toml
[]
= "0.24"
= { = "../shared" }
See demos/bevy/ for a complete Bevy demo.
macroquad adapter
# client/Cargo.toml
[]
= "0.24"
= { = "../shared" }
See demos/macroquad/ for a macroquad demo.
Channel reference
| Mode | Ordering | Reliability | Canonical use |
|---|---|---|---|
UnorderedUnreliable |
None | None | High-frequency telemetry |
SequencedUnreliable |
Newest-wins | None | Position updates (stale ok) |
UnorderedReliable |
None | Guaranteed | One-off notifications |
OrderedReliable |
FIFO | Guaranteed | Chat, game events |
TickBuffered |
Per-tick | Guaranteed | Client input for prediction |
| Bidirectional + Reliable | FIFO | Guaranteed | Request / response pairs |
Platform support
| Target | Transport | Notes |
|---|---|---|
| Linux / macOS / Windows | UDP | naia-socket-native |
Browser (wasm32-unknown-unknown) |
WebRTC data channel | Enable wbindgen feature; build with wasm-pack or trunk |
| iOS / Android (native) | — | Not yet supported — blocked on transport_quic; see FEATURES.md |
| iOS / Android (via WebView) | WebRTC data channel | Run the WASM client inside WKWebView (iOS) or Android WebView; same build as the browser target |
The server always runs natively. Only the client needs WebRTC support for browser targets.
Steam relay (ISteamNetworkingSockets): Not built-in. The Socket trait is
pluggable — a community crate can add Steam networking without modifying naia
core.
Coming from another library?
| You know… | naia equivalent | Key difference |
|---|---|---|
renet NetworkedEntity / RenetServer |
Server<E> + #[derive(Replicate)] |
naia replicates ECS state automatically (diff + send); renet is message-only |
renet send_message / receive_message |
server.send_message::<Ch, M>() |
naia wraps channels as typed Rust generics |
renet ClientId |
UserKey |
Same concept — opaque handle to a connected client |
lightyear Replicate component |
#[derive(Replicate)] on a struct |
naia is ECS-agnostic; lightyear is Bevy-only |
lightyear ComponentRegistry |
Protocol::add_component::<C>() |
Same idea; naia uses a builder pattern |
lightyear InputChannel / predicted input |
TickBuffered channel + receive_tick_buffer_messages |
naia delivers client input at the matching server tick for rollback |
lightyear Predicted / Interpolated |
not built-in | naia supplies the data; you write the prediction/interpolation logic (see PREDICTION.md) |
bevy_replicon Replication marker |
server.spawn_entity() + #[derive(Replicate)] |
naia has fine-grained per-user scope control via rooms + UserScopeMut |
| bevy_replicon visibility filter | server.user_scope_mut(&user) / rooms |
naia: rooms = coarse; user-scope = fine-grained |