Expand description
plaza_client_utils
This crate provides client-side utilities designed to complement applications built with the Plaza server framework. It focuses on helping client applications implement common networking patterns such as:
- Client-Side Prediction (CSP): Allowing clients to predict the outcome of their inputs locally for immediate feedback.
- Server Reconciliation: Correcting client predictions with authoritative state received from the server.
- State Interpolation/Extrapolation: Smoothing the display of remote entities.
The utilities are designed to be generic and unopinionated about the specific
game engine or rendering library used by the client application. They provide
data structures and algorithms that operate on application-defined StateType
and ClientOp types.
§Core Components
input_buffer::ClientInputBuffer: Stores a history of client inputs sent to the server, essential for replaying inputs during reconciliation.prediction::PredictedEntity: Manages the predicted state of a client-controlled entity and handles the reconciliation process against server updates.interpolation::SnapshotBufferand theInterpolatabletrait: buffer server snapshots of remote entities and interpolate between them for smooth rendering.interpolation::InterpolationClocksupplies the render-time target they need.extrapolation::ExtrapolationBaseand theExtrapolatabletrait: project a remote entity’s movement for short durations to hide gaps between updates.smoothing::ErrorSmoother: eases a reconciliation correction over a few frames instead of snapping it.timestep::FixedTimestepandPeriodic: turning however long the last frame took into whole fixed steps or into “is it time yet”. Two simulations running the same rule at different step sizes drift apart and the drift reads as network jitter, so both sides should take the step from here.meter::RateMeter: what the wire cost, as a windowed rate. A session average would keep creeping toward a new level without reaching it.plaza_server_utilsre-exports it, so both ends use the same arithmetic.determinism: the draws, noise and hashes a shared rule derives its world from, identical on wasm and native and pinned by test, plusdigest::StateDigestfor detecting a divergence before it shows on screen.rollback: the other netcode family, peer-to-peer deterministic lockstep.StateHistory,InputTimelineand theRollbackSessionbundle predict a missing remote input and roll back to re-simulate when the guess is disproved.
§Four principles
No type can enforce these. Between them they account for every netcode bug
found while building the playground examples. The rest of this crate can
only recover from those bugs after they happen. The first two are about
simulation and the last two about rendering. The examples’ LEARNINGS.md
records the bugs behind each one.
1. A shared rule must be shared code, not code written twice. The apply
you hand PredictedPlayer or HeldInputPredictor should be the server’s
own step function rather than a client approximation of it. Anything the
server does that your copy leaves out shows up as a constant correction. It
looks like network jitter, it is largest when it is most visible and it is
very hard to track down. Across two examples, every entity whose rule lived
in one function both sides called stayed correct and every entity whose rule
was written twice drifted.
If your client’s rule needs the world to run (gravity, wind, a moving platform), pass it through the context parameter. Without a way to pass the world in, people end up writing a second, simpler rule. Treat that as a gap in the API to fix and keep the one rule.
2. Prediction is presentation; shared rules consume authoritative state. Feeding a locally predicted position into a rule that both sides run makes the client compute a different world from the server’s and every packet then pulls it back. Prediction drives the camera and the local player’s own marker. The rules both sides run read the authoritative state, even though it is older. This is easy to get wrong, because using the freshest local data looks like an improvement.
3. One instant per frame. A client that renders in the past picks a
single instant T for the whole frame and evaluates everything at T. That
covers where entities are drawn and also everything a behaviour rule reads
while producing the frame, such as aim targets and chase context. An entity
simulated to T that reads its target from the newest packet mixes two
timelines in one scene, which is a bug even before it becomes visible.
interpolation::InterpolationClock supplies T; making every read use it
is up to you.
4. The timeline comes from declaration, not arrival. Transport facts, round trips and jitter and arrival times, may size buffers and admit or refuse connections. They never decide which moment is on screen or when an input executes; those are declared numbers the server chooses and publishes. A render clock steered by packet arrival hides bad links instead of reporting them and lets every client pick a different “now”. It also lets each player’s ping change what happens in the game.
§The resume contract
Every long-lived client eventually stops reading, for example when a browser tab goes to the background, a laptop sleeps or a frame loop stalls. The socket keeps receiving the whole time, so a resumed client gets a lump: minutes of packets delivered at once, describing moments it can no longer play. Recovery rests on one invariant, stated here because each half lives in a different crate:
A client may discard any stretch of the stream unread, provided it also drops the state derived from it, because an acknowledgement carrying the digest of nothing obligates the server to answer with a full baseline.
The digest-and-rebuild machinery of server_utils::DeltaBaseline and
mirror::DeltaMirror makes that discard safe. There is no “resync
request” message; dropping the mirror acts as the request. Resume handling
is split across three layers:
- the transport discards the backlog before parsing it
(
plaza_ws::trim_backlog), because all of it would be dropped anyway; - the playout queue treats the gap as a discontinuity and restarts
once, keeping only the newest packet (
PlayoutBuffer); - the server stops streaming to a subscriber that has provably stopped
reading (
DeltaBaseline::with_flow), so the lump never grows to megabytes in the first place.
The application has one job left: on
playout::Admission::TimelineLost, drop the mirror and re-anchor the
render clock on what just arrived.
§Which predictor
Pick between the two by how the server consumes input. A wrong choice raises no error; it shows up as a prediction that is always slightly behind.
| the server | use |
|---|---|
| consumes one input per simulation step | PredictedPlayer (replay unacknowledged inputs) |
| holds an input and integrates it every tick | HeldInputPredictor (dead reckon and ease) |
Replaying inputs against a server of the second kind double counts, and gets worse the more you economise on bandwidth, because one coalesced input can cover a long stretch of simulation.
§Philosophy
plaza_client_utils provides building blocks rather than a complete
client-side framework. The application developer is responsible for:
- Defining their
StateTypeandClientOptypes. - Implementing the client-side game logic (how an
OpaffectsStateType). - Integrating with their chosen networking library (e.g., WebSockets, WebRTC, renet)
to send
ClientOps and receive server state updates. - Driving the rendering loop and using the predicted/interpolated states.
Re-exports§
pub use absence::Silence;pub use ack::AckWindow;pub use arrival::ArrivalMonitor;pub use clock_sync::ClockSyncEstimator;pub use coalesce::InputCoalescer;pub use correction::Correction;pub use correction::CorrectionMonitor;pub use determinism::mix64;pub use determinism::ValueNoise;pub use determinism::XorShift;pub use digest::SetDigest;pub use digest::StateDigest;pub use error::ClientUtilError;pub use held_input::HeldInputConfig;pub use held_input::HeldInputPredictor;pub use hermite::hermite_scalar;pub use hermite::HermiteInterpolatable;pub use hermite::HermiteView;pub use filter::ScalarKalman;pub use input_buffer::BufferedInput;pub use input_buffer::ClientInputBuffer;pub use meter::RateMeter;pub use mirror::Agreement;pub use mirror::DeltaMirror;pub use mirror::Divergence;pub use interpolation::InterpolationClock;pub use interpolation::SnapshotBuffer;pub use playout::Admission;pub use playout::PlayoutBuffer;pub use predicted_player::PlayerConfig;pub use predicted_player::PredictedPlayer;pub use prediction::PredictedEntity;pub use remote_view::RemoteView;pub use remote_view::RenderOpts;pub use rollback::repeat_last_input;pub use rollback::Frame;pub use rollback::InputTimeline;pub use rollback::RollbackConfig;pub use rollback::RollbackSession;pub use rollback::StateHistory;pub use route::Heard;pub use route::RoutePredictor;pub use smoothing::AdaptiveDecay;pub use rtt::RttEstimator;pub use slot::ReusePolicy;pub use slot::SlotAllocator;pub use slot::SlotKey;pub use timeline::Probe;pub use timeline::Timeline;pub use timestep::FixedTimestep;pub use timestep::Periodic;pub use timestep::Steps;pub use smoothing::ease_in_cubic;pub use smoothing::ease_in_out_quad;pub use smoothing::ease_in_quad;pub use smoothing::ease_out_cubic;pub use smoothing::linear;pub use smoothing::smoothstep;pub use smoothing::Easing;pub use smoothing::ErrorSmoother;pub use types::ClientTimeMs;pub use types::SequenceNumber;
Modules§
- absence
- Acting on an entity the server has stopped mentioning.
- ack
- Sliding-window acknowledgement: telling the other side exactly what arrived, in sixteen bytes, however badly the link is behaving.
- arrival
- Measuring how a stream actually arrives, and what render delay covers it.
- clock_
sync - Estimating a client’s clock against a server’s, with drift.
- coalesce
- Deciding when an input is worth transmitting, as opposed to when it is worth simulating.
- correction
- Measuring prediction error and detecting when a correction is abnormal.
- determinism
- Draws, noise and hashes that give the same number from the same inputs on both ends and in every build.
- digest
- An order-independent digest of a set of keys, for catching a mirror that has silently stopped matching the set it is supposed to hold.
- error
- Defines error types for the
plaza_client_utilscrate. - extrapolation
- Utilities for client-side extrapolation of remote entity states.
- filter
- A scalar Kalman filter: an optimal smoother for one noisy signal.
- fixed
- Fixed-point arithmetic, for a wire that carries inputs instead of state.
- held_
input - Prediction for a server that holds an input and integrates it, rather than consuming one input per step.
- hermite
- Interpolation that uses the velocity recorded at each sample.
- input_
buffer - Manages a buffer of client inputs that have been sent to the server and are awaiting acknowledgement and reconciliation. This is a core component for implementing client-side prediction.
- interpolation
- Utilities for interpolating remote entity states for smooth rendering.
- math
- Optional basic math types and utilities for
plaza_client_utils. - meter
- Counting what crosses the wire, so bandwidth can be shown as a measured number.
- mirror
- The client’s copy of a set of entities the server is streaming to it, and the bookkeeping that says whether the copy is right.
- net_sim
- A deterministic network simulator for testing and demonstrating netcode.
- playout
- A bounded buffer for packets that play out at the instant they describe, with the discontinuity handling a real transport forces on it.
- predicted_
player - A drop-in predicted local player: the whole client-side entity, wired.
- prediction
- Utilities for managing client-side predicted state and performing server reconciliation.
- remote_
view - A drop-in view of a remote entity: interpolation, extrapolation, and the starvation handling in between, bundled.
- rollback
- Rollback netcode: the deterministic-lockstep building blocks.
- route
- Prediction by shared rule: walk the route yourself, check the route and settle only at rest.
- rtt
- Estimating round-trip time to the other end from probe samples.
- slot
- Naming an entity across a wire when its storage slot gets reused.
- smoothing
- Easing a correction over a few frames instead of snapping it.
- timeline
- The client’s clocks, and the bookkeeping that says which samples still count.
- timestep
- Spending real elapsed time as whole fixed steps, and firing something on a period.
- trajectory
- Second-order dead reckoning: coasting a remote entity through a gap in the packet stream using how its velocity was changing as well as the velocity itself.
- types
- Common utility types used throughout the
plaza_client_utilscrate.