Skip to content

Releases: cBournhonesque/lightyear

Release 0.15.0

15 May 15:56
Compare
Choose a tag to compare

Release 0.15.0

Added

Refactored the Replicate component

Replicate used to be a Component describing all facets of how an entity should be replicated to a remote peer.
It is now a bundle composed of multiple smaller components:

  • Server to Client:
    • ReplicationTarget: which clients should receive the entity
    • SyncTarget: which clients should predict/interpolate the entity
    • VisibilityMode: do we use interest management or not
    • ControlledBy: which clients are the 'owners' of the entity?
  • Client to Server:
    • ReplicateToServer: marker component to initiate the replication to the server
  • Shared
    • Replicating: marker component indicating that the entity should be actively replicated. If this component is removed, the replication is paused.
    • ReplicateHierarchy: whether we replicate the children of this entity or not
    • ReplicationGroup: how do we group multiple entities into a single message

On the receiver side, all replicated entities will have the Replicated component.
For client->server replication, you can use Replicated::client_id() to identify the client that replicated this entity.

You can also customize the replication of a specific component via the components:

  • DisabledComponent<C>: the component is not replicated at all
  • ReplicateOnce<C>: the component's insertion/removal are replicated, but no the updates
  • OverrideTargetComponent<C>: override the replication target (the list of receiving clients) for this component

Entity ownership

It can be useful for the server to be aware of which client is 'owning' an entity.
For example, so that you can despawn all of a client's entities when the client disconnects.

You can add the ControlledBy component on an entity in the server World to specify which clients 'own' the entity.
When this entity gets replicated, the owning clients will have the Controlled marker component added.
This can be useful for the client to identify the entities it should apply client-side prediction on. (usually client-prediction is used only on the entities owned by the client)

When a client disconnects, lightyear automatically despawns the entities owned by that client. (this will be more configurable in the future).

How can you access this per-client metadata on the server? We now spawn one entity per client on the server, which can be used to hold metadata about that client. The entity can be accessed via ConnectionManager::client_entity(client_id).
For now, the only metadata has ControlledEntities component which holds a list of all the entities controlled by a client; feel free to add more if you need!

Replication logic can be updated at runtime

Before, you could only add the Replicate component once; it was not allowed to update the Replicate component after the entity was first replicated.
Now you can freely update the replication components to make changes to the replication logic:

  • modify the ReplicationTarget component to spawn an entity on new clients, or despawn the entity on some clients
  • update the VisibilityMode component to enable interest management
  • update ReplicateHierarchy to start replicating the children of an entity
  • etc.

Client and server plugins are now plugin groups

The ClientPlugin and ServerPlugin plugins have been replaced with the ClientPlugins and ServerPlugins plugin groups.
This means that you can freely override or disable any of the internal plugins that compose the plugin groups.

All internal plugins are enabled by default, but feel free to disable any that you don't need. For example you could disable the VisibilityPlugin on the server if you don't need interest management, the ClientDiagnosticsPlugin if you don't need to compute statistics on the connection, or ClientReplicationSendPlugin if you don't need to replicate entities from the client to the server.

Immediate mode visibility

lightyear used "rooms" to perform interest management. Clients and entities could join rooms, and an entity would only be replicated to a client if they shared a room.
This semi-static method is convenient in most cases, but sometimes you need a more immediate API to handle visibility.
You can now use the VisibilityManager to directly specify the visibility of a (client, entity) pair:

  • VisibilityManager::gain_visibility
  • VisibilityManager::lose_visibility

Miscellaneous

  • You can now override the function that checks if a rollback is necessary for a given component. It defaults to PartialEq::ne (rollback if the values are different) but you can override it with add_should_rollback_fn(custom_fn)
  • Added an example to showcase how to securely send a ConnectToken to a client so that it can initiate a connection.
  • Added the [zstd] feature to run zstd-compression on all packets.

Fixed

  • ChangeDetection is not triggered anymore if a component gets replicated to an entity but is equal to the existing value of the component on that entity.
  • The NetworkingState is now correctly updated when the io gets disconnected (for example via a timeout). The io tasks now get disconnected if the NetworkingState is set to disconnected (for example if the client requests disconnection).
  • Support bevy_xpbd with f64 precision
  • Improved parallelization of prediction and interpolation systems
  • Improved protocol ergonomics: you don't need to specify the component on every function anymore
app.register_component::<PlayerId>(ChannelDirection::ServerToClient)
     .add_prediction(ComponentSyncMode::Once)
     .add_interpolation(ComponentSyncMode::Once);

Breaking changes

  • Changes mentioned above for Replicate, ClientPlugin, ServerPlugin
  • Components in the ComponentRegistry must now implement PartialEq
  • Updated RoomId to be a u64

Release 0.14.0

29 Apr 21:42
Compare
Choose a tag to compare

Release 0.14.0

Added

Refactored the Protocol

lightyear used to require a Protocol struct which contained 2 enums MessageProtocol and ComponentProtocol that held the list of messages that could be sent over the network.

Now you can split up a protocol into multiple pieces, each piece can be registered directly on the App:

app.add_message::<Message1>(ChannelDirection::Bidirectional);
app.add_plugins(InputPlugin::<Inputs>::default());
app.register_component::<PlayerPosition>(ChannelDirection::ServerToClient)
    .add_prediction::<PlayerPosition>(ComponentSyncMode::Full)
    .add_interpolation::<PlayerPosition>(ComponentSyncMode::Full)
    .add_linear_interpolation_fn::<PlayerPosition>();
app.add_channel::<Channel1>(ChannelSettings {
    mode: ChannelMode::OrderedReliable(ReliableSettings::default()),
    ..default()
});

This approach provides more flexibility, since the Protocol can now be defined in various separate plugins; it also removes a lot of procedural macro magic that was hard to maintain.
Currently the only requirement is that the protocol registration must happen after the ClientPlugin or the ServerPlugin have been added.

Network configuration is modifiable at runtime

Previously, your network configuration would be set in stone after adding the ClientPlugin and ServerPlugin.
Now, you can freely update the configuration (by updating the ClientConfig and ServerConfig resources) at any time, and the configuration change will take effect on the next connection attempt.

I also introduce the NetworkingState state to track the status of the client or server (Connecting, Connected, Disconnected).

This means that a machine can dynamically become a client or a server depending on the configuration!
Here is an example where clients can choose at runtime whether to host the game by acting as 'host-servers', or just join the game as clients.

Automatic Resource replication

You can now easily replicate resources!
There are 2 steps:

  1. Define the resource in your protocol: app.register_resource::<R>(ChannelDirection::ServerToClient);
  2. Use commands to start/stop the replication:
  • commands.replicate_resource::<R>(target) starts the replication
  • commands.pause_replicate_resource::<R>() pauses the replication (without removing the resource)

And every changes to your resource will now be replicated!

Updated

Automatic cleanup on client disconnection

When a client gets disconnected, we now automatically cleanup all entities and resources that had been spawned on the client via replication.
In the future I have plans to make this behavior more configurable (we might want some entities to remain even when disconnected), but I chose to enable this currently for simplicity.

Separating the prediction and interpolation mode

Previously, there was only one configuration shared between Prediction and Interpolation. But there are actually situations where one would want to only enable Prediction and not Interpolation, or vice-versa.
For example, you might need to turn on Prediction on a physics component like AngularVelocity, but not turn on Interpolation since AngularVelocity doesn't have any visual impact on the component.
You can now independently specify the prediction behavior and the interpolation behavior.

Breaking changes

  • The ClientPlugin is now directly created from the ClientConfig, same for ServerPlugin
  • You now have to define a shared protocol by registering the protocol on the App after the ClientPlugin and ServerPlugin have been registered.
  • You now update the connection status via Commands:
    • commands.connect_client()
    • commands.disconnect_client()
    • commands.start_server()
    • commands.stop_server()

Release 0.13.0

01 Apr 22:26
9e3b053
Compare
Choose a tag to compare

Release 0.13.0

Added

Added Steam transport

You can now use Steam sockets as a networking Transport layer!
Note that the server can run multiple transports in parallel, so you can have cross-play between steam and non-steam users!

Running lightyear in "Host Server" mode

In the previous release, I updated all the examples to run in "listen server" mode (the server and client are running in the same machine). This was done by starting a client bevy app and a server bevy app in different threads, and sending messages between them via channels.

This works fine but has some disadvantages:

  • extra CPU overhead from running 2 bevy apps and 2 Worlds
  • complexity of having 2 different timelines
  • inputs would be slightly delayed (by 1-2 frames)

In this release, you can now run lightyear in "host server" mode. In this mode, you can create a single bevy app where the server also acts as a client. There are no packets sent between client and server because the client and server plugins are running in the same bevy World!

Updated

  • Added an object pool to re-use memory allocation of ReadBuffers used to deserialize packets

Fixed

  • Fixed a bug with interest management where an entity would get despawned when changing rooms

Breaking changes

  • Updated ClientId: instead of a u64 it is now an enum that depends on the type of Transport used by the client
  • Removed the GlobalMetadata structs; the client_id is directly accessible via connection.id()
  • Pre-Predicted entities are now spawned using the PrePredicted component instead of ShouldBePredicted
  • Updated ConnectEvent and DisconnectedEvent on the client side to also include the ClientId of the client with event.client_id()
  • Inputs are buffered on the client-side using an InputManager resource, not the ClientConnection anymore

Planned Future work

  • Add steam p2p connections
  • Enable runtime configuration of the transports: for example let a different user become the 'host' at runtime

Release 0.12.0

13 Mar 15:21
5077a58
Compare
Choose a tag to compare

Release 0.12.0

Added

Server can support multiple Transports simultaneously

A lightyear server can now listen simultaneously on different types of Transports: WebSocket, WebTransport, UDP, local channels, etc.
It requires almost no code change: instead of providing a single TransportConfig when building the server, you can now provide a Vec<TransportConfig> and the server will be listening on each config simultaneously!

All examples have been updated to showcase this behavior: the server listens for WebTransport connections on port 5000, UDP on port 5001 and WebSocket on port 5002!

This is very exciting for two main reasons:

  • this is will allow cross-play between different connection configurations. I have plans to integrate with Steam and EOS (Epic Online Services). With this feature, players connected via Steam could also play with players connected via UDP directly to a dedicated server!
  • this enables running lightyear in "Listen Server" mode!

Running lightyear in "Listen Server" mode

"Listen Server" means that a player acts as both the server and the client. (the server will run on a separate thread on the client's machine). This could be useful for p2p games, or for some single-player game architectures where the client talks to a local server.
With the above change, it is now easy to run lightyear as a Listen Server! The server and client apps run on different threads and use local channels to communicate with 0 latency, and other clients can still connect to the host.

All examples have been updated to be able to run like this with the command: cargo run -- listen-server

Updated

  • Updated all examples to use a separate settings.ron file to specify the networking configuration. (the previous cli-based approach wasn't flexibly enough when using multiple transports for a given server)

Future work

My next priority will be to add Steam as a connection option.

Release 0.11.0

26 Feb 04:39
4e2dcea
Compare
Choose a tag to compare

Release 0.11.0

Small release with mostly internal changes.

Fixed

Internal refactor by regrouping logic into bevy plugins

Moved some of the logic for the client and server into 3 new internal plugins:

  • NetworkingPlugin: contains the main receive and send systems that receive and send raw packets
  • ReplicationPlugin: contains the logic to perform World replication
  • EventsPlugin: write bevy Events whenever a networking event happens (MessageReceived, InputReceived, ConnectionReceived, etc.)

The main server and client plugins are now relatively short and just consist in importing a few other plugins.

Fixed a bug that was causing the docs to not be generated

The vendored bitcode crate could not be compiled in nightly anymore, this has been fixed.

Added

remove_replicate command

Added an EntityCommand called remove_replicate that lets you stop replicating an entity, and guarantees that any event related to that entity won't be replicated, including Despawns.
For example you can call

entity_commands.remove_replicate();
entity_commands.despawn()

on the server, and the entity's despawn won't be replicated to clients. (This could be useful if you want to play a despawn animation on the client)

Breaking changes

  • The SharedConfig struct does not have a enable_replication field anymore. This field was previously unused, so I am removing it for clarity. In the future, I will probably provide the ability to disable replication by turning the Client and Server plugins into PluginGroups

Release 0.10.0

21 Feb 00:00
491ff18
Compare
Choose a tag to compare

Support for bevy 0.13

Lightyear is updated for bevy 0.13!

There are still a couple of issues (a leafwing-input related problem on the bullet_prespawn example, and waiting for bevy_inspector_egui to be updated for bevy 0.13) which will be fixed in a future minor release.

Hierarchy replication

Lightyear now supports replicating bevy hierarchies (i.e. replicating the Parent/Children components).
You can just set replicate.replicate_hierarchy = true on the Replicate component of the root entity of your hierarchy; all entities in the hierarchy will be replicated in a single ReplicationGroup (meaning that it is guaranteed that updates for all these entities will be received by the remote on the same tick).

Scheduling quality-of-life

Lightyear now makes use of the newly added FixedFirst, FixedPreUpdate, FixedPostUpdate schedules, which means that you can now add all your logic in the FixedUpdate schedule without worrying about ordering with other lightyear systems. In particular, the FixedUpdateSet::Main SystemSet has been removed.

Visual interpolation

Most networked data is meant to be updated in the FixedUpdate schedule so that they don't depend on framerate.
This can cause visual artifacts (jitter, etc.) because you could have multiple frames in a row without the FixedUpdate schedule running, or the FixedUpdate schedule could run multiple times in a single frame.

I've added a plugin VisualInterpolationPlugin that smoothly interpolates the value of a component between the FixedUpdate values. This adds 1 tick (FixedUpdate timestep) of visual delay, which should be negligible in most cases.
You can read more about it in the book.

Fixed

  • Interpolation is now more precise, as it uses the Time<Fixed>::overstep_fraction to compute the interpolation percentage

Migration

  • The FixedUpdateSet::Main SystemSet has been removed, you can now just add your systems to the FixedUpdate schedule
  • The MapEntities trait has been replaced with the LightyearMapEntities trait
  • The input_buffer systems have to be added to the FixedPreUpdate schedule. (this limitation is only present for native inputs, not for leafwing_inputs)
  • The LogConfig setting in SharedConfig has been removed. I will provide a custom tracing::subscriber Layer to add additional metrics/logging

Release 0.9.0

13 Feb 21:59
808a3f6
Compare
Choose a tag to compare

Added

Bandwidth management and priority scores

Lightyear now supports putting a limit on the bandwidth between a client and a server.
You can set a cap (for example 50KB/sec).

If there are too many messages to send, lightyear will use a priority with accumulation scheme to decide which messages will be sent and which will be discarded. You can set a priority score on each message, channel or entity to define its relative priority to other messages.

You can find more information in the book.
You can also check an example online

Added traits to represent the long-running connection

We already have a trait Transport that represents how to send raw byte slices over the wire, with several implementations: UdpSocket, WebTransport, WebSocket, etc. An Io is a wrapper around a dyn Transport, which lets us swap the different transport implementations effortlessly.

Similarly, I am introduce two new traits NetClient and NetServer that are an abstraction of a long-lived 'Connection': how to connect, do a handshake, get the list of connected clients, disconnect, etc.
The structs ClientConnection and ServerConnection becomes wrapped around dyn NetClient and dyn NetServer, so that we can use different 'connection' implementations. The only implementation currently is based on the netcode protocol, but this opens the door to other connection abstractions. In particular I plan to use this to add steam sockets support!

Added support for WebSockets (from @Nul-led )

There is one new implementation of the Transport trait using websockets! Those work on both native and wasm; they can be a good alternative to WebTransport on browsers that don't support WebTransport (Safari). They can be easier to work with as well as there is no need to generate a short-lived certificate.
Be mindful that websockets use TCP and can encounter head-of-line blocking.

Fixed

  • Some bugfixes related to pre-spawning entities
  • The io connection (WebTransport, UDP socket, etc.) establishes the connection when the connect function is called instead of when the lightyear Plugins are built
  • Removed the Eq bound on non-leafwing inputs, so that they can contain f32s

Migration

Release 0.8.0

26 Jan 00:01
82e0014
Compare
Choose a tag to compare

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

17 Jan 00:09
Compare
Choose a tag to compare

Added

  • Split the Client and Server 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 ActionStates could not be replicated correctly from client->server in some edge-cases. Now inputs are networked correctly whether the controlled Entity is Controlled, Predicted or Pre-Predicted.

Migration

  • You will need to update your systems; the old 'monolithic' resources are still available as the SystemParams Client/ClientMut and Server/ServerMut. Otherwise you will mostly need to use the resources ClientConnectionManager and ServerConnectionManager.

Release 0.6.1

11 Jan 15:28
b904339
Compare
Choose a tag to compare
  • Add support for bevy_xpbd 0.3.3
  • Fixes some issues (overflow) for WrappedTime
  • Removed some info! and warn! logs in client sync