Releases: cBournhonesque/lightyear
Release 0.8.0
Added
WebTransport with WASM support!
I am super excited for this: thanks to @Nul-led and @MOZGIII's help I was able the wasm webtransport working!
This means that lightyear
is now available in the browser.
See here for an example running in WASM along with instructions.
PreSpawning entities
lightyear
already had some support for prespawning entities on the Predicted timeline, but it was fairly awkward.
I'm introducing an easier way to do this, where the server and client can use the exact same system to spawn the entities. The only thing you need to do is add the component PreSpawnedPlayerObject
on the entity, on the client and the server.
When the client receives a server entity, it will first check if that entity corresponds to one of its prespawned entities by using a hash of the Archetype and the spawn tick of the entity.
See here for an example of how to use prespawning.
You can also read more about it in the book.
Release 0.7.0
Added
-
Split the
Client
andServer
monolithic resources into a bunch of smaller independent resources to remove coupling and improve parallelism. The new resources are:- ConnectionManager: the main resource to use to send inputs, send messages and handle replication in general.
- Io: for sending/receiving raw packets
- Protocol: to access the list of channels/components/messages/inputs that make up the protocol
- Config: access the lightyear config
- Netcode: abstraction of a network connection on top of the raw io
- Events: a resource that handles creating Bevy events for network-related events
- TimeManager: keeps track of the time and when we should send network packets
- TickManager: keeps track of the internal tick (which is incremented on each FixedUpdate run)
I am still planning on further breaking up the remaining
ConnectionManager
into different parts:-
InputManager to handle inputs
-
SyncManager for handling time-syncing between client and server
-
maybe having a different resource per channel so that you can send messages to different channels in parallel
-
Added diagnostics to print the incoming/outgoing bandwidth that is being used
You can use enable the diagnostics like so:
app.add_plugins(LogDiagnosticsPlugin {
filter: Some(vec![
IoDiagnosticsPlugin::BYTES_IN,
IoDiagnosticsPlugin::BYTES_OUT,
]),
..default()
});
I am planning on adding more diagnostics in the future.
Fixed
-
I fixed a LOT of bugs related to long-term connections after the
Tick
wrapped-around (after around 15 minutes); all features (input handling, replication, time-syncing) should now work correctly even for long-term sessions, up to 46 days. -
Fixed an issue where
leafwing
ActionState
s could not be replicated correctly from client->server in some edge-cases. Now inputs are networked correctly whether the controlled Entity isControlled
,Predicted
orPre-Predicted
.
Migration
- You will need to update your systems; the old 'monolithic' resources are still available as the SystemParams
Client/ClientMut
andServer/ServerMut
. Otherwise you will mostly need to use the resourcesClientConnectionManager
andServerConnectionManager
.
Release 0.6.1
- Add support for bevy_xpbd 0.3.3
- Fixes some issues (overflow) for
WrappedTime
- Removed some
info!
andwarn!
logs in client sync
Release 0.6.0
Summary
This release introduces the following changes:
- added an integration with leafwing_input_manager! This provides a more flexible way of handling inputs, and is now the recommended way. (gated behing the
leafwing
feature) - added an integration with bevy_xpbd_2d! Some components of
bevy_xpbd_2d
now implementMessage
and can be used directly in your replication protocol. (gated behind thexpbd_2d
feature) - added the possibility to add some input delay. This lets you have a trade-off between mispredictions but responsive inputs (if the input delay is 0) or no mispredictions but delayed inputs (if the input delay is greater than the RTT)
- added the possibility to specify how to handle mispredictions: the default behaviour is to immediately snap to the Corrected state, but it is now possible to visually interpolate between the current state and the corrected state over a few ticks
- added the possibility to specify custom replication characterics per component for a given entity. The current options are:
- whether to replicate this component or not
- whether to replicate this component only once (but not send further updates)
- whether to replicate this component only to a subset of Clients
- examples have been updated so that they work on real connections. I have tested all examples on a connection with a remote headless server with both UDP and WebTransport!
- Added an example showcasing most new features:
- uses
leafwing_input_manager
to handle inputs - replicates physics via
bevy_xpbd_2d
- each client predicts every entity, and uses variable
input_delay
andcorrection_ticks
to improve the handling of mispredictions - each client controls several entities using different keys
- uses
- Various bug fixes
Changelog
Added
Handling inputs via leafwing_input_manager
(feature leafwing
)
The current input_handling is quite crude and has several limitations:
- the user can only provide a single enum of possible actions
- the user must handle inputs in the FixedUpdate::Main system set
This release adds special support for networking inputs via leafwing_input_manager
, so that we can get all the benefits from that crate (handling chords, having multiple entities controlled by different keys).
IMPORTANT:
- I am not sure that all features are currently working with the current ("native") input handler (for example input delay). I might remove it in the future to only support leafwing_input_manager
- It is currently not possible to replicate inputs using a global
ActionState
resource. Only inputs attached to entities are supported - Inputs that are attached to pre-Predicted entities or Confirmed entities are currently supported, but not for Predicted entities.
Bevy xpbd 2d integration (feature xpbd_2d
)
It can be hard currently to directly use external components in your replication protocol, as every component must implement such custom lightyear traits such as Message
or MapEntities
.
I wanted to create a demo of a physics-based networked simulation so I manually implemented some good defaults for some components of bevy_xpbd_2d
, so that you can use them directly in your replication protocol.
Input delay
Lightyear now supports adding some input delay: i.e. the user presses a button on tick 10 but their character only moves on tick 15.
This lets you choose a good trade-off between having a less responsive game (more input delay) vs a very responsive game but where there is more mispredictions because the client is far in the future (low input delay).
At the extreme where the input delay is higher than the RTT, the game won't have any mispredictions as both the client and server will have access to all inputs.
See more information here: https://www.snapnet.dev/docs/core-concepts/input-delay-vs-rollback/
Prediction correction
This release adds the option to visually interpolate between the current position and the corrected position when there is a misprediction rollback. This is the technique used in RocketLeague to ensure that the game still runs smoothly even when all clients are being predicted.
The two default behaviours are:
InstantCorrector
(no correction, we just perform rollback as usual)InterpolatedCorrector
(we re-use the interpolation logic for the correction; which will happen over a few ticks)
Note that the correction still has some jittery artifacts, but that's something that I will be working on in the future.
Enable custom replication logic for specific components of an Entity
It is now possible to have some replication custom logic per component of an entity.
For instance, by default only components present in the ComponentProtocol
get replicated; but there could be situations where we don't want to replicate a component of an entity even it is present in the ComponentProtocol
.
This can be achieved simply by calling the following methods on the Replicate
component (which defines how this entity gets replicated):
replicate.disable_component<Component1>()
to disable replicationreplicate.enable_component<Component1>()
to enable replication.
(by default, all components are enabled)
Another example is that we might want to replicate the ActionState
of an entity to all players except for the player who controls the entity (since they are the ones who are updating the ActionState
with their inputs). This can be done easily by calling:
replicate.add_target::<ActionState>(NetworkTarget::AllExceptSingle(client_id));
Benchmarks
I used divan
to add CPU/memory benchmarks for replicating many entities to one client, or a few entities to many clients.
I still have to think about how to best interpret the results from the benchmarks. For example I suspect that I'm doing unnecessary clones:
- during serialization/deserialization in the packet/message layer
- when replicating a single object to many different clients
Ideally the benchmarks should reflect this but I haven't been able to use them for this yet.
Improved examples
I have cleaned-up the examples and tested them all in a real connection with a remote server.
The server now has the option to run in headless
mode, i.e. with all rendering-related plugins disabled. Only Bevy's MinimalPlugins
are enabled, so that only the simulation is running.
All examples are working with both UDP and WebTransport!
QOL improvements
-
We are now exporting the type aliases:
type Client = Client<MyProtocol> type Server = Server<MyProtocol> type Replicate = Replicate<MyProtocol>
for convenience, so that we don't have to keep mentioning the protocol as generic parameter.
-
Added the
AllExceptSingle
andSingle
NetworkTarget
s -
The trait
Message
is now automatically implemented. You just need to implement theMapEntities
andNamed
traits.
Fixed
-
Fixed an issue where prediction time/interpolation time were not being computed properly when the client started, which would cause the
Predicted
/Interpolated
entities to appear frozen -
PredictionHistory
was being added even to entities that were not beingPredicted
. This was consuming additional CPU/memory for no reason, and has been fixed -
Removed
warn
loggings being emitted by mistake forpre-Predicted
entities -
We are no longer getting a panic when we try to despawn the entities of a client who has disconnected
-
Fixed an issue where if two entities were in the same ReplicationGroup and one of them rolled back, the other would not roll back as well. Now it is guaranteed that all components of all predicted entities will rollback if there is a mispredictions on any of them.
-
Fixed some issues with rollback (including an off-by-1 error in the rollback logic)
-
Fixed some issues with replication (we now compute the bevy ChangeTick correctly)
Migration
- Use
Client
instead ofClient<Protocol>
,Replicate
instead ofReplicate<Protocol>
UserInput
has been renamed toUserAction
- Components don't implement
SyncComponent
directly anymore (because this was preventing users from directly using components from external crates due to the orphan rule), instead theComponentProtocol
implementsSyncMetadata<C>
for each component. Use this to access themode
,interpolator
,corrector
of eachComponent
Release 0.5.0
Summary
This release introduces the following changes:
- client-side replication: entities/components spawned can now be replicated from the client to the server and to other clients!
- entity pre-spawning: it is now possible to spawn Pre-Predicted entities on the client! This means that you can spawn entities on the client timeline (i.e without any delay); as soon as you get the corresponding server entities, the pre-spawned predicted entity will update to using the server's entity as 'source of truth' for rollback
- tested most edge-cases of client-prediction (rolling back a despawned entity, rolling back a removed component); most of them are now supported correctly!
Changelog
Added
Message broadcasting
You can know send messages from client to other clients. Previously we could only send messages between a client and the server. I added the method client.send_message_to_target
which allows a client to send a message to any other client (by routing via the server)
Client-side prediction
It is now possible to have replication from the client to the server!
Note that currently I do not provide any particular helper to broadcast an entity from a client to other clients; the only way to do that is to:
- replicate an entity from the client to the server
- add the Replicate component to the server entity, to specify how it will be replicated to other clients
I might add some more ergonomic way to achieve this in the future (i.e specifying directly on the client how we want the entity to be replicated to other clients).
Pre-spawning Predicted entities on the Client
Currently, the only way to spawn Predicted entities (i.e. responsive and on the client's timeline) was to spawn them on the server and wait for them to be replicated. This would introduce a delay if the entity was spawned as part of a client action. We would have to wait for:
- the client's action to be sent to the server
- then for the server to spawn an entity
- and then for that entity to be replicated to the client
to be able to spawn a Predicted entity on the client, which is at a minimum a full RTT of delay.
Instead, we can now spawn a pre-predicted entity on the client. Here is an example:
- a client spawns a PrePredicted entity C1 by adding the component
ShouldBePredicted { client_entity }
- the client replicates that entity C1 to the server, which creates an entity S1
- the server replicates the entity S1 back to the client, which creates a Confirmed entity C2 and re-uses the existing entity C1 as its Predicted entity
Correctly handling replication edge-cases
There are a lot of potential edge-cases with client-prediction, especially with the addition of spawning Pre-Predicted entities.
I have listed most of them in that link; for instance:
- despawning a predicted entity, but the server doesn't despawn -> need to rollback the Predicted despawn
- despawning a predicted entity, and the server accepts the despawn -> we want the server's replicated entity to be immediately despawned
- despawning the server entity -> need to replicate the despawn to Predicted entities
- ...
etc.
I have tested most them; almost all situations are supported correctly!
The only one that has some caveats is rolling back a predicted despawn; this only happens if we receive a server update for that entity after the despawn.
Client replication example
Added a new example client_replication
that show-cases the newly-added features:
- pressing M will send a message from a client to other clients
- performs client-authoritative replication by replicating the user's cursor to other clients
- pressing Space will spawn a Pre-Predicted entity. The entity is spawned immediately, and then becomes the Predicted version of a Confirmed entity once it gets replicated
- pressing Delete will delete the Predicted entity. You can use this to confirm the behavior of rollback in various edge-cases.
Updated
- Renamed the send_message methods from
buffer_send
->send_message
andsend_to_target
->send_message_to_target
for clarity.
Migration guide 0.4.0 -> 0.5.0
server.send_to_target
has been renamed toserver.send_message_to_target
server.buffer_send
has been renamed toserver.send_message
client.buffer_send
has been renamed toclient.send_message
- Adding
Replicate
on the server to re-replicate client entities to other clients must be done in the newly addedMainSet::ClientReplication
SystemSet
- Correctly despawning
Predicted
entities requires to useEntityCommands::prediction_despawn
instead ofEntityCommands::despawn
Release 0.4.0
Summary
This release:
- enables the replication of Messages and Components that contain Entities
- greatly improves the stability of client-prediction and snapshot-interpolation
- adds a more involved example showing how to use custom interpolation logic, and components that contain Entities
Changelog
Added
- Added an example
ReplicationGroups
that showcases:- the concept of
ReplicationGroups
: where multiple entities are guaranteed to be replicated in a single message, so will always be in sync with each other - how replicating components that refer to other entities works (via the
MapEntities
trait) - how to execute a complex custom snapshot interpolation logic: the interpolation is not a simple linear interpolation. The interpolated will stay on the "path" of the snake, and never do any diagonal movement
- showcases how prediction/interpolation performs with bad connections; the example default settings are with 400ms RTT, 40ms jitter and 10% packet loss
- the concept of
Updated
-
Handle entity mappings more rigorourously now! By
Entity Mapping
I mean Messages or Components that contain references to other entities, such asHasParent(Entity)
. The entity in the message is from the server's World, and it needs to be mapped to the client's World.- refactored the
MapEntities
trait to be more flexible - introduced also entity mapping between the Confirmed entities and the Predicted/Interpolated entities (i.e. a component with
HasParent(Entity)
on the Confirmed entity will be synced with the correct mapping on the Predicted/Interpolated entities) - A Replication Group will be guaranteed to be replicated with an entity order that respects any dependencies between the entities: for example if you have two entities A and B and B depends on A (maybe because it contains a component
HasParent(A)
, B will be replicated after A has finished replicating.
- refactored the
-
Updated the Input handling logic to get rid of some jitter around client prediction:
- clients now must send inputs every tick (even if there are no inputs), so that the server can distinguish between "no input" and "lost input"
- For a lost input, the server uses the last received input as a fallback estimate
- this helps reduce prediction errors by a huge amount; client prediction should be more robust now!
-
Refactored some of the interpolation logic:
- a Component only needs to implement
Add
andMul
if it usesLinearInterpolation
, not if it uses a custom interpolation function - added an option to give the user complete freedom to implement their interpolation logic
- a Component only needs to implement
Fixed
- Fixed an issue that was causing some instability during client-prediction: the client inputs were not buffered for long enough, so the input buffer was sometimes empty during rollback
- Fixed some issues that were causing instability/jitter during snapshot-interpolation: the interpolation start and end ticks were sometimes not computed correctly, which would cause the interpolation to 'jitter'. Snapshot interpolation should be much smoother now
- Fixed an issue where the ping buffer was sometimes too small and would panic, especially if the ping send_interval was short
Migration guide
- Clients must now send inputs every tick, even if the user isn't doing any action. Practically, this means that you need to add a
None
variant in your Input enum, like so:
pub enum Inputs {
Direction(Direction),
Delete,
Spawn,
// *new*: inputs must be sent every tick, even if there is no action
NoAction,
}
- The MapEntities trait has been modified; all old implementors must be updated to match the new trait:
/// Trait that Messages or Components must implement to be able to map entities
pub trait MapEntities<'a> {
/// Map the entities inside the message or component from the remote World to the local World
fn map_entities(&mut self, entity_mapper: Box<dyn EntityMapper + 'a>);
/// Get all the entities that are present in that message or component
fn entities(&self) -> EntityHashSet<Entity>;
}
Release 0.3.0
Release 0.3.0
Added
- Added interest management via the concept of
Room
, to be able to replicate only a subset entities to a certain client to save bandwidth and prevent cheating. See more information here - Updated the replication manager to be much more robust. The new manager:
- makes sure that an entity or a group of entities (via
ReplicationGroup
) are replicated with a consistent state. See more information here - inspired by what
bevy_replicon
is doing - this fixes some edge cases with prediction/interpolation; in particular when send_interval is small compared with jitter
- makes sure that an entity or a group of entities (via
- Added a new
UnreliableSenderWithAck
channel where senders can get notified when a given message has been ACK-ed - Some common
bevy
structs (Transform
,Color
,Visibility
) now implement theMessage
trait and can be used for replication
Updated
- the
ComponentProtocol
now derivesDebug
directly (which shows the type of the replicated Component, but not the value)
Fixed
- the existing world state is now replicated properly to newly connected clients (whether they use
Room
or not for replication)