Releases: cBournhonesque/lightyear
Release list
0.29.0
Lightyear 0.29.0
Deterministic peer-to-peer
Lightyear can now run deterministic, input-only P2P games without an authoritative server. For now this only works when the total number of peers is fixed.
A minimal application setup looks like this:
app.add_plugins(ClientPlugins { tick_duration });
app.add_plugins(P2PSessionPlugin);
// This is a capacity including the local peer, not a required player count.
app.insert_resource(P2PSession::new(max_peers));You need to add the P2P component on each Link that should be part of the P2P session.
commands.spawn((
P2P,
Link::default(),
));Trigger P2PStart once the initial cohort has been created; Lightyear waits until the frozen cohort and the input timeline are ready, negotiates one future start tick, and then emits the P2PStarted event on every peer.
Most examples can now be run in P2P mode.
One timeline and rollback pipeline
LocalTimeline and InterpolationTimeline were previously components, but it doesn't make sense to have more than one per App. (LocalTimeline is used to update the global Time<Virtual> resource, and Interpolated entities cannot be displayed for multiple interpolation timelines at once).
They have both been changed into Resources.
LocalTimeline is used to get the local fixed-update tick, and LocalTimelineSync is the resource used to sync the local Time<Virtual> with the remote peers.
You can use the SystemParams SyncedInterpolationTimeline and SyncedLocalTimeline to access the resources while skipping the system if the timelines are not synced.
Similarly, prediction needs storing a history according to the local LocalTimeline, so Prediction/Rollback state is now global per application. Rollback and PredictionManager are now resources.
This lets timeline syncing and prediction work similarly for a client-server or a P2P app (where there is no Link entity symbolizing the local peer).
Interpolation rules
Interpolation is now rule based, similar to Replicon's replication rules. You can define multiple rules for a given component, and they will be applied in order of priority.
The main benefit is that now you only need to define the interpolation logic once. The selected rule is then reused in all interpolation situations:
- delayed interpolation of remote
Interpolatedentities FrameInterpolatebetween fixed ticks- visual correction after a prediction rollback
For a type that implements Bevy's Ease trait, the common setup stays short:
app.component::<Position>()
.replicate()
.predict()
.add_linear_interpolation()
.add_correction();For a custom function, use InterpolationFns::interpolate:
fn lerp_position(start: Position, end: Position, t: f32) -> Position {
Position(start.0 + (end.0 - start.0) * t)
}
app.interpolate_with::<Position>(
InterpolationFns::interpolate(lerp_position),
);InterpolationFns also lets you specialize the interpolation:
interpolate_with_context(fn)also passes the interpolation duration to the interpolation function.history_only()keeps a history of confirmed values (ConfirmedHistory<C>) but doesn't perform any interpolation forInterpolatedentities. Use this if you want to use your own custom logic for interpolating between history values.history_only().interpolate(fn)does the same thing but also registers a function that will be used for correction or frame interpolation.no_history(fn)registers a function only for frame interpolation or correction.disabled()opts matching entities out of that rule kind.
Multiple rules and priority
You can register several rules for the same component. Filters choose which archetypes can use a rule, while priority decides which matching rule wins:
// Broad default rule. A single-component rule has default priority 1.
app.linear_interpolate::<Position>();
// Projectiles use a different curve.
app.interpolate_with_priority_filtered::<Position, With<Projectile>>(
10,
InterpolationFns::interpolate(interpolate_projectile),
);
// Teleporting entities should not interpolate at all.
app.interpolate_with_priority_filtered::<Position, With<Teleporting>>(
100,
InterpolationFns::disabled(),
);For each entity archetype, higher priority wins. If priorities are equal, the rule registered first wins. A filter does not receive an automatic priority bonus, so a filtered override should normally use an explicit higher priority.
Rules can also target a bundle:
app.interpolate_bundle_with::<(Position, Rotation)>(
InterpolationFns::interpolate(interpolate_pose),
);The default priority is the number of components in the target. A default (Position, Rotation) rule therefore has priority 2 and wins over default single-component rules for Position and Rotation on an archetype where all of them match. Bundle rules are useful when several components must be sampled together.
This means an application can define a broad component rule, add specialized filtered rules for particular gameplay archetypes, and add a higher-priority bundle rule where several values must stay coupled. The rule selected for an entity is shared by delayed interpolation, frame interpolation, and correction rather than configuring those three systems separately.
Avian integration
Replication modes
The Avian integration now uses Position mode by default:
app.add_plugins(LightyearAvianPlugin {
replication_mode: AvianReplicationMode::Position {
sync_to_transform: false,
},
..default()
});There is no longer a mode that replicates Position but interpolates Transform. We can always interpolate the Position/Rotation values, even in frame interpolation. Position mode is preferred because it sends the compact physics pose instead of the full Transform. It also keeps simulation state separate from the temporary visual state produced by frame interpolation and correction.
Child colliders
The old integration had a problem with compound rigid bodies; (RigidBodies that have child Collider entities) this is now fixed.
The default Avian protocol now replicates Position only for entities with their own RigidBody:
app.component::<Position>()
.replicate_filtered::<With<RigidBody>>()
.predict()
.add_linear_interpolation()
.add_correction();Child colliders keep their local offset (Transform) and follow the RigidBody root through normal transform propagation.
Misc
Position mode automatically registers a Hermite bundle rule for (Position, Rotation, LinearVelocity, AngularVelocity).
The sync_to_transform option is about the gameplay authoring API, not about what gets interpolated:
- Keep it
falsewhen gameplay updatesPosition,Rotation. - Set it to
trueonly when fixed-update gameplay directly reads or writesTransform. - Use
AvianReplicationMode::Transformonly when transform data such as scale or local hierarchy state is itself part of the networked gameplay state.
Replication improvements
You can add the Persistent marker on replicated entities on the client to not despawn them when the client disconnects. You can add the Persistent marker on the ReplicationReceiver entity to extend this behavior to all replicated entities received by this receiver.
This is the flip-side to the ControlledBy::Lifetime value, which specified what the server does when a client that controls an entity disconnects.
Other changes
-
Tickis now an ordinary non-wrappingu32newtype. Ordering is numeric and arithmetic that returns anotherTicksaturates instead of wrapping. -
Messages and events can also be assigned to a remote timeline with
ChannelSettings::on_timeline. The receiver holds them until that timeline reaches the sender tick. This is mostly useful for the server to trigger events on a client's interpolation timeline. -
the serializer changed to
postcardsincebincodewas unmaintained.
Migration guide
Client setup and connection state
Client is now a unit marker. Connection state can be accessed via the custom QueryData ClientState .
A client that receives replication should include ReplicationReceiver. Prediction is now enabled by inserting PredictionManager as a resource:
app.insert_resource(PredictionManager::default());
commands.spawn((
Client,
ReplicationReceiver,
Link::default().with_conditioner(conditioner),
));Unlink::reason, Unlinked::reason, and Disconnected::reason are no longer strings. Use UnlinkReason and DisconnectedReason, for example UnlinkReason::UserRequested(None) or DisconnectedReason::TransportError(error.to_string()).
Timelines, prediction, and prespawning
InputTimeline has been removed. Read Res<LocalTimeline> for the current simulation tick. Use SyncedLocalTimeline for systems that should only run once the local clock is synchronized; use Option<SyncedLocalTimeline> in systems shared with server or host modes. (or if local_timeline.is_synced())
InterpolationTimeline and InterpolationConfig are resources. Replace client-entity queries with Res<InterpolationTimeline>, or use SyncedInterpolationTimeline when the system should wait for sync.
PredictionManager, LastConfirmedInput, Rollback, InputTimelineConfig, and PreSpawnedReceiver are resources rather than components on the client entity.
SyncEvent<T> has been replaced by the application-wide LocalTimelineShift { delta } event. Code that relied on tick wraparound should use ordinary comparisons and the new saturating Tick arithmetic.
Interpolation, frame interpolation, and c...
0.28.0
Lightyear 0.28.0
This release updates Lightyear to the Bevy 0.19 ecosystem and focuses on input hardening, input authorization hooks, and replication simplification.
Highlights
- Updated the dependency stack for Bevy 0.19, including Aeronet 0.21, Bevy Replicon 0.41, Avian 0.7, Bevy Enhanced Input 0.26, Leafwing Input Manager 0.21, and Rust 1.95.
- Simplified bevy enhanced input replication (you just have to spawn the Action entities on the server and replicate them to the client)
- Hardened input handling by bounding future input tick ranges and adding server-side validation/authorization hooks. Thanks to @mmannerm
- Simplified replication internals: resources are replicated like any other component, so there is no need for special-case logic for then.
- Enable handling diff-compressed components. Instead of replicating the full component state, only the delta between two states is replicated. This can be useful for components that store a Vec
What's Changed
- chore(deps): bump actions/checkout from 6 to 7 by @dependabot[bot] in #1518
- fix(inputs): bound InputMessage end_tick lookahead (DoS) by @mmannerm in #1525
- chore: upgrade deps to 0.19 by @cBournhonesque in #1532
- Simplify CatchUpGated catch-up activation by @cBournhonesque in #1527
- Enable diff-compnents by @cBournhonesque in #1517
- fix(inputs): only buffer inputs after timeline sync by @cBournhonesque in #1534
- feat(inputs): server-side input-validation seam (ValidateInputs) by @mmannerm in #1535
- Simplify resource replication by @cBournhonesque in #1536
- feat(inputs): opt-in authorize_controlled_targets helper (spoofed-target defense) by @mmannerm in #1526
- fix(inputs): simplify BEI input replication by @cBournhonesque in #1537
Full Changelog: 0.27.0...0.28.0
0.27.0
Changelog
This release is still compatible with bevy 0.18. I will submit another release soon for compatibility with bevy 0.19.
Major changes
-
Switched the replication backend to
bevy_replicon.- The goal is to reuse the wider Bevy networking ecosystem's work, avoid splitting contributor efforts, and benefit from Replicon's well-optimized and documented code.
- Lightyear still provides its own higher-level replication API for prediction, interpolation, authority metadata, visibility, hierarchy propagation, and pre-spawning.
- The old
lightyear_replicationimplementation was more tightly integrated with Lightyear and supported having multiple replication senders/receivers per app by addingReplicationSenderandReplicationReceivercomponents. Replicon is centered on server-to-client replication, so other replication patterns (client-to-server replication, distributed authority, etc.) are now unsupported. - Some old replication-layer features are not yet at parity, including component-level delta compression, authority switching, per-component priority, and some advanced sender/receiver topologies.
- The move also brings useful Replicon features into Lightyear, including marker-specific replication rules, a flexible visibility-filter system, and mutation/checkpoint information that can be used by prediction and interpolation.
- Removed the ReplicationGroup API.
-
Added a structured
lightyear_debugtracing layer throughlightyear_tools.- Debug events are emitted as JSONL rows with stable categories such as timeline, prediction, interpolation, input, sync, messages, entities, transport, components, and manual events.
- Components can be sampled with typed debug or structured JSON formatters by adding
LightyearDebug. - Structured debug coverage now spans more of the runtime path, including sync/ping, input buffering, prediction rollback, visual correction, frame interpolation, and replication tick advancement. This is intended to make desync investigations easier to automate and easier to inspect with LLM-assisted analysis.
-
Switched
Tickfromu16tou32.- This avoids practical tick wraparound during normal game sessions and removes a lot of complicated sync/rollback edge cases.
- Replicon's replication tick is now treated as a transport/checkpoint index and mapped back to Lightyear's authoritative simulation
Tick.
-
Improved component registration API.
- The preferred component registration API is now
app.component::<C>()
.replicate()
.predict()
.with_rollback_condition(f)
.add_interpolation_with(f);
The main benefits is that you can register a component with lightyear, and then use any replicon function for the replication logic, e.g. app.component::<C>().replicate_once()
Also now you can only call prediction related functions (with_rollback_condition, etc.) after having called predict().
Enabling rollback without replication has now been renamed to local_rollback instead of add_rollback.
Migration notes
-
Naming between Lightyear and Replicon does not line up one-to-one.
- Replicon's native send-side marker is
Replicated, and received entities useRemote. - Lightyear keeps
Replicateas the user-facing send-side component. Code that previously queried Lightyear receive-side replication markers may need to move to Replicon'sRemote,ConfirmHistory, or the Lightyear compatibility exports depending on intent.
- Replicon's native send-side marker is
-
The visibility API now uses Replicon's visibility filters under the hood.
- Room-based visibility changed significantly: use
RoomAllocatorto allocate globalRoomIds and addRoomsto entities/clients. - Room ids are no longer ad-hoc local values; they must be allocated globally so Replicon's filter bitsets can reason about them consistently.
- Room-based visibility changed significantly: use
-
bevy_enhanced_inputintegration no longer relies on general client-to-server entity replication for action entities.- Action entities are now expected to use the pre-spawning flow and must be spawned on both the client and server.
- Input action replication now serializes the action context through
NetworkActionOfand handles rebroadcasted action entities explicitly.
Added
-
Added deterministic-replication late-join catch-up support.
- New clients can join a running deterministic game without replaying the full historical input log. The catch-up flow combines deterministic input replication with a one-time state snapshot gated through Replicon visibility filters. After receiveing the state snapshot, the new client can just simulate the rest of the game in a deterministic fashion with just input replication.
-
Added optional LZ4 transport packet compression.
- Compression keeps packet headers uncompressed, validates decompressed payload limits, respects MTU constraints, and includes benchmark coverage.
Prediction and interpolation
-
Prediction and interpolation now use Replicon's confirmation and mutation-checkpoint data to reason about entities/components that did not change. This avoids false predictions where an entity was assumed to have changed simply because no correction arrived.
-
Interpolation is more robust under packet loss and visibility changes.
- Interpolated entities now delay despawn until the interpolation timeline reaches the authoritative server despawn tick.
-
Fixed several rollback and confirmed-history edge cases.
- Prediction can now handle predicted entities whose latest confirmed states are not all at the same tick.
Transport and networking fixes
- Timeline sync is more stable under very low latency, keeping the client timeline at least one tick ahead of the remote/server timeline so deterministic inputs can arrive before the server simulates the target tick.
Release 0.26.0
Release 0.26.0
Mainly an update to match bevy's 0.18 release.
Breaking changes
LocalTimelineis now a resource; since there can only be one per App (because it modifies bevy'sTimeresource)NetworkVisibilityy::gain_visibilityandNetworkVisibility::lose_visibilitymethods have now been added on the ReplicationState and removed from the NetworkVisibility component. NetworkVisibility is now a marker component.
What's Changed
- Feat: Fix toml error double bevy_state import + fix lints by @Sirmadeira in #1309
- Feat: Fix spawmy log by @Sirmadeira in #1313
- Feat: Taplo format could add CI if approved by @Sirmadeira in #1310
- chore(deps): update ron requirement from 0.10 to 0.12 by @dependabot[bot] in #1308
- Feat: Readition of lightyear serde features by @Sirmadeira in #1312
- chore: add docs for ServerMultiMessageSender by @cBournhonesque in #1315
- fix: fix incorrect handling of message acks by @cBournhonesque in #1316
- fix: fix assertion message by @YichiZhang0613 in #1317
- Fix: Fragment ACKs Not Being Processed, Causing Excessive Retransmissions by @OlivierCoue in #1318
- fix: keep InputTimeline in sync with LocalTimeline by @Avilad in #1320
- chore: fix ci by @cBournhonesque in #1321
- fix: update Position of child colliders correctly for avian by @cBournhonesque in #1323
- chore: add test for AvianMode::Transform by @cBournhonesque in #1324
- feat: used 16.16 fixed-point types for tick math by @Avilad in #1322
- chore: fix docs for DisableReplicateHierarchy by @cBournhonesque in #1326
- chore(deps): bump actions/checkout from 4 to 6 by @dependabot[bot] in #1327
- feat: introduce a default ReplicationGroup by @cBournhonesque in #1330
- fix: always do a full resync on the first resync attempt by @cBournhonesque in #1333
- feat: separate InputTimelineConfig from InputTimeline by @cBournhonesque in #1334
- feat(websocket): add configurable URL scheme for WebSocket client by @kualta in #1335
- Refactor lightyear_metrics and add benchmark for allocations by @cBournhonesque in #1337
- feat: improve authority transfer behavior by @cBournhonesque in #1338
- fix: fix distributed authority example by @cBournhonesque in #1339
- chore(deps): update criterion requirement from 0.6 to 0.8 by @dependabot[bot] in #1340
- fix: make sure that BEI rebroadcast even in the user has a non-starte… by @cBournhonesque in #1329
- chore: add test for replicating component removes without replicating by @cBournhonesque in #1341
- chore: add bin in lightyear_test for profiling by @cBournhonesque in #1343
- Expose NetworkVisibility::is_visible by @rherv in #1344
- Optimize replication logic by avoiding copies of the same data by @cBournhonesque in #1345
- Add justfile for profiling commands by @cBournhonesque in #1349
- fix(replication): use correct mode in handle_connection to set per-sender predicted/interpolated by @rherv in #1358
- feat: make LocalTimeline a resource by @cBournhonesque in #1359
- fix tests and cargo fmt by @cBournhonesque in #1360
- fix: lightyear_metrics optional dep for std feature by @philpax in #1362
- feat(replication): add ReplicationState::is_visible helper by @rherv in #1366
- fix(reflect): add ReflectDefault to PreSpawned for scene deserialization by @kualta in #1367
- remove f32 bound on avian by @cBournhonesque in #1371
- feat(websocket): add target_url support for hostname connections by @kualta in #1370
- Fix common example code causing compilation error (websockets conf) by @ThierryBerger in #1374
- Add reason to disconnected status by @ThierryBerger in #1377
- chore: upgrade to bevy 0.18 by @cBournhonesque in #1386
- chore: release lightyear 0.26 by @cBournhonesque in #1387
New Contributors
- @YichiZhang0613 made their first contribution in #1317
- @rherv made their first contribution in #1344
- @ThierryBerger made their first contribution in #1374
Full Changelog: 0.25.5...0.26.0
Release 0.25.0
This version now targets bevy 0.17!
Merge Predicted, Confirmed and Interpolated
This is a big change. In the past you would have 2 separate entities:
- a Confirmed entity that contains the remote state received via replication
- a Predicted entity that lives in the future compared to the remote and contains the predicted state of the entity, or an Interpolated entity that lives in the past and contains the interpolated state (interpolated between 2 consecutive Confirmed states)
This model had a lot of flexibility since the entities were separate, but it also added a lot of extra complexity:
- non predicted components had to be synced from the Confirmed to the Predicted entity
- we had to apply entity-mapping from the Confirmed to the Predicted entity
- sending a message from the client to the server about a Predicted entity was tedious, as you had to map from Predicted entity to Confirmed entity first
In this version I'm opting to greatly simplify the logic by merging all these entities into one.
If an entity is Predicted/Interpolated, then the marker component will be added, no additional entity will be spawned.
For Predicted/Interpolated components, the replication updates are now inserted as Confirmed<C> instead of C. Other components are inserted directly as C.
This also removes the need to specify a PredictionMode or InterpolationMode, which have been removed. InterpolationManager has also been removed.
PreSpawned can now be used outside of Prediction
There are situations where you might want to replicate an entity from the server to client, but instead of spawning a new entity on the client you want to target an existing entity. We already had a solution for this, the PreSpawned component, but it was only compatibly with Predicted entities.
It now works for all replicated entities.
The PrePredicted component is now removed as it was complicated to reason about, and I believe that all use-cases can be filled by the PreSpawned component.
DeltaCompression Changes
- DeltaCompression must be added AFTER prediction/interpolation in the Protocol for it to work properly. This is probably a temporary restriction that will be lifted in the future.
- The
Diffabletrait now hasDeltaas a generic type instead of an associated trait. This enables implementing Diffable for a type with multiple choices ofDelta. This can be useful for example forTransform, which implements bothDiffable<Isometry2d>andDiffable<Isometry3d>
Avian-specific Changes
Lightyear now supports 3 modes to work with Avian:
-
Position: Position is replicated and is used for FrameInterpolation and Correction. In your FixedUdpate systems you must update Position and not Transform. -
PositionButInterpolateTransform: Position is replicated, but Transform is used for FrameInterpolation/Correction (this can be convenient since FrameInterpolation/Correction are mostly visual concerns). In your FixedUdpate systems you must update Position and not Transform. Note that there might still be issues with TransformPropagation to children. -
Transform: Transform is replicated and is used for FrameInterpolation and Correction. In your FixedUdpate systems you can update either Position or Transform.
The LightyearAvianPlugin must now be added manually to your app, it's not added automatically when the avian feature is enabled.
The IslandsPlugin and IslandSleepingPlugin need to be disabled if using input-based replication as they cause issues with rollback.
BevyEnhancedInput
- Rebroadcasting BEI inputs now works correctly in HostServer mode
- The serialization model has been reworked to consume a LOT less bandwidth (up to 6-7X less)
DeterministicPredicted
A new field skip_despawn has been added to DeterministicPredicted.
By default it's false, and any rollback where the entity was spawned after the start of the rollback will cause the entity to be despawned. This is usually what you want if the entity was spawned as part of some input (for example a projectile that is fired when a button is pressed). But in some cases you don't want the entity to be despawned, for example if the entity was replicated by the server as a one-time event. In those cases you can set skip_despawn=True.
Projectiles example
A new example has been added to showcase different ways of handling projectile replication.
It is by far the most complex example to date and shows how to handle concepts like:
- entity visibility via rooms
- physics handling with avian
- multiple replication models (deterministic-only, interpolated-only, all-predicted, etc.)
- bevy_enhanced_input with input rebroadcasting for predicting other clients
- lag compensation via the LagCompensationSpatialQuery
What's Changed
- Fix steam callback system by @ironpeak in #1212
- fix(transport): fix rare bug when ack metadata causes a panic on wrapped PacketId by @cBournhonesque in #1214
- feature: adding UI panel with debug information by @cBournhonesque in #1216
- feature: simplify interpolation by @cBournhonesque in #1217
- Projectile example: various fixes + add shooting bullets by @cBournhonesque in #1218
- fix: remove disconnected clients from replication components by @cBournhonesque in #1221
- fix: make bot shoot as well by @cBournhonesque in #1222
- fix: don't import websocket server prelude in wasm by @AdamWhitehurst in #1230
- feature: merge Predicted/Interpolated entities with Confirmed entity by @cBournhonesque in #1229
New Contributors
- @ironpeak made their first contribution in #1212
- @AdamWhitehurst made their first contribution in #1230
Full Changelog: 0.24.0...0.25.0
Release 0.24.0
0.24.0
Smaller release focused on bug fixes.
- WebSocket support with
lightyear_websocket - Enables input rebroadcast for
lightyear_inputs_bei - The
ClientandClientOfentities are now mapped on the client and server - Some important bug fixes, especially related to input handling
What's Changed
- Enable replay protection only after Connection by @cBournhonesque in #1164
- skip spawning predicted/interpolated entities if not connected by @cBournhonesque in #1165
- feature: add websocket support by @cBournhonesque in #1167
- Feature: enable customizing input rebroadcasting target by @cBournhonesque in #1169
- feature: entity map client<>client_of by @cBournhonesque in #1175
- Fix link typo to ServerPlugins in setup.md by @Soupborsh in #1177
- chore(deps): bump actions/upload-pages-artifact from 3 to 4 by @dependabot[bot] in #1180
- fix: ClientPlugins and ServerPlugins defaults by @erictuvesson in #1187
- fix: export server wasm types by @gak in #1190
- Projectile demo + improvements by @cBournhonesque in #1183
- fix: propagate ReplicateLike down hierarchy by @TheNeikos in #1203
- Simplify input system in avian 3d example by @cBournhonesque in #1204
- Improve projectile demo by @cBournhonesque in #1209
- Release 0.24 by @cBournhonesque in #1210
New Contributors
- @Soupborsh made their first contribution in #1177
- @erictuvesson made their first contribution in #1187
- @gak made their first contribution in #1190
- @TheNeikos made their first contribution in #1203
Full Changelog: 0.23.0...0.24.0
Release 0.23.0
Release 0.23.0
Support for bevy_enhanced_input 0.16.0
BEI did a full revamp of their API where actions and bindinds are now separate entities, which are tied to Context entities via relationships. This release now supports the new API. I haven't tested this in all scenarios so please let me know if you encounter issues.
Deterministic replication works in host-client mode
DR still had a couple of caveats, noteably that it did not work in host-client mode. This should now be fixed; you can try running the deterministic_replication example in host-client mode!
What's Changed
- Fix
InputPluginoflightyear_inputs_beitoFixedPreUpdateby @hukasu in #1121 - fix(registration): do not panic if type was already registered in typ… by @cBournhonesque in #1130
- fix host-client mode message required components by @komodo472 in #1135
- Reformat setup.md by @esuriru in #1137
- Fix deterministic example host-client by @cBournhonesque in #1138
- Remove noisy logs by @magic-sysrq in #1140
- Update
rust-versionto 1.88 by @esuriru in #1143 - fix(steam): prevent Steam LinkOfs from using Netcode by @cBournhonesque in #1144
- chore(test): add test for sending message from server to host-client by @cBournhonesque in #1147
- chore(test): add test for sending message from server to host-client by @cBournhonesque in #1149
- chore: clippy for rust 1.89 by @cBournhonesque in #1150
- Feat: Fix unchecked unwrap on lag compensation by @Sirmadeira in #1152
- fix: fix issues with deterministic replication example by @cBournhonesque in #1153
- fix host-client message registration by @komodo472 in #1145
- Upgrade to latest bevy_enhanced_input by @cBournhonesque in #1151
- chore(deps): bump actions/download-artifact from 4 to 5 by @dependabot[bot] in #1154
- release 0.23.0 by @cBournhonesque in #1157
New Contributors
- @esuriru made their first contribution in #1137
- @magic-sysrq made their first contribution in #1140
Full Changelog: 0.22.5...0.23.0
Release 0.22.0
Release 0.22.0
Upgraded rollback detection
Previously there was only one way we could rollback: if we received a new state update on the Confirmed entity which did not match the prediction history.
Now we have separate a RollbackMode for state replication and input replication; the possible values are Always (rollback whenever we get a new state or input), Check (rollback whenever we get a new state that doesn't match the predicted history, or a new input that doesn't match the input history), Never.
The Always mode can be useful to force trigger rollbacks and to check if your game can handle a certain amount of rollbacks.
But the main benefit is that this change allows for deterministic replication; a networking model where only inputs are replicated (instead of state).
Deterministic Replication
Lightyear now supports deterministic replication! In this mode you can choose to replicate only inputs; each client should receive the inputs from all other clients and use that to deterministically simulate the game.
The API is extremely similar to state replication: instead of Predicted you need to add the DeterministicPredicted component!
By adjusting the InputDelayConfig you can decide the level of prediction that is applied:
InputDelayConfig::no_prediction: there is no prediction so the clients will progress in lockstep. NOTE: there currently is no check that for a given tick we have received all the inputs from all clients!- with input delay: the local client will predict all other clients' actions
A new example has been added that illustrates those 2 modes.
Note that you can also do a mix between state replication and input replication!
Rollback overhaul + bug fixes
The rollback logic has been overhauled, in particular the logic that was handling the smooth correction from the previously predicted value to the new corrected value. Thanks to the help of @maniwani for that!
I've also fixed a big number of subtle bugs related to rollback, timeline syncs and input buffers.
The result is that rollbacks are now MUCH smoother, as you can check yourself by running the deterministic_replication, avian_physics or avian_3d_character examples. This is a huge upgrade that I am extremely happy about.
Support for immutable components / deprecation of RelationshipSync
It is now possible to use immutable components in the protocol; you will have to use add_immutable_prediction instead of add_prediction, and they are not compatible with PredictionMode::Full.
With this change, the RelationshipSync plugins are not needed anymore.
To replicate Relationships you can just add them directly to your protocol, like so: app.register_component::<R>()
What's Changed
- fix(avian): improve avian plugin system ordering by @cBournhonesque in #1066
- fix(bug): fix important bug on input broadcast by @cBournhonesque in #1067
- Make sure that remote inputs are correct during rollbacks by @cBournhonesque in #1069
- Crossbeam clone by @SueHeir in #1070
- remove direct steamworks dependency by @extrawurst in #1073
- chore(docs): explain handle_predicted_spawn more by @interwhy in #1076
- skip crossbeam links in ServerUdpIO by @SueHeir in #1078
- feat(rollback): add more conditions are rollback checks by @cBournhonesque in #1079
- Fix RemoteTimeline syncing by @Avilad in #1085
- Add deterministic replication example by @cBournhonesque in #1087
- fix(rollback): More rollback fixes for deterministic replication by @cBournhonesque in #1089
- feat(rollback): Add RollbackMode::Check for inputs by @cBournhonesque in #1091
- Remove symlinking in favor of lib paths. by @BigBadE in #1090
- feat(deterministic): Add lightyear_deterministic_replication by @cBournhonesque in #1092
- fix(lockstep): fixes for lockstep by @cBournhonesque in #1099
- chore(docs): improve docs for ShouldRollbackFn by @cBournhonesque in #1104
- chore(inputs): simplify native inputs by @cBournhonesque in #1107
- fix(avian): update avian examples by @cBournhonesque in #1112
- fix(example): fix system ordering in avian 3d example by @cBournhonesque in #1114
- Support immutable components in prediction/interpolation by @cBournhonesque in #1105
New Contributors
- @extrawurst made their first contribution in #1073
- @interwhy made their first contribution in #1076
- @Avilad made their first contribution in #1085
Full Changelog: 0.21.0...0.22.0
Release 0.21.0
Lightyear 0.21.0
Multiple crates
This is a massive release that is basically a full rewrite of the crate. In particular, the crate has been split into multiple subcrates, for the following reasons:
- faster compile times
- more modularity by splitting up small pieces into different crates
- encourages a better separation of concerns
This was definitely challenging but I think it's for the better.
Similarly to bevy, you will have lightyear_* subcrates and a main lightyear crate which imports every subcrate.
It is possible to use the subcrates directly if you want, as the main crate just imports the relevant plugins from the subcrates.
From Resources to Entities
Instead of managing the networking behaviour via a set of Resources (ClientConfig, ClientConnectionManager, etc.), we now switch to a model where you have one entity per network connection between the local peer and the remote peer. (this approach was pioneered by
aeronet)
-
at the transport layer (how to send/remove bytes):
- you can add one of the IO components (UdpIO, WebTransportIO, etc.) to specify how you will send or receive bytes from the remote peer
- the IO-agnostic
Linkcomponent is used to abstract away from the actual io - you can access metadata about the io link using the
LocalAddrandPeerAddrcomponents - the state of the link can be accessed via various marker components (
Linked,Unlinked) - the
Transportcomponent can be added to specify various channels that provide different reliability and ordering guarantees - the
MessageSender<M>andMessageReceiver<M>components can be added to add the ability to send and receive structs instead of raw bytes. TheMessageManageris responsible for handling the serialization and the entity mapping
-
at the connection layer (how to establish a persistent connection on top of the raw io):
- you can add the
ClientorServercomponent to specify the role of the entity. In particular, theServercomponent will listen for incoming connections and spawn newClientOfentities for each connected client - the state of the connection can be accessed via various marker components (
Connected,Disconnected,Started,Stopped) - the
NetcodeClientorNetcodeServercomponents can be added to use netcode as the layer providing authentication + persistent IDs on top of the raw IO
- you can add the
-
at the syncing layer (how to make sure that the local and remote peers are in sync)
- there is now a new
Timelinecomponent that is used to represent the various networking timelines:- the
LocalTimelinesimply increments the tick whenever the FixedUpdate schedule runs - the
RemoteTimelineis the local peer's best estimate of the remote peer's timeline; computed from the received packets. This is used to make sure that all the timelines remain in sync with the remote peer - the
InterpolationTimelineis in the past compared to the Remote, and is used to define how to interpolate between the received remote packets - the
InputTimelineis in the future to the Remote, and is used to define when ticks should be buffered
- the
- there is now a new
-
at the input layer (how do the clients send inputs to the server)
- the ActionState component defines the current state of the inputs at the current tick. It is guaranteed to be the same between client and server for a given tick
- the InputBuffer component can be used to access the received inputs that were locally buffered
- lightyear now has a shared generic inputs plugin that makes sure input-handling is correct with rollback, input_delay, rebroadcasting to other clients, etc. There are 3 implementations: native, leafwing, and bevy-enhanced-input
-
at the replication layer (how to replicate the state of the World to the remote)
- you can add the
ReplicationSenderandReplicationReceivercomponents to specify if you want the peer to be part of replication - the
PredictionManagerandInterpolationManagermark a link entity as having prediction/interpolation enabled
- you can add the
The bevy ECS provides a lot of flexibility: you can add or remove these components from a subset of the peers to change their networking configuration. Usually if you make changes, they will be applied on the next connection attempt.
This change was mostly made possibly with the recent ECS innovations: observers and relationships.
Moving away from Client-Server
Lightyear was strongly tied to the concepts of client and server. You had to use some specialized client or server resources (ClientManager and ServerManager) and those would have very similar code that varied in subtle ways. For example the replication code was essentially duplicated between them, apart from some server-specific code.
With the new component-based approach, the code has become fairly agnostic to the roles of client and servers.
For example there is now a unique replication system that works on any entity that has a ReplicationSender component, so each local peer can replicate to a remote peer in the exact same way!
I think this is super exciting as this opens the door to handling P2P topology with code that is very similar to client-server topologies!
Total parity?
This refactor of the crate doesn't achieve total parity with the previous version. Some of the functionality that hasn't been ported is:
- Resource replication: I believe bevy is moving towards resource-as-entities, where resources are simply components stored on an entity. I will just be waiting for this to be merged before re-adding resource replication
- input broadcasting: we previously had convenience functions so that a client could send a message to another client (the message would be sent to the server, who would broadcast it to the correct client). This hasn't been re-added yet
- authority: the handling of authority is still somewhat in flux, I have made steps towards it but it hasn't been properly tested, and the
distributed_authorityexample is still outdated.
Extras
-
Instead of maintaining my own steam, webtransport, websocket io layers, I now defer to the excellent aeronet crate!
-
Special thanks to @hukasu for helping me improve the compile time of lightyear!
Next steps
With the increased flexibility and modularity, I would like to tackle:
- handling networking modes where only inputs are replicated, such as deterministic lockstep! I believe this should now be very much within reach, as it mostly involves adding a
LockstepTimelineto define how the local and remote peers stay in sync - handling P2P topologies
What's Changed
- Bevy main refactor by @cBournhonesque in #989
- Update criterion requirement from 0.5 to 0.6 by @dependabot in #1013
- Fixed Client and server Working in Launcher Example by @JosephLassuy in #1014
- Allow replicating immutable components by @cBournhonesque in #1015
- Release 0.21 rc1 by @cBournhonesque in #1017
- Separate Connected from LocalId/RemoteId by @cBournhonesque in #1018
- Add steam using aeronet by @cBournhonesque in #1019
- Add rollback tolerance to avian examples by @cBournhonesque in #1020
- Fix lobby example (without HostServer) and add protocolhash by @cBournhonesque in #1021
- fix(prediction): add history-buffer for pre-existing components when … by @cBournhonesque in #1022
- Add HostServer by @cBournhonesque in #1023
- chore(example): enable host-client mode on simple box by @cBournhonesque in #1024
- chore(replication): add simple test to check that replicate can be re… by @cBournhonesque in #1026
- fix(host-server): fix overlapping ui nodes breaking disconnect button by @komodo472 in #1030
- fix(host): fix host-server disconnect by @komodo472 in #1031
- chore(host-server): enable host-server for all examples by @cBournhonesque in #1029
- Add #[reflect(MapEntities)] to RelationshipSync by @BigBadE in #1033
- Adds #[reflect(Component)] to Replicate. by @BigBadE in #1038
- feat(BEI): support BEI inputs by @cBournhonesque in #1039
- bug(inputs): fix inputs by @cBournhonesque in #1040
- Make workspace crates depend on individual bevy crates by @hukasu in #1043
- fix(bug): fix bug on fps example with missing PlayerMarker component by @cBournhonesque in #1047
- Updates notes for SinceLastSend and DeltaCompression by @cBournhonesque in #1048
- Alternative replication system + fix delta-compression by @cBournhonesque in #1049
- chore(tests): add tests for delta-compression by @cBournhonesque in #1051
- chore(docs) by @cBournhonesque in https://githu...
0.20.2
- Upgrade avian to 0.3
- Fix wasm support
What's Changed
- Fix inserting ChildOfSync in host-server mode by @cBournhonesque in #1004
- Make Relationship plugins public by @cBournhonesque in #1006
- make AppTriggerExt pub by @cBournhonesque in #1007
- Fix wasm for 0.20.0 by @cBournhonesque in #1003
- Fix examples and upgrade avian by @cBournhonesque in #1008
- upgrade to 0.20.2 by @cBournhonesque in #1009
Full Changelog: 0.20.1...0.20.2