From 83adcadb66e05c867ae243ce8f9dcb41284e4811 Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Thu, 21 Dec 2023 14:56:36 -0500 Subject: [PATCH 1/2] fix --- NOTES.md | 23 +- lightyear/src/channel/builder.rs | 3 +- lightyear/src/channel/receivers/mod.rs | 6 +- .../channel/senders/fragment_ack_receiver.rs | 9 +- lightyear/src/channel/senders/mod.rs | 10 +- lightyear/src/channel/senders/reliable.rs | 2 +- .../senders/unordered_unreliable_with_acks.rs | 10 +- lightyear/src/client/components.rs | 8 +- lightyear/src/client/config.rs | 2 - lightyear/src/client/connection.rs | 12 +- lightyear/src/client/events.rs | 1 - lightyear/src/client/input.rs | 1 - lightyear/src/client/interpolation/despawn.rs | 22 +- .../src/client/interpolation/interpolate.rs | 2 +- .../interpolation/interpolation_history.rs | 38 +- lightyear/src/client/interpolation/mod.rs | 60 +-- lightyear/src/client/interpolation/plugin.rs | 12 +- .../src/client/interpolation/resource.rs | 18 + lightyear/src/client/plugin.rs | 3 +- lightyear/src/client/prediction/mod.rs | 28 +- lightyear/src/client/prediction/plugin.rs | 10 +- .../client/prediction/predicted_history.rs | 33 +- lightyear/src/client/prediction/resource.rs | 19 + lightyear/src/client/prediction/rollback.rs | 4 +- lightyear/src/client/resource.rs | 3 +- lightyear/src/client/sync.rs | 4 - lightyear/src/client/systems.rs | 4 +- lightyear/src/connection/events.rs | 9 +- lightyear/src/connection/message.rs | 12 +- lightyear/src/connection/mod.rs | 17 +- lightyear/src/inputs/input_buffer.rs | 3 +- lightyear/src/inputs/mod.rs | 7 +- lightyear/src/lib.rs | 8 +- lightyear/src/netcode/server.rs | 2 +- lightyear/src/packet/header.rs | 10 +- lightyear/src/packet/message.rs | 1 - lightyear/src/packet/message_manager.rs | 7 +- lightyear/src/packet/packet_manager.rs | 2 - lightyear/src/packet/stats_manager.rs | 8 +- lightyear/src/protocol/component.rs | 2 +- lightyear/src/protocol/mod.rs | 4 +- lightyear/src/server/config.rs | 1 - lightyear/src/server/connection.rs | 14 +- lightyear/src/server/events.rs | 1 - lightyear/src/server/plugin.rs | 1 - lightyear/src/server/resource.rs | 6 +- lightyear/src/server/room.rs | 27 +- lightyear/src/server/systems.rs | 6 +- lightyear/src/shared/config.rs | 1 - lightyear/src/shared/log.rs | 5 +- lightyear/src/shared/ping/manager.rs | 5 +- lightyear/src/shared/ping/message.rs | 4 - lightyear/src/shared/ping/store.rs | 1 - .../src/shared/replication/components.rs | 16 +- .../src/shared/replication/entity_map.rs | 11 +- lightyear/src/shared/replication/manager.rs | 437 +++++++----------- lightyear/src/shared/replication/mod.rs | 36 +- lightyear/src/shared/replication/resources.rs | 6 +- lightyear/src/shared/replication/systems.rs | 12 +- .../src/tests/examples/connection_soak.rs | 4 +- lightyear/src/tests/stepper.rs | 2 - lightyear/src/transport/io.rs | 7 +- .../src/transport/webtransport/server.rs | 11 +- lightyear/src/utils/bevy.rs | 3 +- lightyear/src/utils/named.rs | 2 +- macros/src/component.rs | 28 +- 66 files changed, 473 insertions(+), 613 deletions(-) create mode 100644 lightyear/src/client/interpolation/resource.rs create mode 100644 lightyear/src/client/prediction/resource.rs diff --git a/NOTES.md b/NOTES.md index 05441b2f1..474f852f0 100644 --- a/NOTES.md +++ b/NOTES.md @@ -12,10 +12,11 @@ because start tick or end tick are not updated correctly in some edge cases. -- add PredictionGroup and InterpolationGroup. +- add PredictionGroup and InterpolationGroup? - on top of ReplicationGroup? - or do we just re-use the replication group id (that usually will have a remote entity id) and use it to see the prediction/interpolation group? - then we add the prediction group id on the Confirmed or Predicted components? + - when we receive a replicated group with ShouldBePredicted, we udpate the replication graph of the prediction group. - Then we don't really need the Confirmed/Predicted components anymore, we could just have resources on the Prediction or Interpolation plugin - The resource needs: - confirmed<->predicted mapping @@ -25,7 +26,27 @@ - for each entity, fetch the confirmed/predicted entity - do entity mapping if needed - users can add their own entities in the prediction group (even if thre ) +- examples: + - a shooter, we shoot bullets. the bullets should be in our prediction group? + I guess it's not needed if we don't do rollback for those bullets, i.e. we don't give them a Predicted component. + Or we could create the bullet, make it part of the entity group; the server will create the bullet a bit later. + When the bullet gets replicated on client; we should be able to map the Confirmed to the predicted bullet; so we don't spawn a new predicted. + (in practice, for important stuff, we would just wait for the server replication to spawn the entity (instead of spawning it on client and then deleting it if the server version doesn't spawn it?, and for non-important stuff we would just spawn a short-lived entity that is not predicted.) + - a character has HasWeapon(Entity), weapon has HasParent(Entity) which forms a cycle. I guess instead of creating this graph of deps, + we should just deal with all spawns first separately! Same for prediction, we first do all spawns first + + +- TODO: Give an option for rollback to choose how to perform the rollback! + - the default option is to snapback instantly to the rollback state. + - another option is: snapback to rollback state, perform rollback, then tell the user the previous predicted state and the new predicted state. + for example they could choose to lerp over several frames from the [old to new] (i.e correct only 10% of the way). + this would cause many consecutive rollback frames, but smoother corrections. + - register a component RollbackResult { + // use option because the component could have gotten removed + old: Option, + new: Option, + } - DEBUGGING REPLICATION BOX: diff --git a/lightyear/src/channel/builder.rs b/lightyear/src/channel/builder.rs index 03a311b4a..5db701488 100644 --- a/lightyear/src/channel/builder.rs +++ b/lightyear/src/channel/builder.rs @@ -1,7 +1,8 @@ //! This module contains the [`Channel`] trait -use lightyear_macros::ChannelInternal; use std::time::Duration; +use lightyear_macros::ChannelInternal; + use crate::channel::receivers::ordered_reliable::OrderedReliableReceiver; use crate::channel::receivers::sequenced_reliable::SequencedReliableReceiver; use crate::channel::receivers::sequenced_unreliable::SequencedUnreliableReceiver; diff --git a/lightyear/src/channel/receivers/mod.rs b/lightyear/src/channel/receivers/mod.rs index 807d0114a..c9deef62a 100644 --- a/lightyear/src/channel/receivers/mod.rs +++ b/lightyear/src/channel/receivers/mod.rs @@ -1,8 +1,8 @@ -use enum_dispatch::enum_dispatch; - -use crate::packet::message::{MessageContainer, SingleData}; +use crate::packet::message::MessageContainer; +use crate::packet::message::{FragmentData, SingleData}; use crate::shared::tick_manager::TickManager; use crate::shared::time_manager::TimeManager; +use enum_dispatch::enum_dispatch; /// Utilities to receive a Message from multiple fragment packets pub(crate) mod fragment_receiver; diff --git a/lightyear/src/channel/senders/fragment_ack_receiver.rs b/lightyear/src/channel/senders/fragment_ack_receiver.rs index e7dae5688..8906f214f 100644 --- a/lightyear/src/channel/senders/fragment_ack_receiver.rs +++ b/lightyear/src/channel/senders/fragment_ack_receiver.rs @@ -1,11 +1,8 @@ -use bevy::utils::HashMap; - use anyhow::Result; -use bytes::Bytes; +use bevy::utils::HashMap; use tracing::{error, trace}; -use crate::packet::message::{FragmentData, FragmentIndex, MessageAck, MessageId, SingleData}; -use crate::packet::packet::FRAGMENT_SIZE; +use crate::packet::message::{FragmentIndex, MessageId}; use crate::shared::time_manager::WrappedTime; /// `FragmentReceiver` is used to reconstruct fragmented messages @@ -103,8 +100,6 @@ impl FragmentAckTracker { #[cfg(test)] mod tests { - use crate::channel::senders::fragment_sender::FragmentSender; - use super::*; #[test] diff --git a/lightyear/src/channel/senders/mod.rs b/lightyear/src/channel/senders/mod.rs index 098d290f5..79122bbab 100644 --- a/lightyear/src/channel/senders/mod.rs +++ b/lightyear/src/channel/senders/mod.rs @@ -1,13 +1,11 @@ -use std::collections::VecDeque; - -use bytes::Bytes; -use crossbeam_channel::Receiver; -use enum_dispatch::enum_dispatch; - use crate::packet::message::{FragmentData, MessageAck, MessageId, SingleData}; use crate::shared::ping::manager::PingManager; use crate::shared::tick_manager::TickManager; use crate::shared::time_manager::TimeManager; +use bytes::Bytes; +use crossbeam_channel::Receiver; +use enum_dispatch::enum_dispatch; +use std::collections::VecDeque; pub(crate) mod fragment_ack_receiver; pub(crate) mod fragment_sender; diff --git a/lightyear/src/channel/senders/reliable.rs b/lightyear/src/channel/senders/reliable.rs index e56977ad5..b39ac750a 100644 --- a/lightyear/src/channel/senders/reliable.rs +++ b/lightyear/src/channel/senders/reliable.rs @@ -4,7 +4,7 @@ use std::time::Duration; use bytes::Bytes; use crossbeam_channel::Receiver; -use tracing::{info, trace}; +use tracing::trace; use crate::channel::builder::ReliableSettings; use crate::channel::senders::fragment_sender::FragmentSender; diff --git a/lightyear/src/channel/senders/unordered_unreliable_with_acks.rs b/lightyear/src/channel/senders/unordered_unreliable_with_acks.rs index 21d877755..f54d0c4b6 100644 --- a/lightyear/src/channel/senders/unordered_unreliable_with_acks.rs +++ b/lightyear/src/channel/senders/unordered_unreliable_with_acks.rs @@ -1,16 +1,15 @@ -use bevy::utils::HashMap; use std::collections::VecDeque; use bytes::Bytes; +use crossbeam_channel::{Receiver, Sender}; use crate::channel::senders::fragment_ack_receiver::FragmentAckReceiver; use crate::channel::senders::fragment_sender::FragmentSender; use crate::channel::senders::ChannelSend; -use crate::packet::message::{FragmentData, FragmentIndex, MessageAck, MessageId, SingleData}; +use crate::packet::message::{FragmentData, MessageAck, MessageId, SingleData}; use crate::shared::ping::manager::PingManager; use crate::shared::tick_manager::TickManager; use crate::shared::time_manager::{TimeManager, WrappedTime}; -use crossbeam_channel::{Receiver, Sender}; const DISCARD_AFTER: chrono::Duration = chrono::Duration::milliseconds(3000); @@ -130,10 +129,9 @@ impl ChannelSend for UnorderedUnreliableWithAcksSender { #[cfg(test)] mod tests { - use super::*; - use crate::channel::senders::fragment_ack_receiver::FragmentAckTracker; use crate::packet::packet::FRAGMENT_SIZE; - use crossbeam_channel::TryRecvError; + + use super::*; #[test] fn test_receive_ack() { diff --git a/lightyear/src/client/components.rs b/lightyear/src/client/components.rs index ea6e4f5be..f7444f75a 100644 --- a/lightyear/src/client/components.rs +++ b/lightyear/src/client/components.rs @@ -1,11 +1,11 @@ /*! Defines components that are used for the client-side prediction and interpolation */ +use std::fmt::Debug; -use crate::prelude::{Named, TypeNamed}; -use bevy::ecs::component::TableStorage; use bevy::prelude::{Component, Entity}; -use std::fmt::{Debug, Formatter}; + +use crate::prelude::{MapEntities, Named}; /// Marks an entity that contains the server-updates that are received from the Server /// (this entity is a copy of Predicted that is RTT ticks behind) @@ -15,7 +15,7 @@ pub struct Confirmed { pub interpolated: Option, } -pub trait SyncComponent: Component + Clone + PartialEq + Named { +pub trait SyncComponent: Component + Clone + PartialEq + Named + for<'a> MapEntities<'a> { fn mode() -> ComponentSyncMode; } diff --git a/lightyear/src/client/config.rs b/lightyear/src/client/config.rs index 1c68dcf26..c1749117e 100644 --- a/lightyear/src/client/config.rs +++ b/lightyear/src/client/config.rs @@ -6,8 +6,6 @@ use crate::client::interpolation::plugin::InterpolationConfig; use crate::client::prediction::plugin::PredictionConfig; use crate::client::sync::SyncConfig; use crate::shared::config::SharedConfig; -use crate::transport::io::IoConfig; - use crate::shared::ping::manager::PingConfig; #[derive(Clone)] diff --git a/lightyear/src/client/connection.rs b/lightyear/src/client/connection.rs index 1c5ece6f8..46d5f67b6 100644 --- a/lightyear/src/client/connection.rs +++ b/lightyear/src/client/connection.rs @@ -3,20 +3,14 @@ use std::time::Duration; use anyhow::Result; -use bevy::prelude::World; -use tracing::{debug, info, trace}; +use tracing::{debug, trace}; -use crate::channel::builder::PingChannel; use crate::client::sync::SyncConfig; -use crate::connection::events::ConnectionEvents; -use crate::connection::message::ProtocolMessage; use crate::inputs::input_buffer::InputBuffer; -use crate::packet::packet_manager::Payload; -use crate::protocol::channel::{ChannelKind, ChannelRegistry}; +use crate::protocol::channel::ChannelRegistry; use crate::protocol::Protocol; use crate::serialize::reader::ReadBuffer; -use crate::shared::ping::manager::{PingConfig, PingManager}; -use crate::shared::ping::message::SyncMessage; +use crate::shared::ping::manager::PingConfig; use crate::shared::tick_manager::Tick; use crate::shared::tick_manager::TickManager; use crate::shared::time_manager::TimeManager; diff --git a/lightyear/src/client/events.rs b/lightyear/src/client/events.rs index ef7ca8272..77e4d1d13 100644 --- a/lightyear/src/client/events.rs +++ b/lightyear/src/client/events.rs @@ -1,7 +1,6 @@ //! Wrapper around [`ConnectionEvents`] that adds client-specific functionality //! use crate::connection::events::ConnectionEvents; -use crate::prelude::Tick; use crate::protocol::Protocol; pub struct ClientEvents { diff --git a/lightyear/src/client/input.rs b/lightyear/src/client/input.rs index 45c11b313..53bac8eaf 100644 --- a/lightyear/src/client/input.rs +++ b/lightyear/src/client/input.rs @@ -1,5 +1,4 @@ //! Handles client-generated inputs -use anyhow::Context; use bevy::prelude::{ not, App, EventReader, EventWriter, FixedUpdate, IntoSystemConfigs, IntoSystemSetConfigs, Plugin, PostUpdate, Res, ResMut, SystemSet, diff --git a/lightyear/src/client/interpolation/despawn.rs b/lightyear/src/client/interpolation/despawn.rs index f652c90bf..1a7f43b2b 100644 --- a/lightyear/src/client/interpolation/despawn.rs +++ b/lightyear/src/client/interpolation/despawn.rs @@ -1,10 +1,9 @@ -use std::collections::HashMap; - -use bevy::prelude::{Commands, Entity, EventReader, Query, RemovedComponents, ResMut, Resource}; +use bevy::prelude::{Commands, EventReader, Query, RemovedComponents, ResMut}; use crate::client::components::{Confirmed, SyncComponent}; use crate::client::interpolation::interpolate::InterpolateStatus; use crate::client::interpolation::interpolation_history::ConfirmedHistory; +use crate::client::interpolation::resource::InterpolationManager; use crate::shared::events::ComponentRemoveEvent; // Despawn logic: @@ -17,13 +16,6 @@ use crate::shared::events::ComponentRemoveEvent; // - TODO: despawning another client entity as a consequence from prediction, but we want to roll that back: // - maybe we don't do it, and we wait until we are sure (confirmed despawn) before actually despawning the entity -#[derive(Resource, Default)] -/// Mapping from confirmed entities to interpolated entities -/// Needed to despawn interpolated entities when the confirmed entity gets despawned -pub struct InterpolationMapping { - pub confirmed_to_interpolated: HashMap, -} - /// Remove the component from interpolated entities when it gets removed from confirmed pub(crate) fn removed_components( mut commands: Commands, @@ -46,12 +38,16 @@ pub(crate) fn removed_components( /// Despawn interpolated entities when the confirmed entity gets despawned /// TODO: we should despawn interpolated only when it reaches the latest confirmed snapshot? pub(crate) fn despawn_interpolated( + mut manager: ResMut, mut commands: Commands, - mut mapping: ResMut, mut query: RemovedComponents, ) { - for entity in query.read() { - if let Some(interpolated) = mapping.confirmed_to_interpolated.remove(&entity) { + for confirmed_entity in query.read() { + if let Some(interpolated) = manager + .interpolated_entity_map + .remote_to_interpolated + .remove(&confirmed_entity) + { if let Some(mut entity_mut) = commands.get_entity(interpolated) { entity_mut.despawn(); } diff --git a/lightyear/src/client/interpolation/interpolate.rs b/lightyear/src/client/interpolation/interpolate.rs index ad19d155c..41e444587 100644 --- a/lightyear/src/client/interpolation/interpolate.rs +++ b/lightyear/src/client/interpolation/interpolate.rs @@ -1,5 +1,5 @@ use bevy::prelude::{Component, Query, ResMut}; -use tracing::{info, trace, warn}; +use tracing::trace; use crate::client::components::{ComponentSyncMode, SyncComponent}; use crate::client::interpolation::interpolation_history::ConfirmedHistory; diff --git a/lightyear/src/client/interpolation/interpolation_history.rs b/lightyear/src/client/interpolation/interpolation_history.rs index d955a7e5f..fff75eda0 100644 --- a/lightyear/src/client/interpolation/interpolation_history.rs +++ b/lightyear/src/client/interpolation/interpolation_history.rs @@ -1,14 +1,14 @@ use std::ops::Deref; use bevy::prelude::{ - Commands, Component, DetectChanges, Entity, EventReader, Query, Ref, Res, ResMut, With, Without, + Commands, Component, DetectChanges, Entity, Query, Ref, Res, ResMut, With, Without, }; -use tracing::{debug, error, info, trace}; +use tracing::{debug, error, trace}; use crate::client::components::Confirmed; use crate::client::components::{ComponentSyncMode, SyncComponent}; -use crate::client::events::ComponentUpdateEvent; use crate::client::interpolation::interpolate::InterpolateStatus; +use crate::client::interpolation::resource::InterpolationManager; use crate::client::interpolation::Interpolated; use crate::client::resource::Client; use crate::protocol::Protocol; @@ -76,6 +76,7 @@ impl ConfirmedHistory { // TODO: maybe add the component history on the Confirmed entity instead of Interpolated? would make more sense maybe pub(crate) fn add_component_history( + manager: Res, mut commands: Commands, client: ResMut>, interpolated_entities: Query>, With)>, @@ -90,11 +91,14 @@ pub(crate) fn add_component_history( commands.get_entity(interpolated_entity).unwrap(); // insert history let history = ConfirmedHistory::::new(); + // map any entities from confirmed to interpolated + let mut new_component = confirmed_component.deref().clone(); + new_component.map_entities(Box::new(&manager.interpolated_entity_map)); match T::mode() { ComponentSyncMode::Full => { debug!("spawn interpolation history"); interpolated_entity_mut.insert(( - // confirmed_component.deref().clone(), + new_component, history, InterpolateStatus:: { start: None, @@ -103,10 +107,11 @@ pub(crate) fn add_component_history( }, )); } - _ => { + ComponentSyncMode::Once | ComponentSyncMode::Simple => { debug!("copy interpolation component"); - // interpolated_entity_mut.insert(confirmed_component.deref().clone()); + interpolated_entity_mut.insert(new_component); } + ComponentSyncMode::None => {} } } } @@ -117,6 +122,7 @@ pub(crate) fn add_component_history( /// When we receive a server update, we need to store it in the confirmed history, /// or update the interpolated component directly if InterpolatedComponentMode::Sync pub(crate) fn apply_confirmed_update( + manager: ResMut, client: Res>, mut interpolated_entities: Query< // TODO: handle missing T? @@ -127,8 +133,8 @@ pub(crate) fn apply_confirmed_update( ) { for (confirmed_entity, confirmed, confirmed_component) in confirmed_entities.iter() { if let Some(p) = confirmed.interpolated { - if confirmed_component.is_changed() { - if let Ok((interpolated_component, history_option)) = + if confirmed_component.is_changed() && !confirmed_component.is_added() { + if let Ok((mut interpolated_component, history_option)) = interpolated_entities.get_mut(p) { match T::mode() { @@ -150,17 +156,19 @@ pub(crate) fn apply_confirmed_update( ); continue; }; - info!(component = ?confirmed_component.name(), tick = ?channel.latest_tick, "adding confirmed update to history"); + // map any entities from confirmed to predicted + let mut component = confirmed_component.deref().clone(); + component.map_entities(Box::new(&manager.interpolated_entity_map)); + trace!(component = ?component.name(), tick = ?channel.latest_tick, "adding confirmed update to history"); // assign the history at the value that the entity currently is - // TODO: think about mapping entities! - history - .buffer - .add_item(channel.latest_tick, confirmed_component.deref().clone()); + history.buffer.add_item(channel.latest_tick, component); } // for sync-components, we just match the confirmed component ComponentSyncMode::Simple => { - // TODO: think about mapping entities! - // *interpolated_component = confirmed_component.deref().clone(); + // map any entities from confirmed to predicted + let mut component = confirmed_component.deref().clone(); + component.map_entities(Box::new(&manager.interpolated_entity_map)); + *interpolated_component = component; } _ => {} } diff --git a/lightyear/src/client/interpolation/mod.rs b/lightyear/src/client/interpolation/mod.rs index f43c9cfbc..3307f8cdc 100644 --- a/lightyear/src/client/interpolation/mod.rs +++ b/lightyear/src/client/interpolation/mod.rs @@ -8,32 +8,15 @@ pub use interpolate::InterpolateStatus; pub use interpolation_history::ConfirmedHistory; pub use plugin::{add_interpolation_systems, add_prepare_interpolation_systems}; -use crate::client::components::{ComponentSyncMode, Confirmed, SyncComponent}; -use crate::client::interpolation::despawn::InterpolationMapping; +use crate::client::components::{Confirmed, SyncComponent}; +use crate::client::interpolation::resource::InterpolationManager; use crate::shared::replication::components::ShouldBeInterpolated; mod despawn; mod interpolate; pub mod interpolation_history; pub mod plugin; - -/// This module handles doing snapshot interpolations for entities controlled by other clients. -/// -/// We want to receive smooth updates for the other players' actions -/// But we receive their actions at a given timestep that might not match the physics timestep. - -/// Which means we can do one of two things: -/// - apply client prediction for all players -/// - apply client prediction for the controlled player, and snapshot interpolation for the other players - -// TODO: -// - same thing, add InterpolationTarget on Replicate, which translates into ShouldBeInterpolated. -// - if we see that on a confirmed entity, then we create a Interpolated entity. -// - that entity will keep a component history (along with the ticks), and we will interpolate between the last 2 components. -// - re-use component history ? - -// TODO: maybe merge this with PredictedComponent? -// basically it is a HistoryComponent. And we can have modes for Prediction or Interpolation +mod resource; pub trait InterpFn { fn lerp(start: C, other: C, t: f32) -> C; @@ -66,31 +49,6 @@ pub trait InterpolatedComponent: SyncComponent { } } -// pub trait InterpolatedComponent: SyncComponent + Sized { -// type Fn: InterpFn; -// /// Which interpolation function to use -// /// By default, it will be a linear interpolation -// fn lerp_mode() -> LerpMode; -// -// fn lerp_linear(start: Self, other: Self, t: f32) -> Self -// where -// Self: Mul + Add, -// { -// start * (1.0 - t) + other * t -// } -// -// fn lerp_custom(start: Self, other: Self, t: f32, lerp: fn(Self, Self, f32) -> Self) -> Self { -// lerp(start, other, t) -// } -// } - -// #[derive(Debug)] -// pub enum LerpMode { -// Linear, -// // TODO: change this to a trait object? -// Custom(fn(C, C, f32) -> C), -// } - /// Marks an entity that is being interpolated by the client #[derive(Component, Debug)] pub struct Interpolated { @@ -100,12 +58,11 @@ pub struct Interpolated { // - despawn immediately all components // - leave the entity alive until the confirmed entity catches up to it and then it gets removed. // - or do this only for certain components (audio, animation, particles..) -> mode on PredictedComponent - // rollback_state: RollbackState, } pub fn spawn_interpolated_entity( + mut manager: ResMut, mut commands: Commands, - mut mapping: ResMut, mut confirmed_entities: Query<(Entity, Option<&mut Confirmed>), Added>, ) { for (confirmed_entity, confirmed) in confirmed_entities.iter_mut() { @@ -113,12 +70,15 @@ pub fn spawn_interpolated_entity( let interpolated_entity_mut = commands.spawn(Interpolated { confirmed_entity }); let interpolated = interpolated_entity_mut.id(); + // update the entity mapping + manager + .interpolated_entity_map + .remote_to_interpolated + .insert(confirmed_entity, interpolated); + // add Confirmed to the confirmed entity // safety: we know the entity exists let mut confirmed_entity_mut = commands.get_entity(confirmed_entity).unwrap(); - mapping - .confirmed_to_interpolated - .insert(confirmed_entity, interpolated); if let Some(mut confirmed) = confirmed { confirmed.interpolated = Some(interpolated); } else { diff --git a/lightyear/src/client/interpolation/plugin.rs b/lightyear/src/client/interpolation/plugin.rs index 63e931d74..bb8d1ddbc 100644 --- a/lightyear/src/client/interpolation/plugin.rs +++ b/lightyear/src/client/interpolation/plugin.rs @@ -2,15 +2,13 @@ use std::marker::PhantomData; use std::time::Duration; use bevy::prelude::{ - apply_deferred, App, IntoSystemConfigs, IntoSystemSetConfigs, Plugin, PostUpdate, PreUpdate, - SystemSet, + apply_deferred, App, IntoSystemConfigs, IntoSystemSetConfigs, Plugin, PostUpdate, SystemSet, }; use crate::client::components::SyncComponent; -use crate::client::interpolation::despawn::{ - despawn_interpolated, removed_components, InterpolationMapping, -}; +use crate::client::interpolation::despawn::{despawn_interpolated, removed_components}; use crate::client::interpolation::interpolate::{interpolate, update_interpolate_status}; +use crate::client::interpolation::resource::InterpolationManager; use crate::protocol::component::ComponentProtocol; use crate::protocol::Protocol; use crate::shared::sets::MainSet; @@ -179,7 +177,7 @@ impl Plugin for InterpolationPlugin

{ } // RESOURCES - app.init_resource::(); + app.init_resource::(); // SETS app.configure_sets( PostUpdate, @@ -213,7 +211,7 @@ impl Plugin for InterpolationPlugin

{ app.add_systems( PostUpdate, ( - // spawn_interpolated_entity.in_set(InterpolationSet::SpawnInterpolation), + spawn_interpolated_entity.in_set(InterpolationSet::SpawnInterpolation), despawn_interpolated.in_set(InterpolationSet::Despawn), ), ); diff --git a/lightyear/src/client/interpolation/resource.rs b/lightyear/src/client/interpolation/resource.rs new file mode 100644 index 000000000..ab2c88505 --- /dev/null +++ b/lightyear/src/client/interpolation/resource.rs @@ -0,0 +1,18 @@ +//! Defines bevy resources needed for Interpolation +use bevy::prelude::Resource; + +use crate::shared::replication::entity_map::InterpolatedEntityMap; + +#[derive(Resource, Default)] +pub struct InterpolationManager { + /// Map between remote and predicted entities + pub interpolated_entity_map: InterpolatedEntityMap, +} + +impl InterpolationManager { + pub fn new() -> Self { + Self { + interpolated_entity_map: Default::default(), + } + } +} diff --git a/lightyear/src/client/plugin.rs b/lightyear/src/client/plugin.rs index aa2bfe9ac..67d895051 100644 --- a/lightyear/src/client/plugin.rs +++ b/lightyear/src/client/plugin.rs @@ -2,7 +2,6 @@ use std::ops::DerefMut; use std::sync::Mutex; -use crate::client::resource::{Authentication, Client}; use bevy::prelude::IntoSystemSetConfigs; use bevy::prelude::{ apply_deferred, not, resource_exists, App, Condition, FixedUpdate, IntoSystemConfigs, @@ -14,12 +13,12 @@ use crate::client::input::InputPlugin; use crate::client::interpolation::plugin::InterpolationPlugin; use crate::client::prediction::plugin::{is_in_rollback, PredictionPlugin}; use crate::client::prediction::Rollback; +use crate::client::resource::{Authentication, Client}; use crate::client::systems::{is_ready_to_send, receive, send, sync_update}; use crate::protocol::component::ComponentProtocol; use crate::protocol::message::MessageProtocol; use crate::protocol::Protocol; use crate::shared::plugin::SharedPlugin; -use crate::shared::replication::resources::ReplicationData; use crate::shared::sets::{FixedUpdateSet, MainSet}; use crate::shared::systems::tick::increment_tick; use crate::transport::io::Io; diff --git a/lightyear/src/client/prediction/mod.rs b/lightyear/src/client/prediction/mod.rs index 2961294e4..34c3369b6 100644 --- a/lightyear/src/client/prediction/mod.rs +++ b/lightyear/src/client/prediction/mod.rs @@ -1,7 +1,7 @@ //! Handles client-side prediction use std::fmt::Debug; -use bevy::prelude::{Added, Commands, Component, Entity, Query, Resource}; +use bevy::prelude::{Added, Commands, Component, Entity, Query, ResMut, Resource}; use tracing::info; pub use despawn::{PredictionCommandsExt, PredictionDespawnMarker}; @@ -9,6 +9,7 @@ pub use plugin::add_prediction_systems; pub use predicted_history::{ComponentState, PredictionHistory}; use crate::client::components::{ComponentSyncMode, Confirmed}; +use crate::client::prediction::resource::PredictionManager; use crate::shared::replication::components::ShouldBePredicted; use crate::shared::tick_manager::Tick; @@ -28,6 +29,7 @@ use crate::shared::tick_manager::Tick; mod despawn; pub mod plugin; pub mod predicted_history; +mod resource; pub(crate) mod rollback; /// Marks an entity that is being predicted by the client @@ -57,20 +59,12 @@ pub enum RollbackState { }, } -// What we want to achieve: -// - client defines a set of components that are predicted. -// - several cases: -// - not owner prediction: we spawn ball on server, we choose on server to add [Confirmed] component. -// Confirmed gets replicated, we spawn a predicted ball on client for the last server tick, we quickly fast-forward it via rollback? -// - owner prediction: we spawn player on server, we choose on server to add [Confirmed] component. -// Confirmed gets replicated, we spawn a predicted player on client for the last server tick, we quickly fast-forward it with rollback (and apply buffer inputs) -// -// - other approach: -// - we know on the client which entity to predict (for example ball + player), we spawn the predicted on client right away. seems worse. -// -// - what's annoying is that Confirmed contains some client-specific information that will get replicated. Maybe we can create a specific ShouldBeReplicated marker for this. -// for now, the easiest option would be to just replicate the entirety of Confirmed ? +/// Spawn a predicted entity for each confirmed entity that has the `ShouldBePredicted` component added +/// The `Confirmed` entity could already exist because we share for prediction and interpolation. +// TODO: (although normally an entity shouldn't be both predicted and interpolated, so should we +// instead panic if we find an entity that is both predicted and interpolated?) pub fn spawn_predicted_entity( + mut manager: ResMut, mut commands: Commands, mut confirmed_entities: Query<(Entity, Option<&mut Confirmed>), Added>, ) { @@ -79,6 +73,12 @@ pub fn spawn_predicted_entity( let predicted_entity_mut = commands.spawn(Predicted { confirmed_entity }); let predicted_entity = predicted_entity_mut.id(); + // update the entity mapping + manager + .predicted_entity_map + .remote_to_predicted + .insert(confirmed_entity, predicted_entity); + // add Confirmed to the confirmed entity // safety: we know the entity exists let mut confirmed_entity_mut = commands.get_entity(confirmed_entity).unwrap(); diff --git a/lightyear/src/client/prediction/plugin.rs b/lightyear/src/client/prediction/plugin.rs index 6d1e21e44..e8e218905 100644 --- a/lightyear/src/client/prediction/plugin.rs +++ b/lightyear/src/client/prediction/plugin.rs @@ -10,6 +10,7 @@ use crate::client::prediction::despawn::{ remove_component_for_despawn_predicted, remove_despawn_marker, }; use crate::client::prediction::predicted_history::update_prediction_history; +use crate::client::prediction::resource::PredictionManager; use crate::prelude::Named; use crate::protocol::component::ComponentProtocol; use crate::protocol::Protocol; @@ -147,6 +148,7 @@ impl Plugin for PredictionPlugin

{ P::Components::add_prediction_systems(app); // RESOURCES + app.init_resource::(); app.insert_resource(Rollback { state: RollbackState::Default, }); @@ -184,10 +186,10 @@ impl Plugin for PredictionPlugin

{ ); // no need, since we spawn predicted entities/components in replication - // app.add_systems( - // PreUpdate, - // spawn_predicted_entity.in_set(PredictionSet::SpawnPrediction), - // ); + app.add_systems( + PreUpdate, + spawn_predicted_entity.in_set(PredictionSet::SpawnPrediction), + ); // 2. (in prediction_systems) add ComponentHistory and a apply_deferred after // 3. (in prediction_systems) Check if we should do rollback, clear histories and snap prediction's history to server-state // 4. Potentially do rollback diff --git a/lightyear/src/client/prediction/predicted_history.rs b/lightyear/src/client/prediction/predicted_history.rs index aef55b116..930ee7d9d 100644 --- a/lightyear/src/client/prediction/predicted_history.rs +++ b/lightyear/src/client/prediction/predicted_history.rs @@ -1,13 +1,12 @@ use std::ops::Deref; -use bevy::prelude::GamepadButtonType::C; use bevy::prelude::{ Commands, Component, DetectChanges, Entity, Query, Ref, RemovedComponents, Res, With, Without, }; -use tracing::{error, info}; +use tracing::error; use crate::client::components::SyncComponent; -use crate::client::interpolation::ConfirmedHistory; +use crate::client::prediction::resource::PredictionManager; use crate::client::resource::Client; use crate::prelude::Named; use crate::protocol::Protocol; @@ -114,8 +113,11 @@ impl PredictionHistory { // TODO: add more options: // - copy component and add component history (for rollback) // - copy component to history and don't add component + +// TODO: only run this for SyncComponent where SyncMode != None #[allow(clippy::type_complexity)] pub fn add_component_history( + manager: Res, mut commands: Commands, client: Res>, predicted_entities: Query<(Entity, Option>), Without>>, @@ -155,6 +157,9 @@ pub fn add_component_history( // safety: we know the entity exists let mut predicted_entity_mut = commands.get_entity(predicted_entity).unwrap(); + // map any entities from confirmed to predicted + let mut new_component = confirmed_component.deref().clone(); + new_component.map_entities(Box::new(&manager.predicted_entity_map)); match T::mode() { ComponentSyncMode::Full => { // insert history, it will be quickly filled by a rollback (since it starts empty before the current client tick) @@ -163,19 +168,13 @@ pub fn add_component_history( client.tick(), ComponentState::Updated(confirmed_component.deref().clone()), ); - predicted_entity_mut.insert(history); - // TODO: we do not insert the component here because we need to map entities! - // also we already added the component during replication - // but we might to handle components that are not replicated... - // for components that are not replicated, no need to apply any mapping! - // so maybe just check if the component existed already? - // predicted_entity_mut - // .insert((confirmed_component.deref().clone(), history)); + predicted_entity_mut.insert((new_component, history)); } - _ => { + ComponentSyncMode::Once | ComponentSyncMode::Simple => { // we only sync the components once, but we don't do rollback so no need for a component history - // predicted_entity_mut.insert(confirmed_component.deref().clone()); + predicted_entity_mut.insert(new_component); } + _ => {} } } } @@ -260,6 +259,7 @@ pub fn update_prediction_history( /// When we receive a server update, we might want to apply it to the predicted entity #[allow(clippy::type_complexity)] pub(crate) fn apply_confirmed_update( + manager: Res, client: Res>, mut predicted_entities: Query< &mut T, @@ -274,7 +274,7 @@ pub(crate) fn apply_confirmed_update( let latest_server_tick = client.latest_received_server_tick(); for (confirmed_entity, confirmed_component) in confirmed_entities.iter() { if let Some(p) = confirmed_entity.predicted { - if confirmed_component.is_changed() { + if confirmed_component.is_changed() && !confirmed_component.is_added() { if let Ok(mut predicted_component) = predicted_entities.get_mut(p) { match T::mode() { ComponentSyncMode::Full => { @@ -285,7 +285,10 @@ pub(crate) fn apply_confirmed_update( } // for sync-components, we just match the confirmed component ComponentSyncMode::Simple => { - *predicted_component = confirmed_component.deref().clone(); + // map any entities from confirmed to predicted + let mut component = confirmed_component.deref().clone(); + component.map_entities(Box::new(&manager.predicted_entity_map)); + *predicted_component = component; } _ => {} } diff --git a/lightyear/src/client/prediction/resource.rs b/lightyear/src/client/prediction/resource.rs new file mode 100644 index 000000000..c25f20af1 --- /dev/null +++ b/lightyear/src/client/prediction/resource.rs @@ -0,0 +1,19 @@ +//! Defines bevy resources needed for Prediction + +use bevy::prelude::Resource; + +use crate::shared::replication::entity_map::PredictedEntityMap; + +#[derive(Resource, Default)] +pub struct PredictionManager { + /// Map between remote and predicted entities + pub predicted_entity_map: PredictedEntityMap, +} + +impl PredictionManager { + pub fn new() -> Self { + Self { + predicted_entity_map: Default::default(), + } + } +} diff --git a/lightyear/src/client/prediction/rollback.rs b/lightyear/src/client/prediction/rollback.rs index e0d97bfaa..464c0c61b 100644 --- a/lightyear/src/client/prediction/rollback.rs +++ b/lightyear/src/client/prediction/rollback.rs @@ -3,8 +3,8 @@ use std::fmt::Debug; use bevy::prelude::{ Commands, Entity, EventReader, FixedUpdate, Query, Res, ResMut, Without, World, }; -use bevy::utils::{EntityHashSet, HashSet}; -use tracing::{debug, error, info, trace, trace_span, warn}; +use bevy::utils::EntityHashSet; +use tracing::{debug, error, info, trace, trace_span}; use crate::client::components::{ComponentSyncMode, Confirmed, SyncComponent}; use crate::client::events::{ComponentInsertEvent, ComponentRemoveEvent, ComponentUpdateEvent}; diff --git a/lightyear/src/client/resource.rs b/lightyear/src/client/resource.rs index ccd95e1ea..d79b014d5 100644 --- a/lightyear/src/client/resource.rs +++ b/lightyear/src/client/resource.rs @@ -4,7 +4,7 @@ use std::time::Duration; use anyhow::Result; use bevy::prelude::{Resource, Time, Virtual, World}; -use tracing::{debug, trace}; +use tracing::trace; use crate::channel::builder::Channel; use crate::connection::events::ConnectionEvents; @@ -14,7 +14,6 @@ use crate::netcode::{ConnectToken, Key}; use crate::packet::message::Message; use crate::protocol::channel::ChannelKind; use crate::protocol::Protocol; -use crate::shared::ping::message::SyncMessage; use crate::shared::replication::manager::ReplicationManager; use crate::shared::tick_manager::TickManager; use crate::shared::tick_manager::{Tick, TickManaged}; diff --git a/lightyear/src/client/sync.rs b/lightyear/src/client/sync.rs index 8d1c5ebf0..46ec839f2 100644 --- a/lightyear/src/client/sync.rs +++ b/lightyear/src/client/sync.rs @@ -1,10 +1,8 @@ /*! Handles syncing the time between the client and the server */ -use std::pin::pin; use std::time::Duration; use bevy::prelude::Res; -use bevy::time::Stopwatch; use tracing::{debug, info, trace}; use crate::client::interpolation::plugin::InterpolationDelay; @@ -12,8 +10,6 @@ use crate::client::resource::Client; use crate::packet::packet::PacketId; use crate::protocol::Protocol; use crate::shared::ping::manager::PingManager; -use crate::shared::ping::message::{Ping, Pong}; -use crate::shared::ping::store::{PingId, PingStore}; use crate::shared::tick_manager::Tick; use crate::shared::tick_manager::TickManager; use crate::shared::time_manager::{TimeManager, WrappedTime}; diff --git a/lightyear/src/client/systems.rs b/lightyear/src/client/systems.rs index 0c6fac82e..249f0d6fc 100644 --- a/lightyear/src/client/systems.rs +++ b/lightyear/src/client/systems.rs @@ -1,8 +1,8 @@ //! Defines the client bevy systems and run conditions use bevy::prelude::{Events, Fixed, Mut, Res, ResMut, Time, Virtual, World}; -use tracing::{debug, info, trace}; +use tracing::trace; -use crate::client::events::{ConnectEvent, DisconnectEvent, EntityDespawnEvent, EntitySpawnEvent}; +use crate::client::events::{EntityDespawnEvent, EntitySpawnEvent}; use crate::client::resource::Client; use crate::connection::events::{IterEntityDespawnEvent, IterEntitySpawnEvent}; use crate::protocol::component::ComponentProtocol; diff --git a/lightyear/src/connection/events.rs b/lightyear/src/connection/events.rs index 8df539268..3bc11b3b4 100644 --- a/lightyear/src/connection/events.rs +++ b/lightyear/src/connection/events.rs @@ -3,8 +3,8 @@ use std::iter; use bevy::prelude::{Component, Entity}; -use bevy::utils::{EntityHashMap, HashMap, HashSet}; -use tracing::{info, trace}; +use bevy::utils::HashMap; +use tracing::trace; use crate::packet::message::Message; use crate::prelude::{Named, Tick}; @@ -12,7 +12,6 @@ use crate::protocol::channel::ChannelKind; use crate::protocol::component::IntoKind; use crate::protocol::message::{MessageBehaviour, MessageKind}; use crate::protocol::{EventContext, Protocol}; -use crate::shared::ping::message::{Ping, Pong, SyncMessage}; // TODO: don't make fields pub but instead make accessors #[derive(Debug)] @@ -407,9 +406,9 @@ impl IterComponentInsertEvent

for ConnectionEvents

{ #[cfg(test)] mod tests { - use super::*; use crate::tests::protocol::*; - use bevy::utils::HashSet; + + use super::*; #[test] fn test_iter_messages() { diff --git a/lightyear/src/connection/message.rs b/lightyear/src/connection/message.rs index b865779d2..51fd3e245 100644 --- a/lightyear/src/connection/message.rs +++ b/lightyear/src/connection/message.rs @@ -1,19 +1,13 @@ /*! Provides a [`ProtocolMessage`] enum that is a wrapper around all the possible messages that can be sent over the network */ -use crate::_reexport::{InputMessage, ShouldBeInterpolated, ShouldBePredicted, TickManager}; -use crate::connection::events::ConnectionEvents; -use crate::prelude::{EntityMapper, MapEntities, RemoteEntityMap, Tick}; -use crate::protocol::channel::ChannelKind; +use serde::{Deserialize, Serialize}; +use tracing::{info_span, trace}; + use crate::protocol::Protocol; use crate::shared::ping::message::SyncMessage; use crate::shared::replication::{ReplicationMessage, ReplicationMessageData}; -use crate::shared::time_manager::TimeManager; use crate::utils::named::Named; -use bevy::prelude::Entity; -use bevy::utils::EntityHashSet; -use serde::{Deserialize, Serialize}; -use tracing::{info, info_span, trace, trace_span}; // pub enum InternalMessage { // InputMessage(InputMessage), diff --git a/lightyear/src/connection/mod.rs b/lightyear/src/connection/mod.rs index 3b4924a14..37bafe622 100644 --- a/lightyear/src/connection/mod.rs +++ b/lightyear/src/connection/mod.rs @@ -1,15 +1,10 @@ /*! A connection is a wrapper that lets us send message and apply replication */ -// only public for proc macro -pub mod events; - -pub(crate) mod message; - use anyhow::Result; -use bevy::prelude::{Entity, World}; +use bevy::prelude::World; use serde::{Deserialize, Serialize}; -use tracing::{info, trace, trace_span}; +use tracing::{trace, trace_span}; use crate::channel::builder::{EntityUpdatesChannel, PingChannel}; use crate::channel::senders::ChannelSend; @@ -31,6 +26,11 @@ use crate::shared::tick_manager::TickManager; use crate::shared::time_manager::TimeManager; use crate::utils::named::Named; +// only public for proc macro +pub mod events; + +pub(crate) mod message; + /// Wrapper to send/receive messages via channels to a remote address pub struct Connection { pub ping_manager: PingManager, @@ -241,9 +241,6 @@ impl Connection

{ #[cfg(test)] mod tests { - use super::*; - use crate::tests::protocol::*; - // #[test] // fn test_notify_ack() -> Result<()> { // let protocol = protocol(); diff --git a/lightyear/src/inputs/input_buffer.rs b/lightyear/src/inputs/input_buffer.rs index deed66a7a..b82b2daf1 100644 --- a/lightyear/src/inputs/input_buffer.rs +++ b/lightyear/src/inputs/input_buffer.rs @@ -4,10 +4,9 @@ use std::fmt::Debug; use bevy::prelude::Resource; use serde::{Deserialize, Serialize}; -use crate::inputs::UserInput; use lightyear_macros::MessageInternal; -use tracing::info; +use crate::inputs::UserInput; use crate::protocol::BitSerializable; use crate::shared::tick_manager::Tick; diff --git a/lightyear/src/inputs/mod.rs b/lightyear/src/inputs/mod.rs index 830067392..c78faf220 100644 --- a/lightyear/src/inputs/mod.rs +++ b/lightyear/src/inputs/mod.rs @@ -2,11 +2,12 @@ Handles dealing with inputs (keyboard presses, mouse clicks) sent from a player (client) to server */ -/// Defines an [`InputBuffer`](input_buffer::InputBuffer) buffer to store the inputs of a player for each tick -pub mod input_buffer; +use std::fmt::Debug; use crate::protocol::BitSerializable; -use std::fmt::Debug; + +/// Defines an [`InputBuffer`](input_buffer::InputBuffer) buffer to store the inputs of a player for each tick +pub mod input_buffer; // TODO: should we request that a user input is a message? pub trait UserInput: diff --git a/lightyear/src/lib.rs b/lightyear/src/lib.rs index 16448bf72..54cad1944 100644 --- a/lightyear/src/lib.rs +++ b/lightyear/src/lib.rs @@ -15,10 +15,11 @@ You can find more information in the [book](https://cbournhonesque.github.io/lig pub mod _reexport { pub use enum_delegate; pub use enum_dispatch::enum_dispatch; + pub use paste::paste; + pub use lightyear_macros::{ component_protocol_internal, message_protocol_internal, ChannelInternal, MessageInternal, }; - pub use paste::paste; pub use crate::channel::builder::TickBufferChannel; pub use crate::channel::builder::{ @@ -109,6 +110,9 @@ pub mod prelude { pub use crate::client::sync::SyncConfig; } pub mod server { + #[cfg(feature = "webtransport")] + pub use wtransport::tls::Certificate; + pub use crate::server::config::NetcodeConfig; pub use crate::server::config::ServerConfig; pub use crate::server::events::{ @@ -118,8 +122,6 @@ pub mod prelude { pub use crate::server::plugin::{PluginConfig, ServerPlugin}; pub use crate::server::resource::Server; pub use crate::server::room::{RoomId, RoomMut, RoomRef}; - #[cfg(feature = "webtransport")] - pub use wtransport::tls::Certificate; } } diff --git a/lightyear/src/netcode/server.rs b/lightyear/src/netcode/server.rs index 3cba95d66..9e04b09de 100644 --- a/lightyear/src/netcode/server.rs +++ b/lightyear/src/netcode/server.rs @@ -7,7 +7,7 @@ use tracing::{debug, error, trace}; use crate::serialize::reader::ReadBuffer; use crate::serialize::wordbuffer::reader::ReadWordBuffer; use crate::transport::io::Io; -use crate::transport::{PacketReceiver, PacketSender, Transport}; +use crate::transport::{PacketReceiver, PacketSender}; use super::{ bytes::Bytes, diff --git a/lightyear/src/packet/header.rs b/lightyear/src/packet/header.rs index 55926dc30..956212318 100644 --- a/lightyear/src/packet/header.rs +++ b/lightyear/src/packet/header.rs @@ -1,15 +1,11 @@ -use derive_more::{AddAssign, SubAssign}; -use std::collections::{HashMap, HashSet}; -use std::thread::current; -use std::time::{Duration, Instant}; +use std::collections::HashMap; -use crate::_reexport::{ReadyBuffer, TimeManager, WrappedTime}; use bitcode::{Decode, Encode}; -use chrono::format::ParseErrorKind; use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; use serde::{Deserialize, Serialize}; -use tracing::{info, trace}; +use tracing::trace; +use crate::_reexport::{TimeManager, WrappedTime}; use crate::packet::packet::PacketId; use crate::packet::packet_type::PacketType; use crate::packet::stats_manager::PacketStatsManager; diff --git a/lightyear/src/packet/message.rs b/lightyear/src/packet/message.rs index bf888eb1e..216d70063 100644 --- a/lightyear/src/packet/message.rs +++ b/lightyear/src/packet/message.rs @@ -10,7 +10,6 @@ use crate::serialize::writer::WriteBuffer; use crate::shared::replication::entity_map::MapEntities; use crate::shared::tick_manager::Tick; use crate::utils::named::Named; -use crate::utils::wrapping_id; use crate::utils::wrapping_id::wrapping_id; // strategies to avoid copying: diff --git a/lightyear/src/packet/message_manager.rs b/lightyear/src/packet/message_manager.rs index 38978d19f..13ed6679e 100644 --- a/lightyear/src/packet/message_manager.rs +++ b/lightyear/src/packet/message_manager.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, HashMap, VecDeque}; use std::marker::PhantomData; use anyhow::{anyhow, Context}; -use tracing::{info, trace}; +use tracing::trace; use crate::channel::builder::ChannelContainer; use crate::channel::receivers::ChannelReceive; @@ -259,12 +259,7 @@ mod tests { use std::collections::HashMap; use std::time::Duration; - use serde::{Deserialize, Serialize}; - use crate::_reexport::*; - use crate::channel::builder::{ - ChannelDirection, ChannelMode, ChannelSettings, ReliableSettings, - }; use crate::packet::message::MessageId; use crate::packet::packet::FRAGMENT_SIZE; use crate::prelude::*; diff --git a/lightyear/src/packet/packet_manager.rs b/lightyear/src/packet/packet_manager.rs index 476a71d3e..577d2e8ef 100644 --- a/lightyear/src/packet/packet_manager.rs +++ b/lightyear/src/packet/packet_manager.rs @@ -1,6 +1,5 @@ use std::collections::{BTreeMap, VecDeque}; -use crate::_reexport::TimeManager; use bitcode::encoding::Gamma; use crate::netcode::MAX_PACKET_SIZE; @@ -616,7 +615,6 @@ impl PacketBuilder { #[cfg(test)] mod tests { use std::collections::{BTreeMap, VecDeque}; - use std::time::Duration; use bytes::Bytes; diff --git a/lightyear/src/packet/stats_manager.rs b/lightyear/src/packet/stats_manager.rs index 0c94e65e2..67a821ff3 100644 --- a/lightyear/src/packet/stats_manager.rs +++ b/lightyear/src/packet/stats_manager.rs @@ -1,8 +1,10 @@ +use std::time::Duration; + +use derive_more::{AddAssign, SubAssign}; +use tracing::trace; + use crate::shared::time_manager::{TimeManager, WrappedTime}; use crate::utils::ready_buffer::ReadyBuffer; -use derive_more::{AddAssign, SubAssign}; -use std::time::Duration; -use tracing::{info, trace}; type PacketStatsBuffer = ReadyBuffer; diff --git a/lightyear/src/protocol/component.rs b/lightyear/src/protocol/component.rs index f48f480ff..032a153e4 100644 --- a/lightyear/src/protocol/component.rs +++ b/lightyear/src/protocol/component.rs @@ -1,11 +1,11 @@ use std::fmt::Debug; use std::hash::Hash; -use crate::client::components::{ComponentSyncMode, SyncComponent}; use bevy::prelude::{App, Component, EntityWorldMut, World}; use serde::de::DeserializeOwned; use serde::Serialize; +use crate::client::components::ComponentSyncMode; use crate::connection::events::{ IterComponentInsertEvent, IterComponentRemoveEvent, IterComponentUpdateEvent, }; diff --git a/lightyear/src/protocol/mod.rs b/lightyear/src/protocol/mod.rs index ea93a71b3..53cb33ee8 100644 --- a/lightyear/src/protocol/mod.rs +++ b/lightyear/src/protocol/mod.rs @@ -5,11 +5,11 @@ //! Inputs, Messages and Components are all data structures that can be serialized and sent over the network. //! Channels are an abstraction over how the data will be sent over the network (reliability, ordering, etc.) +use std::fmt::Debug; + use bevy::prelude::App; -use lightyear_macros::ChannelInternal; use serde::de::DeserializeOwned; use serde::Serialize; -use std::fmt::Debug; use crate::channel::builder::{Channel, ChannelSettings}; use crate::inputs::UserInput; diff --git a/lightyear/src/server/config.rs b/lightyear/src/server/config.rs index 2e1bd9ccd..89e10cdc5 100644 --- a/lightyear/src/server/config.rs +++ b/lightyear/src/server/config.rs @@ -4,7 +4,6 @@ use std::time::Duration; use crate::netcode::Key; use crate::shared::config::SharedConfig; use crate::shared::ping::manager::PingConfig; -use crate::transport::io::IoConfig; #[derive(Clone)] pub struct NetcodeConfig { diff --git a/lightyear/src/server/connection.rs b/lightyear/src/server/connection.rs index 6bc954d22..04120ef92 100644 --- a/lightyear/src/server/connection.rs +++ b/lightyear/src/server/connection.rs @@ -1,23 +1,15 @@ //! Wrapper around [`crate::connection::Connection`] that adds server-specific functionality -use std::pin::pin; -use std::time::Duration; - -use crate::_reexport::ReadBuffer; use anyhow::Result; use bevy::ecs::component::Tick as BevyTick; use bevy::prelude::World; use tracing::trace; -use crate::channel::builder::PingChannel; +use crate::_reexport::ReadBuffer; use crate::connection::events::{ConnectionEvents, IterMessageEvent}; -use crate::connection::message::ProtocolMessage; use crate::inputs::input_buffer::{InputBuffer, InputMessage}; -use crate::packet::packet_manager::Payload; -use crate::protocol::channel::{ChannelKind, ChannelRegistry}; +use crate::protocol::channel::ChannelRegistry; use crate::protocol::Protocol; -use crate::shared::ping::manager::{PingConfig, PingManager}; -use crate::shared::ping::message::{Ping, Pong, SyncMessage}; -use crate::shared::tick_manager::TickManager; +use crate::shared::ping::manager::PingConfig; use crate::shared::time_manager::TimeManager; /// Wrapper around a [`crate::connection::Connection`] with server-specific logic diff --git a/lightyear/src/server/events.rs b/lightyear/src/server/events.rs index 14d1959d8..1fc5f89bd 100644 --- a/lightyear/src/server/events.rs +++ b/lightyear/src/server/events.rs @@ -9,7 +9,6 @@ use crate::connection::events::{ use crate::netcode::ClientId; use crate::packet::message::Message; use crate::protocol::Protocol; -use crate::shared::ping::message::{Ping, Pong}; pub struct ServerEvents { // have to handle disconnects separately because the [`ConnectionEvents`] are removed upon disconnection diff --git a/lightyear/src/server/plugin.rs b/lightyear/src/server/plugin.rs index be2d6792d..e28954287 100644 --- a/lightyear/src/server/plugin.rs +++ b/lightyear/src/server/plugin.rs @@ -17,7 +17,6 @@ use crate::server::resource::Server; use crate::server::room::RoomPlugin; use crate::server::systems::{clear_events, is_ready_to_send}; use crate::shared::plugin::SharedPlugin; -use crate::shared::replication::resources::ReplicationData; use crate::shared::replication::systems::add_replication_send_systems; use crate::shared::sets::ReplicationSet; use crate::shared::sets::{FixedUpdateSet, MainSet}; diff --git a/lightyear/src/server/resource.rs b/lightyear/src/server/resource.rs index 418c6019d..a86ee4508 100644 --- a/lightyear/src/server/resource.rs +++ b/lightyear/src/server/resource.rs @@ -4,12 +4,12 @@ use std::collections::HashMap; use std::net::SocketAddr; use std::time::Duration; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; use bevy::ecs::component::Tick as BevyTick; use bevy::prelude::{Entity, Resource, World}; use bevy::utils::HashSet; use crossbeam_channel::Sender; -use tracing::{debug, debug_span, info, trace, trace_span, warn}; +use tracing::{debug, debug_span, info, trace, trace_span}; use crate::channel::builder::Channel; use crate::inputs::input_buffer::InputBuffer; @@ -18,8 +18,6 @@ use crate::packet::message::Message; use crate::protocol::channel::ChannelKind; use crate::protocol::Protocol; use crate::server::room::{RoomId, RoomManager, RoomMut, RoomRef}; -use crate::shared::ping::manager::PingManager; -use crate::shared::ping::message::SyncMessage; use crate::shared::replication::components::{NetworkTarget, Replicate}; use crate::shared::replication::components::{ShouldBeInterpolated, ShouldBePredicted}; use crate::shared::replication::ReplicationSend; diff --git a/lightyear/src/server/room.rs b/lightyear/src/server/room.rs index b4958efd0..2ecd60262 100644 --- a/lightyear/src/server/room.rs +++ b/lightyear/src/server/room.rs @@ -1,17 +1,17 @@ -use crate::netcode::ClientId; -use crate::prelude::{MainSet, Replicate, ReplicationSet}; -use crate::protocol::Protocol; -use crate::server::resource::Server; -use crate::server::systems::is_ready_to_send; -use crate::shared::replication::components::DespawnTracker; -use crate::utils::wrapping_id::wrapping_id; use bevy::app::App; use bevy::prelude::{ Entity, IntoSystemConfigs, IntoSystemSetConfigs, Plugin, PostUpdate, Query, RemovedComponents, Res, ResMut, Resource, SystemSet, }; use bevy::utils::{HashMap, HashSet}; -use tracing::info; + +use crate::netcode::ClientId; +use crate::prelude::{Replicate, ReplicationSet}; +use crate::protocol::Protocol; +use crate::server::resource::Server; +use crate::server::systems::is_ready_to_send; +use crate::shared::replication::components::DespawnTracker; +use crate::utils::wrapping_id::wrapping_id; // Id for a room, used to perform interest management // An entity will be replicated to a client only if they are in the same room @@ -448,15 +448,18 @@ fn clean_entity_despawns( #[cfg(test)] mod tests { - use super::*; + use std::time::Duration; + + use bevy::ecs::system::RunSystemOnce; + use bevy::prelude::Events; + use crate::prelude::client::*; use crate::prelude::*; use crate::shared::replication::components::ReplicationMode; use crate::tests::protocol::*; use crate::tests::stepper::{BevyStepper, Step}; - use bevy::ecs::system::RunSystemOnce; - use bevy::prelude::{EventReader, Events}; - use std::time::Duration; + + use super::*; fn setup() -> BevyStepper { let frame_duration = Duration::from_millis(10); diff --git a/lightyear/src/server/systems.rs b/lightyear/src/server/systems.rs index bb2bc1aea..a70886f35 100644 --- a/lightyear/src/server/systems.rs +++ b/lightyear/src/server/systems.rs @@ -1,14 +1,12 @@ //! Defines the server bevy systems and run conditions use bevy::prelude::{Events, Mut, Res, ResMut, Time, World}; -use tracing::{debug, error, info, trace}; +use tracing::{debug, error, trace}; use crate::connection::events::{IterEntityDespawnEvent, IterEntitySpawnEvent}; -use crate::netcode::ClientId; use crate::protocol::message::MessageProtocol; use crate::protocol::Protocol; use crate::server::events::{ConnectEvent, DisconnectEvent, EntityDespawnEvent, EntitySpawnEvent}; use crate::server::resource::Server; -use crate::shared::replication::resources::ReplicationData; use crate::shared::replication::ReplicationSend; pub(crate) fn receive(world: &mut World) { @@ -90,6 +88,8 @@ pub(crate) fn send(mut server: ResMut>) { server.send_packets().unwrap(); server.new_clients.clear(); + + // TODO: clear the dependency graph for replication groups send } pub(crate) fn clear_events(mut server: ResMut>) { diff --git a/lightyear/src/shared/config.rs b/lightyear/src/shared/config.rs index d6bb901b2..658a4e2ff 100644 --- a/lightyear/src/shared/config.rs +++ b/lightyear/src/shared/config.rs @@ -2,7 +2,6 @@ use std::time::Duration; use crate::shared::log::LogConfig; - use crate::shared::tick_manager::TickConfig; #[derive(Clone)] diff --git a/lightyear/src/shared/log.rs b/lightyear/src/shared/log.rs index 141fc0262..5f5ca79b2 100644 --- a/lightyear/src/shared/log.rs +++ b/lightyear/src/shared/log.rs @@ -6,12 +6,9 @@ use bevy::prelude::{App, Plugin}; use metrics_tracing_context::{MetricsLayer, TracingContextLayer}; #[cfg(feature = "metrics")] use metrics_util::layers::Layer; - -use tracing::{warn, Level}; +use tracing::Level; use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter}; -use tracing_log::LogTracer; - /// Adds logging to Apps. /// /// # Panics diff --git a/lightyear/src/shared/ping/manager.rs b/lightyear/src/shared/ping/manager.rs index aa3743bc8..95bbad43d 100644 --- a/lightyear/src/shared/ping/manager.rs +++ b/lightyear/src/shared/ping/manager.rs @@ -1,12 +1,9 @@ //! Manages sending/receiving pings and computing network statistics use std::time::Duration; -use bevy::prelude::Res; use bevy::time::Stopwatch; -use tracing::{debug, error, info, trace}; +use tracing::{error, trace}; -use crate::client::resource::Client; -use crate::packet::packet::PacketId; use crate::protocol::Protocol; use crate::shared::ping::message::{Ping, Pong, SyncMessage}; use crate::shared::ping::store::{PingId, PingStore}; diff --git a/lightyear/src/shared/ping/message.rs b/lightyear/src/shared/ping/message.rs index 296c1a8d5..c8b53c47a 100644 --- a/lightyear/src/shared/ping/message.rs +++ b/lightyear/src/shared/ping/message.rs @@ -1,11 +1,7 @@ //! Defines the actual ping/pong messages -use crate::prelude::MapEntities; -use bevy::prelude::Entity; -use bevy::utils::EntityHashSet; use serde::{Deserialize, Serialize}; use crate::shared::ping::store::PingId; -use crate::shared::tick_manager::Tick; use crate::shared::time_manager::WrappedTime; /// Ping message; the remote should response immediately with a pong diff --git a/lightyear/src/shared/ping/store.rs b/lightyear/src/shared/ping/store.rs index 8ab042425..6035360dd 100644 --- a/lightyear/src/shared/ping/store.rs +++ b/lightyear/src/shared/ping/store.rs @@ -1,7 +1,6 @@ //! Store the latest pings sent to remote use crate::shared::time_manager::WrappedTime; use crate::utils::sequence_buffer::SequenceBuffer; -use crate::utils::wrapping_id; use crate::utils::wrapping_id::wrapping_id; wrapping_id!(PingId); diff --git a/lightyear/src/shared/replication/components.rs b/lightyear/src/shared/replication/components.rs index 6e28f5190..8ee352515 100644 --- a/lightyear/src/shared/replication/components.rs +++ b/lightyear/src/shared/replication/components.rs @@ -1,15 +1,16 @@ //! Components used for replication -use crate::channel::builder::{Channel, EntityActionsChannel, EntityUpdatesChannel}; -use crate::client::components::{ComponentSyncMode, SyncComponent}; -use crate::netcode::ClientId; -use crate::prelude::{EntityMapper, MapEntities}; -use crate::protocol::channel::ChannelKind; -use crate::server::room::{ClientVisibility, RoomId}; use bevy::prelude::{Component, Entity}; use bevy::utils::{EntityHashSet, HashMap, HashSet}; -use lightyear_macros::MessageInternal; use serde::{Deserialize, Serialize}; +use lightyear_macros::MessageInternal; + +use crate::channel::builder::Channel; +use crate::client::components::{ComponentSyncMode, SyncComponent}; +use crate::netcode::ClientId; +use crate::prelude::{EntityMapper, MapEntities}; +use crate::server::room::ClientVisibility; + /// Component inserted to each replicable entities, to detect when they are despawned #[derive(Component, Clone, Copy)] pub struct DespawnTracker; @@ -187,7 +188,6 @@ impl<'a> MapEntities<'a> for ShouldBePredicted { #[cfg(test)] mod tests { use super::*; - use crate::prelude::ClientId; #[test] fn test_network_target() { diff --git a/lightyear/src/shared/replication/entity_map.rs b/lightyear/src/shared/replication/entity_map.rs index 1ba2befec..d60e3570e 100644 --- a/lightyear/src/shared/replication/entity_map.rs +++ b/lightyear/src/shared/replication/entity_map.rs @@ -10,6 +10,7 @@ pub trait EntityMapper { } impl EntityMapper for &T { + #[inline] fn map(&self, entity: Entity) -> Option { (*self).map(entity) } @@ -29,6 +30,7 @@ pub struct PredictedEntityMap { } impl EntityMapper for PredictedEntityMap { + #[inline] fn map(&self, entity: Entity) -> Option { self.remote_to_predicted.get(&entity).copied() } @@ -41,6 +43,7 @@ pub struct InterpolatedEntityMap { } impl EntityMapper for InterpolatedEntityMap { + #[inline] fn map(&self, entity: Entity) -> Option { self.remote_to_interpolated.get(&entity).copied() } @@ -53,10 +56,12 @@ impl RemoteEntityMap { self.local_to_remote.insert(local_entity, remote_entity); } + #[inline] pub(crate) fn get_local(&self, remote_entity: Entity) -> Option<&Entity> { self.remote_to_local.get(&remote_entity) } + #[inline] pub(crate) fn get_remote(&self, local_entity: Entity) -> Option<&Entity> { self.local_to_remote.get(&local_entity) } @@ -115,6 +120,7 @@ impl RemoteEntityMap { } impl EntityMapper for RemoteEntityMap { + #[inline] fn map(&self, entity: Entity) -> Option { self.get_local(entity).copied() } @@ -130,6 +136,7 @@ pub trait MapEntities<'a> { } impl<'a> MapEntities<'a> for Entity { + #[inline] fn map_entities(&mut self, entity_mapper: Box) { if let Some(local) = entity_mapper.map(*self) { *self = local; @@ -141,6 +148,7 @@ impl<'a> MapEntities<'a> for Entity { } } + #[inline] fn entities(&self) -> EntityHashSet { EntityHashSet::from_iter(vec![*self]) } @@ -148,11 +156,12 @@ impl<'a> MapEntities<'a> for Entity { #[cfg(test)] mod tests { + use std::time::Duration; + use crate::prelude::client::*; use crate::prelude::*; use crate::tests::protocol::*; use crate::tests::stepper::{BevyStepper, Step}; - use std::time::Duration; // An entity gets replicated from server to client, // then a component gets removed from that entity on server, diff --git a/lightyear/src/shared/replication/manager.rs b/lightyear/src/shared/replication/manager.rs index 5969e2a10..37f914e8a 100644 --- a/lightyear/src/shared/replication/manager.rs +++ b/lightyear/src/shared/replication/manager.rs @@ -1,42 +1,32 @@ //! General struct handling replication -use anyhow::Context; -use bevy::a11y::accesskit::Action; -use std::collections::hash_map::Entry; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::BTreeMap; +use std::iter::Extend; -use crate::_reexport::{ - EntityActionsChannel, EntityUpdatesChannel, IntoKind, ShouldBeInterpolated, ShouldBePredicted, -}; -use crate::client::components::{ComponentSyncMode, Confirmed}; -use crate::client::prediction::Predicted; -use crate::connection::events::ConnectionEvents; -use crate::protocol::component::ComponentProtocol; +use anyhow::Context; use bevy::ecs::component::Tick as BevyTick; -use bevy::prelude::{Entity, EntityWorldMut, World}; -use bevy::utils::petgraph::algo::toposort; +use bevy::prelude::{Entity, World}; use bevy::utils::petgraph::data::ElementIterator; -use bevy::utils::petgraph::graphmap::DiGraphMap; -use bevy::utils::petgraph::prelude::DiGraph; -use bevy::utils::EntityHashMap; +use bevy::utils::{EntityHashMap, HashMap, HashSet}; use crossbeam_channel::Receiver; -use tracing::{debug, error, info, trace, trace_span}; +use tracing::{debug, error, info, trace, trace_span, warn}; use tracing_subscriber::filter::FilterExt; use tracing_subscriber::fmt::writer::MakeWriterExt; -use super::entity_map::{InterpolatedEntityMap, PredictedEntityMap, RemoteEntityMap}; -use super::{ - EntityActionMessage, EntityActions, EntityUpdatesMessage, Replicate, ReplicationMessage, - ReplicationMessageData, -}; -use crate::connection::message::ProtocolMessage; -use crate::netcode::ClientId; +use crate::_reexport::{EntityActionsChannel, EntityUpdatesChannel, IntoKind}; +use crate::connection::events::ConnectionEvents; use crate::packet::message::MessageId; -use crate::prelude::client::Interpolated; use crate::prelude::{MapEntities, Tick}; use crate::protocol::channel::ChannelKind; +use crate::protocol::component::ComponentProtocol; use crate::protocol::component::{ComponentBehaviour, ComponentKindBehaviour}; use crate::protocol::Protocol; -use crate::shared::replication::components::{ReplicationGroup, ReplicationGroupId}; +use crate::shared::replication::components::ReplicationGroupId; + +use super::entity_map::{InterpolatedEntityMap, PredictedEntityMap, RemoteEntityMap}; +use super::{ + EntityActionMessage, EntityActions, EntityUpdatesMessage, ReplicationMessage, + ReplicationMessageData, +}; // TODO: maybe separate send/receive side for clarity? pub(crate) struct ReplicationManager { @@ -54,10 +44,9 @@ pub(crate) struct ReplicationManager { HashMap>, >, pub pending_updates: EntityHashMap>>, - // Get the graph of dependencies between entities within a same group. - // (for example if a component of entity1 refers to entity2, then entity2 must be spawned before entity1. - // In that case we add an edge entity2 -> entity1 in the graph - pub group_dependencies: EntityHashMap>, + // Set of unique components for each entity, to avoid sending multiple updates/inserts for the same component + pub pending_unique_components: + EntityHashMap>>, // RECEIVE /// Map between local and remote entities. (used mostly on client because it's when we receive entity updates) @@ -83,7 +72,7 @@ impl ReplicationManager

{ updates_message_id_to_group_id: Default::default(), pending_actions: EntityHashMap::default(), pending_updates: EntityHashMap::default(), - group_dependencies: EntityHashMap::default(), + pending_unique_components: EntityHashMap::default(), // RECEIVE remote_entity_map: RemoteEntityMap::default(), predicted_entity_map: PredictedEntityMap::default(), @@ -235,10 +224,6 @@ impl ReplicationManager

{ .entry(entity) .or_default(); actions.spawn = true; - self.group_dependencies - .entry(group) - .or_default() - .add_node(entity); } pub(crate) fn prepare_entity_despawn(&mut self, entity: Entity, group: ReplicationGroupId) { @@ -248,10 +233,6 @@ impl ReplicationManager

{ .entry(entity) .or_default() .despawn = true; - self.group_dependencies - .entry(group) - .or_default() - .add_node(entity); } // we want to send all component inserts that happen together for the same entity in a single message @@ -263,13 +244,23 @@ impl ReplicationManager

{ group: ReplicationGroupId, component: P::Components, ) { - // if component contains entities, add an edge in the dependency graph - let graph = self.group_dependencies.entry(group).or_default(); - graph.add_node(entity); - component.entities().iter().for_each(|e| { - // `e` must be spawned before `entity` - graph.add_edge(*e, entity, ()); - }); + let kind: P::ComponentKinds = (&component).into(); + if self + .pending_unique_components + .entry(group) + .or_default() + .entry(entity) + .or_default() + .contains(&kind) + { + warn!( + ?group, + ?entity, + ?kind, + "Trying to insert a component that is already in the message" + ); + return; + } self.pending_actions .entry(group) .or_default() @@ -277,25 +268,43 @@ impl ReplicationManager

{ .or_default() .insert .push(component); + self.pending_unique_components + .entry(group) + .or_default() + .entry(entity) + .or_default() + .insert(kind); } pub(crate) fn prepare_component_remove( &mut self, entity: Entity, group: ReplicationGroupId, - component: P::ComponentKinds, + kind: P::ComponentKinds, ) { - self.pending_actions + if self + .pending_unique_components .entry(group) .or_default() .entry(entity) .or_default() - .remove - .push(component); - self.group_dependencies + .contains(&kind) + { + error!( + ?group, + ?entity, + ?kind, + "Trying to remove a component that is already in the message as an insert/update" + ); + return; + } + self.pending_actions .entry(group) .or_default() - .add_node(entity); + .entry(entity) + .or_default() + .remove + .insert(kind); } pub(crate) fn prepare_entity_update( @@ -304,19 +313,35 @@ impl ReplicationManager

{ group: ReplicationGroupId, component: P::Components, ) { - // if component contains entities, add an edge in the dependency graph - let graph = self.group_dependencies.entry(group).or_default(); - graph.add_node(entity); - component.entities().iter().for_each(|e| { - // `e` must be spawned before `entity` - graph.add_edge(*e, entity, ()); - }); + let kind: P::ComponentKinds = (&component).into(); + if self + .pending_unique_components + .entry(group) + .or_default() + .entry(entity) + .or_default() + .contains(&kind) + { + warn!( + ?group, + ?entity, + ?kind, + "Trying to update a component that is already in the message" + ); + return; + } self.pending_updates .entry(group) .or_default() .entry(entity) .or_default() .push(component); + self.pending_unique_components + .entry(group) + .or_default() + .entry(entity) + .or_default() + .insert(kind); } /// Finalize the replication messages @@ -331,91 +356,51 @@ impl ReplicationManager

{ let mut messages = Vec::new(); // get the list of entities in topological order - for (group_id, dependency_graph) in self.group_dependencies.drain() { - match toposort(&dependency_graph, None) { - Ok(entities) => { - // create an actions message - if let Some(mut actions) = self.pending_actions.remove(&group_id) { - let channel = self.group_channels.entry(group_id).or_default(); - let message_id = channel.actions_next_send_message_id; - channel.actions_next_send_message_id += 1; - channel.last_action_tick = tick; - let mut actions_message = vec![]; - - // add any updates for that group into the actions message - let mut my_updates = self.pending_updates.remove(&group_id); - - // add actions to the message for entities in topological order - for e in entities.iter() { - let mut a = actions.remove(e).unwrap_or_else(EntityActions::default); - // for any update that was not already in insert/updates, add it to the update list - if let Some(ref mut updates) = my_updates { - // TODO: this suggests that we should already store inserts/updates as HashSet! - let existing_inserts = a - .insert - .iter() - .map(|c| c.into()) - .collect::>(); - let existing_updates = a - .updates - .iter() - .map(|c| c.into()) - .collect::>(); - if let Some(u) = updates.remove(e) { - u.into_iter() - .filter(|c| { - !existing_inserts.contains(&(c.into())) - && !existing_updates.contains(&(c.into())) - }) - .for_each(|c| a.updates.push(c)); - } - } - actions_message.push((*e, a)); - } - - messages.push(( - ChannelKind::of::(), - group_id, - ReplicationMessageData::Actions(EntityActionMessage { - sequence_id: message_id, - actions: actions_message, - }), - )); - } - - // create an updates message - if let Some(mut updates) = self.pending_updates.remove(&group_id) { - let channel = self.group_channels.entry(group_id).or_default(); - let mut updates_message = vec![]; - - // add updates to the message in topological order - entities.iter().for_each(|e| { - if let Some(u) = updates.remove(e) { - updates_message.push((*e, u)); - }; - }); - - messages.push(( - ChannelKind::of::(), - group_id, - ReplicationMessageData::Updates(EntityUpdatesMessage { - last_action_tick: channel.last_action_tick, - updates: updates_message, - }), - )); - }; - } - Err(e) => { - error!("There is a cyclic dependency in the group (with entity {:?})! Replication aborted.", e.node_id()); + for (group_id, mut actions) in self.pending_actions.drain() { + // add any updates for that group + if let Some(updates) = self.pending_updates.remove(&group_id) { + for (entity, components) in updates { + actions + .entry(entity) + .or_default() + .updates + .extend(components.into_iter()); } } + let channel = self.group_channels.entry(group_id).or_default(); + let message_id = channel.actions_next_send_message_id; + channel.actions_next_send_message_id += 1; + channel.last_action_tick = tick; + messages.push(( + ChannelKind::of::(), + group_id, + ReplicationMessageData::Actions(EntityActionMessage { + sequence_id: message_id, + // TODO: maybe we can just send the HashMap directly? + actions: Vec::from_iter(actions.into_iter()), + }), + )); } - self.pending_actions.clear(); - self.pending_updates.clear(); + // send the remaining updates + for (group_id, updates) in self.pending_updates.drain() { + let channel = self.group_channels.entry(group_id).or_default(); + messages.push(( + ChannelKind::of::(), + group_id, + ReplicationMessageData::Updates(EntityUpdatesMessage { + last_action_tick: channel.last_action_tick, + // TODO: maybe we can just send the HashMap directly? + updates: Vec::from_iter(updates.into_iter()), + }), + )); + } + if !messages.is_empty() { trace!(?messages, "Sending replication messages"); } + // clear send buffers + self.pending_unique_components.clear(); messages } @@ -432,22 +417,28 @@ impl ReplicationManager

{ match replication { ReplicationMessageData::Actions(m) => { info!(?m, "Received replication actions"); - // NOTE: order matters here, the entities are stored in the message in topological order - // i.e. entities that depend on other entities are read later on - for (entity, actions) in m.actions.into_iter() { - info!(remote_entity = ?entity, "Received entity actions"); - + // NOTE: order matters here, because some components can depend on other entities. + // These components could even form a cycle, for example A.HasWeapon(B) and B.HasHolder(A) + // Our solution is to first handle spawn for all entities separately. + for (entity, actions) in m.actions.iter() { + debug!(remote_entity = ?entity, "Received entity actions"); assert!(!(actions.spawn && actions.despawn)); - - // spawn/despawn + // spawn if actions.spawn { // TODO: optimization: spawn the bundle of insert components let local_entity = world.spawn_empty(); - self.remote_entity_map.insert(entity, local_entity.id()); + self.remote_entity_map.insert(*entity, local_entity.id()); debug!(remote_entity = ?entity, "Received entity spawn"); events.push_spawn(local_entity.id()); - } else if actions.despawn { + } + } + + for (entity, actions) in m.actions.into_iter() { + info!(remote_entity = ?entity, "Received entity actions"); + + // despawn + if actions.despawn { debug!(remote_entity = ?entity, "Received entity despawn"); if let Some(local_entity) = self.remote_entity_map.remove_by_remote(entity) { @@ -457,145 +448,39 @@ impl ReplicationManager

{ error!("Received despawn for an entity that does not exist") } continue; - } else if self.remote_entity_map.get_by_remote(world, entity).is_err() { - error!("cannot find entity"); - continue; } // safety: we know by this point that the entity exists - let local_entity = *self.remote_entity_map.get_local(entity).unwrap(); + let Ok(mut local_entity_mut) = + self.remote_entity_map.get_by_remote(world, entity) + else { + error!("cannot find entity"); + continue; + }; - // prediction / interpolation + // inserts let kinds = actions .insert .iter() .map(|c| c.into()) .collect::>(); - - // NOTE: we handle it here because we want to spawn the predicted/interpolated entities - // for the group in topological sort as well, and to maintain a new entity mapping - // i.e. if ComponentA contains an entity E, - // predicted component A' must contain an entity E' that is predicted - let predicted_kind = - P::ComponentKinds::from(&P::Components::from(ShouldBePredicted)); - if kinds.contains(&predicted_kind) { - // spawn a new predicted entity - let mut predicted_entity_mut = world.spawn(Predicted { - confirmed_entity: local_entity, - }); - - let predicted_entity = predicted_entity_mut.id(); - self.predicted_entity_map - .remote_to_predicted - .insert(entity, predicted_entity); - - // TODO: I don't like having coupling between the manager and prediction/interpolation - // now every component needs to implement ComponentSyncMode... - // sync components that have ComponentSyncMode != None - for mut component in actions - .insert - .iter() - .filter(|c| !matches!(c.mode(), ComponentSyncMode::None)) - .cloned() - { - info!("syncing component {:?} for predicted entity", component); - // map any entities inside the component - component.map_entities(Box::new(&self.predicted_entity_map)); - component.insert(&mut predicted_entity_mut); - } - - // add Confirmed to the confirmed entity - // safety: we know the entity exists - let mut local_entity_mut = world.entity_mut(local_entity); - if let Some(mut confirmed) = local_entity_mut.get_mut::() { - confirmed.predicted = Some(predicted_entity); - } else { - local_entity_mut.insert(Confirmed { - predicted: Some(predicted_entity), - interpolated: None, - }); - } - info!( - "Spawn predicted entity {:?} for confirmed: {:?}", - predicted_entity, local_entity, - ); - #[cfg(feature = "metrics")] - { - metrics::increment_counter!("spawn_predicted_entity"); - } - } - // NOTE: we handle it here because we want to spawn the predicted/interpolated entities - // for the group in topological sort as well, and to maintain a new entity mapping - // i.e. if ComponentA contains an entity E, - // predicted component A' must contain an entity E' that is predicted - // maybe instead there should be an entity dependency graph that clients are aware of and maintain, - // and replication between confirmed/predicted should use that graph (even for non replicated components) - // For example what happens if on confirm we spawn a particle HasParent(ConfirmedEntity)? - // most probably it wouldn't get synced, but if it is we need to perform a mapping as well. - // - let interpolated_kind = - P::ComponentKinds::from(&P::Components::from(ShouldBeInterpolated)); - if kinds.contains(&interpolated_kind) { - // spawn a new interpolated entity - let mut interpolated_entity_mut = world.spawn(Interpolated { - confirmed_entity: local_entity, - }); - - let interpolated_entity = interpolated_entity_mut.id(); - self.interpolated_entity_map - .remote_to_interpolated - .insert(entity, interpolated_entity); - - // TODO: I don't like having coupling between the manager and prediction/interpolation - // now every component needs to implement ComponentSyncMode... - // sync components that have ComponentSyncMode != None - for mut component in actions - .insert - .iter() - .filter(|c| !matches!(c.mode(), ComponentSyncMode::None)) - .cloned() - { - // map any entities inside the component - component.map_entities(Box::new(&self.interpolated_entity_map)); - component.insert(&mut interpolated_entity_mut); - } - - // add Confirmed to the confirmed entity - // safety: we know the entity exists - let mut local_entity_mut = world.entity_mut(local_entity); - if let Some(mut confirmed) = local_entity_mut.get_mut::() { - confirmed.interpolated = Some(interpolated_entity); - } else { - local_entity_mut.insert(Confirmed { - interpolated: Some(interpolated_entity), - predicted: None, - }); - } - info!( - "Spawn interpolated entity {:?} for confirmed: {:?}/remote: {:?}", - interpolated_entity, local_entity, entity, - ); - #[cfg(feature = "metrics")] - { - metrics::increment_counter!("spawn_interpolated_entity"); - } - } - - // inserts info!(remote_entity = ?entity, ?kinds, "Received InsertComponent"); - let mut local_entity_mut = world.entity_mut(local_entity); for mut component in actions.insert { // map any entities inside the component component.map_entities(Box::new(&self.remote_entity_map)); // TODO: figure out what to do with tick here - events.push_insert_component(local_entity, (&component).into(), Tick(0)); + events.push_insert_component( + local_entity_mut.id(), + (&component).into(), + Tick(0), + ); component.insert(&mut local_entity_mut); } // removals debug!(remote_entity = ?entity, ?actions.remove, "Received RemoveComponent"); for kind in actions.remove { - events.push_remove_component(local_entity, kind.clone(), Tick(0)); + events.push_remove_component(local_entity_mut.id(), kind.clone(), Tick(0)); kind.remove(&mut local_entity_mut); } @@ -611,14 +496,13 @@ impl ReplicationManager

{ for mut component in actions.updates { // map any entities inside the component component.map_entities(Box::new(&self.remote_entity_map)); - events.push_update_component(local_entity, (&component).into(), Tick(0)); + events.push_update_component( + local_entity_mut.id(), + (&component).into(), + Tick(0), + ); component.update(&mut local_entity_mut); } - - // TODO: - // prediction/interpolation - // sync updates for ComponentSyncMode = simple or full - // NOTE: again we apply the replication here because we want to apply it in topological order } } ReplicationMessageData::Updates(m) => { @@ -750,11 +634,12 @@ impl GroupChannel

{ #[cfg(test)] mod tests { - use super::*; - use crate::_reexport::IntoKind; - use crate::tests::protocol::*; use bevy::prelude::*; + use crate::tests::protocol::*; + + use super::*; + // TODO: add tests for replication with entity relations! #[test] fn test_buffer_replication_messages() { @@ -827,7 +712,7 @@ mod tests { spawn: true, despawn: false, insert: vec![MyComponentsProtocol::Component1(Component1(1.0))], - remove: vec![MyComponentsProtocolKind::Component2], + remove: HashSet::from_iter(vec![MyComponentsProtocolKind::Component2]), updates: vec![MyComponentsProtocol::Component3(Component3(3.0))], } ), @@ -837,7 +722,7 @@ mod tests { spawn: false, despawn: false, insert: vec![], - remove: vec![], + remove: HashSet::default(), updates: vec![MyComponentsProtocol::Component2(Component2(4.0))], } ) diff --git a/lightyear/src/shared/replication/mod.rs b/lightyear/src/shared/replication/mod.rs index 1d0889cd5..9a62293fc 100644 --- a/lightyear/src/shared/replication/mod.rs +++ b/lightyear/src/shared/replication/mod.rs @@ -1,21 +1,20 @@ //! Module to handle replicating entities and components from server to client -use crate::_reexport::{ComponentProtocol, ComponentProtocolKind}; +use std::hash::Hash; + use anyhow::Result; use bevy::ecs::component::Tick as BevyTick; -use bevy::ecs::system::SystemChangeTick; use bevy::prelude::{Component, Entity, Resource}; use bevy::reflect::Map; -use bevy::utils::{EntityHashMap, EntityHashSet, HashMap}; +use bevy::utils::HashSet; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use crate::channel::builder::{Channel, EntityActionsChannel, EntityUpdatesChannel}; +use crate::_reexport::{ComponentProtocol, ComponentProtocolKind}; +use crate::channel::builder::Channel; use crate::netcode::ClientId; use crate::packet::message::MessageId; -use crate::prelude::{EntityMapper, MapEntities, NetworkTarget, RemoteEntityMap, Tick}; -use crate::protocol::channel::ChannelKind; +use crate::prelude::{EntityMapper, MapEntities, NetworkTarget, Tick}; use crate::protocol::Protocol; -use crate::shared::replication::components::{Replicate, ReplicationGroup, ReplicationGroupId}; +use crate::shared::replication::components::{Replicate, ReplicationGroupId}; pub mod components; @@ -46,24 +45,23 @@ pub mod systems; // } #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct EntityActions { - // TODO: do we even need spawn? +pub struct EntityActions { pub(crate) spawn: bool, pub(crate) despawn: bool, - // TODO: do we want HashSets to avoid double-inserts, double-removes? + // Cannot use HashSet because we would need ComponentProtocol to implement Hash + Eq pub(crate) insert: Vec, - pub(crate) remove: Vec, + pub(crate) remove: HashSet, // We also include the updates for the current tick in the actions, if there are any pub(crate) updates: Vec, } -impl Default for EntityActions { +impl Default for EntityActions { fn default() -> Self { Self { spawn: false, despawn: false, insert: Vec::new(), - remove: Vec::new(), + remove: HashSet::new(), updates: Vec::new(), } } @@ -72,8 +70,9 @@ impl Default for EntityActions { // TODO: 99% of the time the ReplicationGroup is the same as the Entity in the hashmap, and there's only 1 entity // have an optimization for that #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct EntityActionMessage { +pub struct EntityActionMessage { sequence_id: MessageId, + // we use vec but the order of entities should not matter pub(crate) actions: Vec<(Entity, EntityActions)>, } @@ -85,7 +84,7 @@ pub struct EntityUpdatesMessage { } #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub enum ReplicationMessageData { +pub enum ReplicationMessageData { /// All the entity actions (Spawn/despawn/inserts/removals) for a given group Actions(EntityActionMessage), /// All the entity updates for a given group @@ -93,7 +92,7 @@ pub enum ReplicationMessageData { } #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct ReplicationMessage { +pub struct ReplicationMessage { pub(crate) group_id: ReplicationGroupId, pub(crate) data: ReplicationMessageData, } @@ -166,11 +165,12 @@ pub trait ReplicationSend: Resource { #[cfg(test)] mod tests { + use std::time::Duration; + use crate::prelude::client::*; use crate::prelude::*; use crate::tests::protocol::*; use crate::tests::stepper::{BevyStepper, Step}; - use std::time::Duration; // An entity gets replicated from server to client, // then a component gets removed from that entity on server, diff --git a/lightyear/src/shared/replication/resources.rs b/lightyear/src/shared/replication/resources.rs index 600ecea52..c12112ac6 100644 --- a/lightyear/src/shared/replication/resources.rs +++ b/lightyear/src/shared/replication/resources.rs @@ -1,11 +1,11 @@ //! Bevy [`bevy::prelude::Resource`]s used for replication -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; -use crate::netcode::ClientId; -use crate::shared::replication::components::Replicate; use bevy::ecs::component::ComponentId; use bevy::prelude::{Entity, FromWorld, Resource, World}; +use crate::shared::replication::components::Replicate; + #[derive(Resource)] /// Internal resource used to keep track of the state of replication pub(crate) struct ReplicationData { diff --git a/lightyear/src/shared/replication/systems.rs b/lightyear/src/shared/replication/systems.rs index 101923982..2601015b9 100644 --- a/lightyear/src/shared/replication/systems.rs +++ b/lightyear/src/shared/replication/systems.rs @@ -1,20 +1,16 @@ //! Bevy [`bevy::prelude::System`]s used for replication -use bevy::ecs::system::SystemChangeTick; use std::ops::Deref; -use crate::connection::events::ConnectionEvents; +use bevy::ecs::system::SystemChangeTick; use bevy::prelude::{ - Added, App, Commands, Component, DetectChanges, Entity, EventReader, IntoSystemConfigs, Mut, - PostUpdate, Query, Ref, RemovedComponents, ResMut, + Added, App, Commands, Component, DetectChanges, Entity, IntoSystemConfigs, PostUpdate, Query, + Ref, RemovedComponents, ResMut, }; -use bevy::utils::HashSet; -use tracing::{debug, info}; +use tracing::debug; -use crate::netcode::ClientId; use crate::prelude::NetworkTarget; use crate::protocol::component::IntoKind; use crate::protocol::Protocol; -use crate::server::events::ConnectEvent; use crate::server::room::ClientVisibility; use crate::shared::replication::components::{DespawnTracker, Replicate, ReplicationMode}; use crate::shared::replication::resources::ReplicationData; diff --git a/lightyear/src/tests/examples/connection_soak.rs b/lightyear/src/tests/examples/connection_soak.rs index 0c990bc5a..cd9af1ada 100644 --- a/lightyear/src/tests/examples/connection_soak.rs +++ b/lightyear/src/tests/examples/connection_soak.rs @@ -1,11 +1,11 @@ //! The connection soak test how sending messages/packets works with a real connection, and loss/jitter //! We put this test here because it uses some private methods. -use bevy::ecs::component::Tick as BevyTick; -use bevy::prelude::World; use std::net::SocketAddr; use std::str::FromStr; use std::time::Duration; +use bevy::ecs::component::Tick as BevyTick; +use bevy::prelude::World; use rand::Rng; use tracing::debug; diff --git a/lightyear/src/tests/stepper.rs b/lightyear/src/tests/stepper.rs index ecefa0d5d..d2916d6ba 100644 --- a/lightyear/src/tests/stepper.rs +++ b/lightyear/src/tests/stepper.rs @@ -5,7 +5,6 @@ use std::time::Duration; use bevy::prelude::{App, Mut, PluginGroup, Real, Time}; use bevy::time::TimeUpdateStrategy; use bevy::MinimalPlugins; -use tracing_subscriber::fmt::format::FmtSpan; use crate::netcode::generate_key; use crate::prelude::client::{ @@ -14,7 +13,6 @@ use crate::prelude::client::{ }; use crate::prelude::server::{NetcodeConfig, Server, ServerConfig}; use crate::prelude::*; - use crate::tests::protocol::{protocol, MyProtocol}; /// Helpers to setup a bevy app where I can just step the world easily diff --git a/lightyear/src/transport/io.rs b/lightyear/src/transport/io.rs index 7dbe7b767..79b3b68f3 100644 --- a/lightyear/src/transport/io.rs +++ b/lightyear/src/transport/io.rs @@ -6,18 +6,17 @@ use std::net::{IpAddr, SocketAddr}; #[cfg(feature = "metrics")] use metrics; +#[cfg(feature = "webtransport")] +use wtransport::tls::Certificate; use crate::transport::conditioner::{ConditionedPacketReceiver, LinkConditionerConfig}; -use crate::transport::local::{LocalChannel, LOCAL_SOCKET}; +use crate::transport::local::LocalChannel; use crate::transport::udp::UdpSocket; - #[cfg(feature = "webtransport")] use crate::transport::webtransport::client::WebTransportClientSocket; #[cfg(feature = "webtransport")] use crate::transport::webtransport::server::WebTransportServerSocket; use crate::transport::{PacketReceiver, PacketSender, Transport}; -#[cfg(feature = "webtransport")] -use wtransport::tls::Certificate; #[derive(Clone)] pub enum TransportConfig { diff --git a/lightyear/src/transport/webtransport/server.rs b/lightyear/src/transport/webtransport/server.rs index eb1404c50..5dc86e078 100644 --- a/lightyear/src/transport/webtransport/server.rs +++ b/lightyear/src/transport/webtransport/server.rs @@ -1,19 +1,20 @@ //! WebTransport client implementation. -use crate::transport::webtransport::MTU; -use crate::transport::{PacketReceiver, PacketSender, Transport}; -use bevy::tasks::{IoTaskPool, TaskPool}; use std::collections::HashMap; use std::net::SocketAddr; use std::sync::{Arc, Mutex}; + use tokio::sync::mpsc; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; -use tracing::{debug, error, info}; +use tracing::{debug, error}; use wtransport; use wtransport::datagram::Datagram; use wtransport::endpoint::IncomingSession; use wtransport::tls::Certificate; -use wtransport::{ClientConfig, ServerConfig}; +use wtransport::ServerConfig; + +use crate::transport::webtransport::MTU; +use crate::transport::{PacketReceiver, PacketSender, Transport}; /// WebTransport client socket pub struct WebTransportServerSocket { diff --git a/lightyear/src/utils/bevy.rs b/lightyear/src/utils/bevy.rs index a7beb5fe1..3a884ea5c 100644 --- a/lightyear/src/utils/bevy.rs +++ b/lightyear/src/utils/bevy.rs @@ -1,8 +1,9 @@ //! Implement lightyear traits for some common bevy types -use crate::prelude::{EntityMapper, MapEntities, Message, Named, RemoteEntityMap}; use bevy::prelude::{Entity, Transform}; use bevy::utils::EntityHashSet; +use crate::prelude::{EntityMapper, MapEntities, Message, Named}; + impl Named for Transform { fn name(&self) -> String { "Transform".to_string() diff --git a/lightyear/src/utils/named.rs b/lightyear/src/utils/named.rs index bb8698a25..6051083d2 100644 --- a/lightyear/src/utils/named.rs +++ b/lightyear/src/utils/named.rs @@ -4,7 +4,7 @@ // - derive Reflect for Messages // - require to derive Reflect for Components ? -use std::fmt::{Debug, Formatter}; +use std::fmt::Debug; pub trait TypeNamed { fn name() -> String; diff --git a/macros/src/component.rs b/macros/src/component.rs index c8e258371..49064b8ad 100644 --- a/macros/src/component.rs +++ b/macros/src/component.rs @@ -187,6 +187,13 @@ pub fn component_protocol_impl( #mode_method } + // impl std::hash::Hash for #enum_name { + // fn hash(&self, state: &mut H) { + // let kind: #enum_kind_name = self.into(); + // kind.hash(state); + // } + // } + impl std::fmt::Debug for #enum_name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { let kind: #enum_kind_name = self.into(); @@ -371,14 +378,17 @@ fn add_sync_systems_method(fields: &Vec, protocol_name: &Ident) -> To let mut interpolation_body = quote! {}; for field in fields { let component_type = &field.ty; - prediction_body = quote! { - #prediction_body - add_prediction_systems::<#component_type, #protocol_name>(app); - }; - prepare_interpolation_body = quote! { - #prepare_interpolation_body - add_prepare_interpolation_systems::<#component_type, #protocol_name>(app); - }; + // we only add sync systems if the SyncComponent is not None + if field.full || field.once || field.simple { + prediction_body = quote! { + #prediction_body + add_prediction_systems::<#component_type, #protocol_name>(app); + }; + prepare_interpolation_body = quote! { + #prepare_interpolation_body + add_prepare_interpolation_systems::<#component_type, #protocol_name>(app); + }; + } if field.full { interpolation_body = quote! { #interpolation_body @@ -533,7 +543,7 @@ fn delegate_method(input: &ItemEnum, enum_kind_name: &Ident) -> TokenStream { }; } - // TODO: make it work with generics + // TODO: make it work with generics (generic components) quote! { impl<'a> MapEntities<'a> for #enum_name { fn map_entities(&mut self, entity_mapper: Box) { From 4f12f132bb66b2cf15f445df7538955da5e8b8d1 Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Thu, 21 Dec 2023 15:00:44 -0500 Subject: [PATCH 2/2] switch to debug logs --- examples/replication_groups/client.rs | 16 ++++++++-------- examples/replication_groups/shared.rs | 8 ++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/examples/replication_groups/client.rs b/examples/replication_groups/client.rs index 7a6ff17fc..709d333ec 100644 --- a/examples/replication_groups/client.rs +++ b/examples/replication_groups/client.rs @@ -278,7 +278,7 @@ pub(crate) fn interpolate( assert_eq!(tail_status.current, parent_status.current); *tail = tail_start_value.clone(); *parent_position = pos_start.clone(); - info!( + debug!( ?tail, ?parent_position, "after interpolation; CURRENT = START" @@ -296,7 +296,7 @@ pub(crate) fn interpolate( assert_eq!(tail_status.current, parent_status.current); *tail = tail_end_value.clone(); *parent_position = pos_end.clone(); - info!( + debug!( ?tail, ?parent_position, "after interpolation; CURRENT = END" @@ -324,13 +324,13 @@ pub(crate) fn interpolate( != tail_start_value.0.front().unwrap().0 { tail.0.push_front(tail_end_value.0.front().unwrap().clone()); - info!("ADD POINT"); + debug!("ADD POINT"); } // the path is straight! just move the head and adjust the tail *parent_position = PlayerPosition::lerp(pos_start.clone(), pos_end.clone(), t); tail.shorten_back(parent_position.0, tail_length.0); - info!( + debug!( ?tail, ?parent_position, "after interpolation; FIRST SEGMENT" @@ -371,7 +371,7 @@ pub(crate) fn interpolate( // now move the head by `pos_distance_to_do` while remaining on the tail path for i in (0..segment_idx).rev() { let dist = segment_length(parent_position.0, tail_end_value.0[i].0); - info!( + debug!( ?i, ?dist, ?pos_distance_to_do, @@ -380,7 +380,7 @@ pub(crate) fn interpolate( "in other segments" ); if pos_distance_to_do < 1000.0 * f32::EPSILON { - info!(?tail, ?parent_position, "after interpolation; ON POINT"); + debug!(?tail, ?parent_position, "after interpolation; ON POINT"); // no need to change anything continue 'outer; } @@ -406,7 +406,7 @@ pub(crate) fn interpolate( .1 .get_tail(tail_end_value.0[i].0, dist - pos_distance_to_do); tail.shorten_back(parent_position.0, tail_length.0); - info!(?tail, ?parent_position, "after interpolation; ELSE"); + debug!(?tail, ?parent_position, "after interpolation; ELSE"); continue 'outer; } } @@ -419,7 +419,7 @@ pub(crate) fn interpolate( .1 .get_tail(pos_end.0, dist - pos_distance_to_do); tail.shorten_back(parent_position.0, tail_length.0); - info!(?tail, ?parent_position, "after interpolation; ELSE FIRST"); + debug!(?tail, ?parent_position, "after interpolation; ELSE FIRST"); } } } diff --git a/examples/replication_groups/shared.rs b/examples/replication_groups/shared.rs index 9eb08ab02..5273a3e2d 100644 --- a/examples/replication_groups/shared.rs +++ b/examples/replication_groups/shared.rs @@ -28,14 +28,10 @@ pub struct SharedPlugin; impl Plugin for SharedPlugin { fn build(&self, app: &mut App) { - app.add_plugins(WorldInspectorPlugin::new()); app.add_systems(Update, draw_snakes); } } -// head -// snake - // This system defines how we update the player's positions when we receive an input pub(crate) fn shared_movement_behaviour(position: &mut PlayerPosition, input: &Inputs) { const MOVE_SPEED: f32 = 10.0; @@ -121,13 +117,13 @@ pub(crate) fn draw_snakes( if position.0.x != points.0.front().unwrap().0.x && position.0.y != points.0.front().unwrap().0.y { - info!("DIAGONAL"); + debug!("DIAGONAL"); } // draw the rest of the lines for (start, end) in points.0.iter().zip(points.0.iter().skip(1)) { gizmos.line_2d(start.0, end.0, color.0); if start.0.x != end.0.x && start.0.y != end.0.y { - info!("DIAGONAL"); + debug!("DIAGONAL"); } } }