lightyear

repository·main·Indexed 21 days ago

https://github.com/cbournhonesque/lightyear

A networking framework for Bevy featuring server authority, client prediction, and rollback reconciliation. The framework supports diff-based component replication via Replicon, integration with avian2d/avian3d physics and leafwing_input_manager, and connection protocols including Netcode and WebTransport for WASM targets.

Tokens
171.7K
Snippets
557
Records
763
Agent score
75%

What's inside lightyear

  1. Overview of lightyear example features

    main

    The examples/ directory contains several implementations demonstrating different networking capabilities:

    Basic Setup

    • simple_setup: Minimal example for creating lightyear client and server plugins.
    • simple_box: Demonstrates sending inputs from client to server, client-prediction, and interpolation.
    • bevy_enhanced_input: Integration with the bevy_enhanced_input crate.

    Intermediate Networking

    • delta_compression: Replicating components by sending only differences when values change.
    • network_visibility: Replicating only a subset of entities to specific players.
    • replication_groups: Ensuring entities that refer to each other (via Entity IDs) are replicated in the same message.
    • priority: Managing bandwidth via priority accumulation (messages sent in order of priority).

    Advanced Use Cases

    • avian_2d / avian_3d: Replicating 2D/3D physics simulations using Avian.
    • projectiles: Projectile replication under various networking modes.
    • fps: Spawning player-objects on the Predicted timeline and using lag compensation for collisions.
    • auth: Using ConnectToken for client authentication.
    • lobby: Changing network topology at runtime (e.g., clients acting as hosts).
  2. Understand the Connection abstraction in lightyear

    main

    While the transport layer only handles raw packet exchange to remote addresses, lightyear provides a stateful 'connection' layer on top of it. This layer manages:

    • Handshake packets for authentication
    • Keep-alive packets to monitor connection status
    • Tracking the list of connected remote peers

    lightyear abstracts this logic using the NetClient and NetServer traits. Depending on your deployment target, you can use different implementations such as Netcode, Steam, or Local.

  3. Implement or use the Transport trait for network communication

    main

    The Transport layer in Lightyear is defined by two primary traits: PacketSender and PacketReceiver. These traits are used to send and receive raw data over a network. To build a custom transport or use existing ones, you must satisfy these interfaces for sending payloads to specific addresses and receiving data along with the sender's address.

    Lightyear provides four built-in implementations:

    • UDP sockets
    • WebTransport (using QUIC)
    • WebSocket
    • crossbeam-channels (primarily used for internal testing)
    pub trait PacketSender: Send + Sync {
        /// Send data on the socket to the remote address
        fn send(&mut self, payload: &[u8], address: &SocketAddr) -> Result<()>;
    }
    
    pub trait PacketReceiver: Send + Sync {
        /// Receive a packet from the socket. Returns the data read and the origin.
        ///
        /// Returns Ok(None) if no data is available
        fn recv(&mut self) -> Result<Option<(&mut [u8], SocketAddr)>>;
    }
  4. Understand the Lightyear Prediction Model

    main

    Lightyear's prediction system is designed to handle networked state replication using a combination of UpdateMessage and MutateMessage types.

    Message Types

    • UpdateMessage: Contains archetype changes (component insertion/removal) and entity spawns/despawns. These are sent as a single unit to ensure all changes for a specific tick are received together.
    • MutateMessage: Contains component value changes. These can be sent across multiple messages and may arrive on different ticks or be lost.

    Core Logic

    • Ordering: Mutations are only applied if all updates prior to the tick they were sent in have been applied.
    • Empty Messages: If no mutations occur on a tick, an empty MutateMessage is still sent to signal that no components were modified.
    • Tracking Reception:
      • ConfirmHistory: Per-replicated-entity tracking of ticks where a mutation/update message was received.
      • ServerMutateTicks: A global resource tracking ticks where all messages sent for that tick have been successfully received.

    Prediction History

    Each predicted component uses a PredictionHistory<C> which stores an enum for each tick:

    • Predicted(C): The value predicted by the local simulation.
    • Confirmed(C): The authoritative value received from the remote server.
    • Removed: Indicates the component was removed.
  5. How Entity Actions are replicated

    main

    Entity Actions (spawn/despawn, component insert/remove) are replicated using an OrderedReliable mechanism to ensure structural changes are never lost or applied out of order.

    Server-side (Send)

    • When any actions occur for a ReplicationGroup, they are bundled into a single message along with any pending EntityUpdates for that group. This bundling ensures that structural changes and the data they introduce arrive together.
    • Each message is assigned a monotonically increasing message id used for ordering.

    Client-side (Receive)

    • The client buffers incoming EntityActions and processes them strictly in order based on their message IDs (e.g., 1, 2, 3, 4...) to ensure the entity archetypes are built correctly.
  6. Understand the difference between Entity Actions and Entity Updates

    main

    Lightyear distinguishes between two types of replication events to optimize network usage and maintain consistency:

    1. Entity Actions: These events change the archetype of an entity. Examples include spawning/despawning an entity or inserting/removing a component. These are critical for structural integrity and are sent using OrderedReliable delivery.
    2. Entity Updates: These events update the value of existing components without changing the archetype. These constitute the vast majority (90%+) of replication messages and are sent using SequencedUnreliable delivery to reduce overhead.

    Understanding this distinction is key to managing how state changes are propagated and how the system handles packet loss or reordering.

  7. Use Pre-spawned Predicted entities for seamless authority handover

    main

    If you want to spawn a predicted entity on the client (e.g., a projectile) and then immediately hand over authority to the server without the 1-RTT delay of waiting for a server-spawned entity, use the PrePredicted pattern.

    Workflow:

    1. On the Client: Spawn the entity and add the PrePredicted component.
    2. Replication: Replicate the entity to the server.
    3. On the Server: To replicate the entity back to the client, manually add a Replicate component to the server entity specifying the target clients. You must add this Replicate component within the ServerReplicationSet::ClientReplication SystemSet.
    4. Handover: When the server replicates the entity back, the client detects the PrePredicted component. Instead of spawning a new predicted entity, the client spawns a Confirmed entity and re-uses the existing entity as the Predicted entity.

    Key Behaviors:

    • Once the handover occurs, the client-to-server replication stops, and the server entity becomes the authoritative one.
    • If the PrePredicted component is missing when the server replicates an entity back, the client will spawn both a new Confirmed and a new Predicted entity (standard behavior).
  8. How bandwidth priority works in lightyear

    main

    Lightyear provides a bandwidth management system that allows you to specify priority for messages, channels, and entities. When the bandwidth quota is reached, lightyear prioritizes sending data with the highest priority values up to the available quota.

    To prevent lower-priority entities from being completely starved of updates, their priority accumulates over time. This ensures that even low-priority data is eventually sent once its accumulated priority exceeds the threshold required to compete with higher-priority traffic.

    In the context of component replication, you can use a PriorityMap (provided by Replicon) to control which component mutations are sent. Note that PriorityMap only affects component mutations; initial entity spawns are always sent immediately.

  9. How replication groups ensure entity consistency

    main

    Replication groups allow Lightyear to replicate multiple entities within a single message. This ensures that all entities in a group are synchronized to the same server tick.

    Without replication groups, independent entities might be replicated on different ticks (e.g., Entity A on tick 10 and Entity B on tick 11). This causes issues when entities have dependencies, such as:

    • Client Prediction: Entities that depend on each other's state for accurate prediction.
    • Parent-Child Relationships: If a weapon entity has a Parent(owner: Entity) component referencing a player, the player must be replicated in the same tick (or before) to ensure the reference is valid when the weapon is spawned.
  10. Understand the Frame Interpolation System Order

    main

    Frame interpolation operates across three distinct system sets to ensure visual smoothness without corrupting the canonical simulation state:

    1. FrameInterpolationSystems::Restore (RunFixedMainLoop): Runs once per rendered frame before the fixed loop. It copies FrameInterpolationHistory<C>::current_value back to the live component. This ensures the fixed simulation reads canonical state rather than the interpolated visual value from the previous frame.

    2. FrameInterpolationSystems::Update (FixedPostUpdate): Runs after each fixed simulation tick. It shifts the existing current_value to previous_value and records the new canonical live value as current_value. During rollback replay, history updates are skipped, and prediction repairs the history from corrected values.

    3. FrameInterpolationSystems::Interpolate (PostUpdate): Runs after replication sends but before transform propagation. It samples the previous and current fixed values using Time<Fixed>::overstep_fraction() and updates the live component.

    Note on Change Detection: Because Interpolate updates the live component via Bevy's mutable access, downstream systems filtering on Changed<C> will observe the interpolated value if they run after FrameInterpolationSystems::Interpolate in PostUpdate.

  11. How predicted bullet spawning works

    main

    To reduce perceived lag, the Spaceships Demo uses prespawning. When a player fires, a bullet is immediately prespawned on the client using a PreSpawnedPlayerObject hash.

    1. Client Action: The client prespawned entity is created locally.
    2. Server Action: The server receives the input, spawns the actual entity with the matching hash, and replicates it.
    3. Reconciliation: Once the Confirmed entity is replicated from the server, the client's Predicted entity is synchronized with it.

    Input Delay & Remote Players: If input delay is configured (e.g., sampling inputs for tick 13 on tick 10), a client might receive a remote player's inputs before simulating that tick locally. In this case, the remote player's bullet is also predictively spawned. If inputs arrive late, the bullet is created via normal replication, and the client performs a rollback to position it correctly.