Multiplayer and multi-user-editor support built into the data layer: change tracking, delta encoding, interest management, authority, events/RPCs, snapshots, and client-side prediction. Server-authoritative from the ground up, with per-field conditions driving everything.
Every mutation during Simulate is recorded.
Field deltas encoded per component schema.
RelevanceSet filters what each client sees.
AuthorityTable resolves who may write what.
One-shot messages outside state deltas.
Full state baselines for resync and joins.
Client-side prediction + reconciliation.
Declare which fields replicate, how they encode, and under what conditions. The handshake serializes the whole schema set so every client can decode any delta it receives. Hand-rolled schemas compose with SchemaBuilder. Or use #[derive(Replicate)] and let the macro register real field accessors for you.
1use pulsar_scenedb::{ReplicationRegistry, ReplicationEncoding, ReplicationCondition,2 EventChannel, SchemaBuilder, Component};34let mut registry = ReplicationRegistry::new();56let builder = registry.register::<Transform>();7registry.insert(8 builder.whole_field("matrix", ReplicationEncoding::Pod, ReplicationCondition::Always)9);1011let builder = registry.register::<Health>();12registry.insert(13 builder.whole_field("value", ReplicationEncoding::DeltaCompressed, ReplicationCondition::SimulatedOnly)14);1516// Serialize schemas for the connection handshake.17let handshake = registry.handshake_message();18let remote_registry = ReplicationRegistry::from_handshake(&handshake).unwrap();
Track changes during Simulate, drain into a Delta at the frame boundary, then filter and encode per client. Relevance, conditions, and event direction all enforced in one pass.
1fn server_tick(2 world: &mut World,3 witness: &CpuSimulateWitness,4 registry: &ReplicationRegistry,5 authority: &AuthorityTable,6 clients: &[ClientId],7 spatial_cells: &[SpatialCell],8 entity_cell_map: &EntityCellMap,9 liveness: &LivenessSnapshot,10 scratch: &mut Scratchpad,11) -> Vec<(Delta, Vec<EventBatch>)> {12 // 1. Track all changes and drain into a Delta in one call.13 let mut tracker = ChangeTracker::new();14 let delta = witness.run_tracked(world, &mut tracker, |world, tracker| {15 run_systems(world, tracker);16 });1718 // 2. Build per-client outputs.19 let mut outputs = Vec::new();20 for &client in clients {21 // Spatial relevance, resolved to ECS entities via EntityCellMap.22 let relevance = RelevanceSet::from_frustum_mapped(23 spatial_cells, &client_frustum(client), liveness, scratch, entity_cell_map,24 );2526 // Filter by relevance + conditions.27 let view = relevance.filter(&delta, authority, registry, client);2829 // Build event batch with direction enforcement.30 let batch = events_to_batch(&view, delta.frame, registry, ClientId(0), client);3132 outputs.push((delta.clone(), batch.into_iter().collect()));33 }34 outputs35}
The reconciler keeps a history ring buffer of server snapshots and a queue of unacknowledged local inputs. When a server delta arrives, it discards acknowledged inputs and replays the remaining predicted inputs on top of the corrected world.
1let mut reconciler = Reconciler::new();23// Each local tick, push the player's input.4reconciler.push_input(ClientInput {5 frame: local_frame,6 entity: player_entity,7 component: component_id::<Movement>(),8 field_data: vec![(0, serialize_movement(&input))],9});1011// When a server delta arrives, apply it, then reconcile.12server_delta.apply(&mut world, ®istry).unwrap();13reconciler.reconcile(&server_delta, &mut world, |world, input| {14 apply_input_to_world(world, input);15});
position/velocity replicated Always · on_impact is a Multicast event
move_direction ClientAuthority delta-compressed · on_jump is a ClientToServer event
selected Shared · custom_properties Shared, serialized Vec
world_position Always · minimap_blips OwnerOnly · fog_of_war_reveal SkipOwner · proxy_mesh SimulatedOnly GpuHandle
| Encoding | Value | What travels |
|---|---|---|
| Pod | 0 | Plain memcpy, the fastest path |
| Serialized | 1 | Self-framing owned/heap data (String, Vec<T>, Option<T>) |
| GpuHandle | 2 | Only the 8-byte handle index travels, the resource never moves |
| DeltaCompressed | 3 | Stateful delta compression per field |
| Event | 4 | One-shot RPC, never in state deltas |
| Opaque | 5 | Engine-defined byte blob |
Want the full replication deep-dive?
Replication Docs