Crate lightyear

Source
Expand description

§Lightyear

Lightyear is a networking library for Bevy. It provides a set of plugins that can be used to create multiplayer games.

It has been tested mostly in the server-client topology, but the API should be flexible enough to support other topologies such as peer-to-peer.

You can find more information in the book or check out the examples!

§Getting started

§Adding the Plugins

Similarly to Bevy, lightyear is composed of several sub crates that each provide a set of features. A wrapper crate lightyear is provided for convenience, which contain two main PluginGroups: ClientPlugins and ServerPlugins that you can add to your app.

use bevy_app::App;
use core::time::Duration;
use lightyear::prelude::*;

pub const FIXED_TIMESTEP_HZ: f64 = 60.0;

fn main() {
    let mut app = App::new();
    app.add_plugins(client::ClientPlugins {
        tick_duration: Duration::from_secs_f64(1.0 / FIXED_TIMESTEP_HZ),
    });
    app.add_plugins(server::ServerPlugins {
        tick_duration: Duration::from_secs_f64(1.0 / FIXED_TIMESTEP_HZ),
    });
}

It is also possible to use the subcrates directly:

IO (How to send bytes over the network)

  • lightyear_link: provides a transport-agnostic Link component which is responsible for sending and receiving bytes over the network.
  • lightyear_crossbeam: IO layer that uses crossbeam channels. Useful for testing or for local networking (by having a server process and a client process on the same machine).
  • lightyear_udp / lightyear_webtransport: IO layers for the UDP protocol and WebTransport protocol respectively.

Connection

  • lightyear_connection: this layer wraps the IO layer by providing a long-running PeerId identifier component that is used to identify a peer in the network. Also provides the Client and Server components for client-server topologies.
  • lightyear_netcode: a connection layer that uses the netcode.io standard for creating secure connections over an unreliable IO such as UDP.
  • lightyear_steam: a connection layer that uses the Steam networking API to both send the bytes and to provide a long-running identifier. This layer operates at both the IO and the connection level.

Currently it is not possible to use an IO layer without a connection layer.

Messages

  • lightyear_transport: provides a Transport component that is provides several channels with different reliability/ordering guarantees when sending raw bytes. This crate also organizes the raw bytes into messages that are assembled into packets.
  • lightyear_messages: provides a MessageManager component responsible for handling the serialization of Messages (serializable structs) into raw bytes that can be sent over the network.

Replication

  • lightyear_replication: provides utilities to replicate the state of the Bevy World between two peers.
  • lightyear_sync: helps synchronize the timelines between two peers.
  • lightyear_prediction: provides client-side prediction and rollback to help hide latency
  • lightyear_interpolation: provides interpolation for replicated entities to smooth out the network updates received from the remote peer.
  • lightyear_frame_interpolation: most of the game logic should run in the FixedMain schedule, but the rendering is done in the PostUpdate schedule. To avoid visual artifacts, we need some interpolation to interpolate the rendering between the FixedMain states.

Inputs

Extra

§Implement the Protocol

The Protocol is a shared configuration between the local and remote peers that defines which types will be sent over the network.

You will have to define your protocol in a shared module that is accessible to both the client and the server.

There are several steps:

NOTE: the protocol must currently be added AFTER the Client/Server Plugins, but BEFORE any Client or Server entity is spawned.

In lightyear, your Client or Server will simply be an entity with the right sets of components.

The Link component can be added on an entity to make it able to send and receive data over the network.

Usually you will add more components on that entity to customize its behavior:

  • define its role using the Client or Server components. Most of the lightyear plugins currently expect the Link entity to have one of these components.
  • make it able to receive/send replication data using the ReplicationSender or ReplicationReceiver components.
  • add a MessageManager component to handle the serialization and deserialization of messages.
  • etc.

The Server entity works a bit differently. It starts a server that listens for incoming connections. When a new client connects, a new entity is spawned with the LinkOf component. You can add a trigger to listen to this event and add the extra components to customize the behaviour of this connection.

fn handle_new_client(trigger: Trigger<OnAdd, LinkOf>, mut commands: Commands) {
    commands.entity(trigger.target()).insert((
        ReplicationSender::new(Duration::from_millis(100), SendUpdatesMode::SinceLastAck, false),
        Name::from("Client"),
    ));
}

§Using lightyear

§Linking

There is a set that reflects if the Link is established. The link represents an IO connection to send bytes to a remote peer. You can trigger LinkStart to start the link, and Unlink to stop it.

The Unlinked, Linking, Linked components represent the current state of the link.

§Connections

A connection is a wrapper around a Link that provides a long-running identifier for the peer. You can use the PeerId component to identify the remote peer that the link is connected to, and LocalId to identify the local peer.

The lifecycle of a connection is controlled by several sets of components.

You can trigger Connect to start the connection, and Disconnect to stop it.

The Disconnected, Connecting, Connected components represent the current state of the connection.

On the server, Start and Stop components are used to control the server’s listening state. The Stopped, Starting, Started components represent the current state of the connection.

While a client is disconnected, you can update its configuration (ReplicationSender, MessageManager, etc.), it will be applied on the next connection attempt.

§Sending messages

The MessageSender component is used to send messages that you have defined in your protocol.

use lightyear::prelude::*;
use lightyear::prelude::server::*;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct MyMessage;

struct MyChannel;

fn send_message(mut sender: Single<&mut MessageSender<MyMessage>>) {
    let _ = sender.send::<MyChannel>(MyMessage);
}

§Receiving messages

The MessageReceiver component is used to receive messages that you have defined in your protocol.



fn send_message(mut receivers: Query<&mut MessageReceiver<MyMessage>>) {
    for mut receiver in receivers.iter_mut() {
        let _ = receiver.receive().for_each(|message| {});
    }
}

§Starting replication

To replicate an entity from the local world to the remote world, you can just add the Replicate component to the entity.

The marker component Replicating indicates that the entity is getting replicated to a remote peer. You can remove the Replicating component to pause the replication. This will not despawn the entity on the remote world; it will simply stop sending replication updates.

§Reacting to replication events

On the receiver side, entities that are replicated from a remote peer will have the Replicated marker component.

You can use to react to components being inserted via replication.



fn component_inserted(query: Query<Entity, (With<Replicated>, Added<MyComponent>)>) {
    for entity in query.iter() {
        println!("MyComponent was inserted via replication on {entity:?}");
    }
}

§Feature Flags

  • client (enabled by default) — enable client plugins

  • server (enabled by default) — enable server plugins and server-only features

  • replication (enabled by default) — Enables replicating entities between two peers

  • prediction (enabled by default) — Enables client-side prediction handling to mask latency for local player actions

  • frame_interpolation — Enables re-exports of lightyear_frame_interpolation

  • interpolation (enabled by default) — Enables interpolation handling, to smooth entity updates received from the remote peer

  • metrics — enable metrics collection (via metrics crate)

  • netcode — Enables netcode to provide persistent IDs to clients

    Note: currently, this must always be enabled when not using Steam

  • webtransport — Enable WebTransport support as an IO layer (defers to aeronet_webtransport)

  • webtransport_dangerous_configuration — Enable unsafe configurations for WebTransport (e.g. allowing unencrypted connections) as a convenience for testing

  • input_native — Add support for handling inputs where you can define your own input structs

  • leafwing — Add support for handling inputs using the leafwing-input-manager crate

  • avian2d — Adds support for Avian

    NOTE: because lightyear doesn’t enable any features of avian by default, your crate must enable essential features (like f32 or f64). Use avian2d

  • avian3d — Use avian3d

  • udp — Enables UDP as an IO layer

  • crossbeam — Enables crossbeam channels as an IO layer

    This is useful when running a client and server in two processes of the same machine so that a client can act as the host

  • steam — Enables Steam as a connection layer

Modules§

connection
core
crossbeamcrossbeam
frame_interpolationframe_interpolation
inputinput_native or leafwing or input_bei
interpolationinterpolation
link
netcodenetcode
predictionprediction
prelude
steamsteam
utils
webtransportwebtransport