From 796b6f7085a090bd201e0aedeb90920e40735a1a Mon Sep 17 00:00:00 2001 From: cBournhonesque Date: Fri, 17 Jan 2025 17:49:17 -0500 Subject: [PATCH 1/2] improve visual interp --- .gitignore | 1 + examples/avian_3d_character/src/protocol.rs | 10 ++++- examples/avian_3d_character/src/renderer.rs | 47 +++++++-------------- examples/avian_3d_character/src/server.rs | 28 ++++++------ examples/avian_3d_character/src/shared.rs | 15 +++---- examples/avian_physics/src/client.rs | 2 - examples/avian_physics/src/protocol.rs | 4 +- examples/avian_physics/src/renderer.rs | 28 +++++++++++- examples/avian_physics/src/shared.rs | 1 - examples/spaceships/src/protocol.rs | 10 ++++- examples/spaceships/src/renderer.rs | 37 +++++++--------- examples/spaceships/src/shared.rs | 4 +- lightyear/src/protocol/component.rs | 1 + lightyear/src/utils/avian2d.rs | 12 ++++-- lightyear/src/utils/avian3d.rs | 14 ++++-- 15 files changed, 118 insertions(+), 96 deletions(-) diff --git a/.gitignore b/.gitignore index dbe8e08ef..651ba9305 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ .DS_Store /examples/target/ /examples/Cargo.lock +.aider* diff --git a/examples/avian_3d_character/src/protocol.rs b/examples/avian_3d_character/src/protocol.rs index 652e3c2d0..1c20fb54d 100644 --- a/examples/avian_3d_character/src/protocol.rs +++ b/examples/avian_3d_character/src/protocol.rs @@ -5,16 +5,16 @@ use leafwing_input_manager::prelude::*; use lightyear_examples_common::shared::FIXED_TIMESTEP_HZ; use serde::{Deserialize, Serialize}; +use crate::shared::color_from_id; use lightyear::client::components::{ComponentSyncMode, LerpFn}; use lightyear::client::interpolation::LinearInterpolator; use lightyear::prelude::client::{self, LeafwingInputConfig}; use lightyear::prelude::server::{Replicate, SyncTarget}; use lightyear::prelude::*; use lightyear::utils::avian3d::{position, rotation}; +use lightyear::utils::bevy::TransformLinearInterpolation; use tracing_subscriber::util::SubscriberInitExt; -use crate::shared::color_from_id; - // For prediction, we want everything entity that is predicted to be part of // the same replication group This will make sure that they will be replicated // in the same message and that all the entities in the group will always be @@ -105,5 +105,11 @@ impl Plugin for ProtocolPlugin { .add_prediction(ComponentSyncMode::Full) .add_interpolation_fn(rotation::lerp) .add_correction_fn(rotation::lerp); + + // do not replicate Transform but make sure to register an interpolation function + // for it so that we can do visual interpolation + // (another option would be to replicate transform and not use Position/Rotation at all) + app.add_interpolation::(ComponentSyncMode::None); + app.add_interpolation_fn::(TransformLinearInterpolation::lerp); } } diff --git a/examples/avian_3d_character/src/renderer.rs b/examples/avian_3d_character/src/renderer.rs index e03d866bf..dd44672ec 100644 --- a/examples/avian_3d_character/src/renderer.rs +++ b/examples/avian_3d_character/src/renderer.rs @@ -28,16 +28,13 @@ impl Plugin for ExampleRendererPlugin { ), ); - // Set up visual interp plugins for Position and Rotation. This doesn't - // do anything until you add VisualInterpolationStatus components to - // entities. - app.add_plugins(VisualInterpolationPlugin::::default()); - app.add_plugins(VisualInterpolationPlugin::::default()); + // Set up visual interp plugins for Transform. Transform is updated in FixedUpdate + // by the physics plugin so we make sure that in PostUpdate we interpolate it + app.add_plugins(VisualInterpolationPlugin::::default()); // Observers that add VisualInterpolationStatus components to entities - // which receive a Position or Rotation component. - app.add_observer(add_visual_interpolation_components::); - app.add_observer(add_visual_interpolation_components::); + // which receive a Position and are predicted + app.add_observer(add_visual_interpolation_components); } } @@ -56,42 +53,30 @@ fn init(mut commands: Commands) { )); } -/// Add the VisualInterpolateStatus component to non-floor entities with -/// component `T`. Floors don't need to be visually interpolated because we +/// Add the VisualInterpolateStatus:: component to non-floor entities with +/// component `Position`. Floors don't need to be visually interpolated because we /// don't expect them to move. /// /// We query Without instead of With so that the server's /// gui will also get some visual interpolation. But we're usually just /// concerned that the client's Predicted entities get the interpolation /// treatment. -/// -/// Make sure that avian's SyncPlugin is run in PostUpdate in order to -/// incorporate the changes in pos/rot due to visual interpolation. Entities -/// rendered based on transforms will then have transforms based on the visual -/// interpolation. -fn add_visual_interpolation_components( - trigger: Trigger, - // TODO: how to guarantee that Predicted has been added before the component gets added? - query: Query, With, Without)>, +fn add_visual_interpolation_components( + // We use Position because it's added by avian later, and when it's added + // we know that Predicted is already present on the entity + trigger: Trigger, + query: Query, Without)>, mut commands: Commands, ) { if !query.contains(trigger.entity()) { return; } - debug!( - "Adding visual interp component for {:?} to {:?}", - std::any::type_name::(), - trigger.entity() - ); commands .entity(trigger.entity()) - .insert(VisualInterpolateStatus:: { - // We must trigger change detection so that the SyncPlugin will - // detect and sync changes from Position/Rotation to Transform. - // - // Without syncing interpolated pos/rot to transform, things like - // sprites, meshes, and text which render based on the *Transform* - // component (not avian's Position) will be stuttery. + .insert(VisualInterpolateStatus:: { + // We must trigger change detection on visual interpolation + // to make sure that child entities (sprites, meshes, text) + // are also interpolated trigger_change_detection: true, ..default() }); diff --git a/examples/avian_3d_character/src/server.rs b/examples/avian_3d_character/src/server.rs index 2861ceca3..a5be22c48 100644 --- a/examples/avian_3d_character/src/server.rs +++ b/examples/avian_3d_character/src/server.rs @@ -85,20 +85,20 @@ fn init(mut commands: Commands) { group: REPLICATION_GROUP, ..default() }; - commands.spawn(( - Name::new("Block"), - BlockPhysicsBundle::default(), - BlockMarker, - Position::new(Vec3::new(1.0, 1.0, 0.0)), - block_replicate_component.clone(), - )); - commands.spawn(( - Name::new("Block"), - BlockPhysicsBundle::default(), - BlockMarker, - Position::new(Vec3::new(-1.0, 1.0, 0.0)), - block_replicate_component.clone(), - )); + // commands.spawn(( + // Name::new("Block"), + // BlockPhysicsBundle::default(), + // BlockMarker, + // Position::new(Vec3::new(1.0, 1.0, 0.0)), + // block_replicate_component.clone(), + // )); + // commands.spawn(( + // Name::new("Block"), + // BlockPhysicsBundle::default(), + // BlockMarker, + // Position::new(Vec3::new(-1.0, 1.0, 0.0)), + // block_replicate_component.clone(), + // )); } pub(crate) fn replicate_inputs( diff --git a/examples/avian_3d_character/src/shared.rs b/examples/avian_3d_character/src/shared.rs index 910d092ba..59ca1e7c7 100644 --- a/examples/avian_3d_character/src/shared.rs +++ b/examples/avian_3d_character/src/shared.rs @@ -95,6 +95,7 @@ impl Plugin for SharedPlugin { // Position and Rotation are the primary source of truth so no need to // sync changes from Transform to Position. + // (we are not applying manual updates to Transform) app.insert_resource(avian3d::sync::SyncConfig { transform_to_position: false, position_to_transform: true, @@ -111,18 +112,14 @@ impl Plugin for SharedPlugin { after_physics_log.after(PhysicsSet::StepSimulation), ); - // We change SyncPlugin to PostUpdate, because we want the visually - // interpreted values synced to transform every time, not just when - // Fixed schedule runs. app.add_plugins( PhysicsPlugins::default() .build() - .disable::() - .disable::(), // disable Sleeping plugin as it can mess up physics rollbacks - // TODO: disabling sleeping plugin causes the player to fall through the floor - // .disable::(), - ) - .add_plugins(SyncPlugin::new(PostUpdate)); + .disable::(), + // disable Sleeping plugin as it can mess up physics rollbacks + // TODO: disabling sleeping plugin causes the player to fall through the floor + // .disable::(), + ); } } diff --git a/examples/avian_physics/src/client.rs b/examples/avian_physics/src/client.rs index c75fe77ff..18dc3d039 100644 --- a/examples/avian_physics/src/client.rs +++ b/examples/avian_physics/src/client.rs @@ -95,8 +95,6 @@ fn add_ball_physics( Entity, ( With, - // insert the physics components on the ball that is displayed on screen - // (either interpolated or predicted) Or<(Added, Added)>, ), >, diff --git a/examples/avian_physics/src/protocol.rs b/examples/avian_physics/src/protocol.rs index 6532954c4..121b04664 100644 --- a/examples/avian_physics/src/protocol.rs +++ b/examples/avian_physics/src/protocol.rs @@ -3,14 +3,14 @@ use bevy::prelude::*; use leafwing_input_manager::prelude::*; use serde::{Deserialize, Serialize}; +use crate::shared::color_from_id; use lightyear::client::components::{ComponentSyncMode, LerpFn}; use lightyear::client::interpolation::LinearInterpolator; use lightyear::prelude::client; use lightyear::prelude::server::{Replicate, SyncTarget}; use lightyear::prelude::*; use lightyear::utils::avian2d::*; - -use crate::shared::color_from_id; +use lightyear::utils::bevy::TransformLinearInterpolation; pub const BALL_SIZE: f32 = 15.0; pub const PLAYER_SIZE: f32 = 40.0; diff --git a/examples/avian_physics/src/renderer.rs b/examples/avian_physics/src/renderer.rs index 6741b3d65..05a4340fe 100644 --- a/examples/avian_physics/src/renderer.rs +++ b/examples/avian_physics/src/renderer.rs @@ -6,7 +6,9 @@ use bevy::color::palettes::css; use bevy::prelude::*; use bevy::render::RenderPlugin; use lightyear::client::components::Confirmed; -use lightyear::prelude::client::{InterpolationSet, PredictionSet}; +use lightyear::client::interpolation::VisualInterpolateStatus; +use lightyear::client::prediction::Predicted; +use lightyear::prelude::client::{InterpolationSet, PredictionSet, VisualInterpolationPlugin}; #[derive(Clone)] pub struct ExampleRendererPlugin { @@ -23,6 +25,14 @@ impl Plugin for ExampleRendererPlugin { .after(InterpolationSet::Interpolate) .after(PredictionSet::VisualCorrection), ); + + // add visual interpolation for Position and Rotation + // (normally we would interpolate on Transform but here this is fine + // since rendering is done via Gizmos that only depend on Position/Rotation) + app.add_plugins(VisualInterpolationPlugin::::default()); + app.add_plugins(VisualInterpolationPlugin::::default()); + app.add_observer(add_visual_interpolation_components); + if self.show_confirmed { app.add_systems( PostUpdate, @@ -34,6 +44,22 @@ impl Plugin for ExampleRendererPlugin { } } +fn add_visual_interpolation_components( + // We use Position because it's added by avian later, and when it's added + // we know that Predicted is already present on the entity + trigger: Trigger, + query: Query>, + mut commands: Commands, +) { + if !query.contains(trigger.entity()) { + return; + } + commands.entity(trigger.entity()).insert(( + VisualInterpolateStatus::::default(), + VisualInterpolateStatus::::default(), + )); +} + fn init(mut commands: Commands) { commands.spawn(Camera2d); } diff --git a/examples/avian_physics/src/shared.rs b/examples/avian_physics/src/shared.rs index 0224c65c6..6b02573f5 100644 --- a/examples/avian_physics/src/shared.rs +++ b/examples/avian_physics/src/shared.rs @@ -33,7 +33,6 @@ impl Plugin for SharedPlugin { .build() .disable::(), ) - .insert_resource(Time::::from_hz(FIXED_TIMESTEP_HZ)) .insert_resource(Gravity(Vec2::ZERO)); // add a log at the start of the physics schedule diff --git a/examples/spaceships/src/protocol.rs b/examples/spaceships/src/protocol.rs index c27b6582d..602051632 100644 --- a/examples/spaceships/src/protocol.rs +++ b/examples/spaceships/src/protocol.rs @@ -5,16 +5,16 @@ use leafwing_input_manager::prelude::*; use lightyear_examples_common::shared::FIXED_TIMESTEP_HZ; use serde::{Deserialize, Serialize}; +use crate::shared::color_from_id; use lightyear::client::components::{ComponentSyncMode, LerpFn}; use lightyear::client::interpolation::LinearInterpolator; use lightyear::prelude::client::{self, LeafwingInputConfig}; use lightyear::prelude::server::{Replicate, SyncTarget}; use lightyear::prelude::*; use lightyear::utils::avian2d::*; +use lightyear::utils::bevy::TransformLinearInterpolation; use tracing_subscriber::util::SubscriberInitExt; -use crate::shared::color_from_id; - pub const BULLET_SIZE: f32 = 1.5; pub const SHIP_WIDTH: f32 = 19.0; pub const SHIP_LENGTH: f32 = 32.0; @@ -286,5 +286,11 @@ impl Plugin for ProtocolPlugin { .add_prediction(ComponentSyncMode::Full) .add_interpolation_fn(rotation::lerp) .add_correction_fn(rotation::lerp); + + // do not replicate Transform but make sure to register an interpolation function + // for it so that we can do visual interpolation + // (another option would be to replicate transform and not use Position/Rotation at all) + app.add_interpolation::(ComponentSyncMode::None); + app.add_interpolation_fn::(TransformLinearInterpolation::lerp); } } diff --git a/examples/spaceships/src/renderer.rs b/examples/spaceships/src/renderer.rs index 10c6bc732..e793bb72f 100644 --- a/examples/spaceships/src/renderer.rs +++ b/examples/spaceships/src/renderer.rs @@ -60,15 +60,12 @@ impl Plugin for SpaceshipsRendererPlugin { app.add_plugins(EntityLabelPlugin); - // set up visual interp plugins for Position and Rotation. - // this doesn't do anything until you add VisualInterpolationStatus components to entities. - app.add_plugins(VisualInterpolationPlugin::::default()); - app.add_plugins(VisualInterpolationPlugin::::default()); + // set up visual interp plugins for Transform + app.add_plugins(VisualInterpolationPlugin::::default()); // observers that add VisualInterpolationStatus components to entities which receive - // a Position or Rotation component. - app.add_observer(add_visual_interpolation_components::); - app.add_observer(add_visual_interpolation_components::); + // a Position + app.add_observer(add_visual_interpolation_components); } } @@ -78,20 +75,13 @@ impl Plugin for SpaceshipsRendererPlugin { // We query filter With so that the correct client entities get visual-interpolation. // We don't want to visually interpolate the client's Confirmed entities, since they are not rendered. // -// If your game uses Interpolated entities as well as Predicted, change the filter to: -// -// Or<(With, With)> -// -// We must trigger change detection so that the SyncPlugin will detect and sync changes -// from Position/Rotation to Transform. -// -// Without syncing interpolated pos/rot to transform, things like sprites, meshes, and text which -// render based on the *Transform* component (not avian's Position) will be stuttery. -// -// (Note also that we've configured avian's SyncPlugin to run in PostUpdate) -fn add_visual_interpolation_components( - trigger: Trigger, - q: Query, Without, With)>, +// We must trigger change detection so that the Transform updates from interpolation +// will be propagated to children (sprites, meshes, text, etc.) +fn add_visual_interpolation_components( + // We use Position because it's added by avian later, and when it's added + // we know that Predicted is already present on the entity + trigger: Trigger, + q: Query, With)>, mut commands: Commands, ) { if !q.contains(trigger.entity()) { @@ -100,7 +90,10 @@ fn add_visual_interpolation_components( debug!("Adding visual interp component to {:?}", trigger.entity()); commands .entity(trigger.entity()) - .insert(VisualInterpolateStatus:: { + .insert(VisualInterpolateStatus:: { + // We must trigger change detection on visual interpolation + // to make sure that child entities (sprites, meshes, text) + // are also interpolated trigger_change_detection: true, ..default() }); diff --git a/examples/spaceships/src/shared.rs b/examples/spaceships/src/shared.rs index 6665d09b5..706515391 100644 --- a/examples/spaceships/src/shared.rs +++ b/examples/spaceships/src/shared.rs @@ -49,10 +49,8 @@ impl Plugin for SharedPlugin { }); // We change SyncPlugin to PostUpdate, because we want the visually interpreted values // synced to transform every time, not just when Fixed schedule runs. - app.add_plugins(PhysicsPlugins::default().build().disable::()) - .add_plugins(SyncPlugin::new(PostUpdate)); + app.add_plugins(PhysicsPlugins::default().build()); - app.insert_resource(Time::::from_hz(FIXED_TIMESTEP_HZ)); app.insert_resource(Gravity(Vec2::ZERO)); // our systems run in FixedUpdate, avian's systems run in FixedPostUpdate. app.add_systems( diff --git a/lightyear/src/protocol/component.rs b/lightyear/src/protocol/component.rs index 7359e0816..f51c2e6e6 100644 --- a/lightyear/src/protocol/component.rs +++ b/lightyear/src/protocol/component.rs @@ -537,6 +537,7 @@ mod interpolation { ) }); } + pub(crate) fn interpolation_mode(&self) -> ComponentSyncMode { let kind = ComponentKind::of::(); self.interpolation_map diff --git a/lightyear/src/utils/avian2d.rs b/lightyear/src/utils/avian2d.rs index d1525b4bb..10eb576a1 100644 --- a/lightyear/src/utils/avian2d.rs +++ b/lightyear/src/utils/avian2d.rs @@ -4,6 +4,7 @@ use crate::shared::replication::delta::Diffable; use crate::shared::sets::{ClientMarker, InternalReplicationSet, ServerMarker}; use avian2d::math::Scalar; use avian2d::prelude::*; +use bevy::app::{RunFixedMainLoop, RunFixedMainLoopSystem}; use bevy::prelude::TransformSystem::TransformPropagate; use bevy::prelude::{App, FixedPostUpdate, Plugin}; use bevy::prelude::{IntoSystemSetConfigs, PostUpdate}; @@ -41,11 +42,16 @@ impl Plugin for Avian2dPlugin { PredictionSet::IncrementRollbackTick, InterpolationSet::UpdateVisualInterpolationState, ) + .after(PhysicsSet::StepSimulation) .after(PhysicsSet::Sync), ); - // if the Avian Sync happens in PostUpdate (because the visual interpolated Position/Rotation are updated - // every frame in PostUpdate), and we want to sync them every frame because some entities (text, etc.) - // might depend on Transform + app.configure_sets( + RunFixedMainLoop, + PhysicsSet::Sync.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop), + ); + // if we are syncing Position/Rotation in PostUpdate (not in FixedLast because FixedLast might not run + // in some frames), and running VisualInterpolation for Position/Rotation, + // we want to first interpolate and then sync to transform app.configure_sets( PostUpdate, ( diff --git a/lightyear/src/utils/avian3d.rs b/lightyear/src/utils/avian3d.rs index f13188a76..dab191d88 100644 --- a/lightyear/src/utils/avian3d.rs +++ b/lightyear/src/utils/avian3d.rs @@ -6,7 +6,7 @@ use avian3d::math::Scalar; use avian3d::prelude::*; use bevy::app::{App, FixedPostUpdate, Plugin}; use bevy::prelude::TransformSystem::TransformPropagate; -use bevy::prelude::{IntoSystemSetConfigs, PostUpdate}; +use bevy::prelude::{IntoSystemSetConfigs, PostUpdate, RunFixedMainLoop, RunFixedMainLoopSystem}; use tracing::trace; pub(crate) struct Avian3dPlugin; @@ -40,10 +40,17 @@ impl Plugin for Avian3dPlugin { PredictionSet::IncrementRollbackTick, InterpolationSet::UpdateVisualInterpolationState, ) + .after(PhysicsSet::StepSimulation) .after(PhysicsSet::Sync), ); - // if the sync happens in PostUpdate (because the visual interpolated Position/Rotation are updated every frame in - // PostUpdate), and we want to sync them every frame because some entities (text, etc.) might depend on Transform + + app.configure_sets( + RunFixedMainLoop, + PhysicsSet::Sync.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop), + ); + // if we are syncing Position/Rotation in PostUpdate (not in FixedLast because FixedLast might not run + // in some frames), and running VisualInterpolation for Position/Rotation, + // we want to first interpolate and then sync to transform app.configure_sets( PostUpdate, ( @@ -53,7 +60,6 @@ impl Plugin for Avian3dPlugin { ) .chain(), ); - // Add rollback for some non-replicated resources // app.add_resource_rollback::(); // app.add_rollback::(); From 5f15db4e942894cc597bdcb52cfa96b088145b0b Mon Sep 17 00:00:00 2001 From: cBournhonesque Date: Fri, 17 Jan 2025 17:58:00 -0500 Subject: [PATCH 2/2] extra --- examples/avian_3d_character/src/server.rs | 28 +++++++++---------- .../interpolation/visual_interpolation.rs | 7 ++--- lightyear/src/utils/avian3d.rs | 1 - 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/examples/avian_3d_character/src/server.rs b/examples/avian_3d_character/src/server.rs index a5be22c48..2861ceca3 100644 --- a/examples/avian_3d_character/src/server.rs +++ b/examples/avian_3d_character/src/server.rs @@ -85,20 +85,20 @@ fn init(mut commands: Commands) { group: REPLICATION_GROUP, ..default() }; - // commands.spawn(( - // Name::new("Block"), - // BlockPhysicsBundle::default(), - // BlockMarker, - // Position::new(Vec3::new(1.0, 1.0, 0.0)), - // block_replicate_component.clone(), - // )); - // commands.spawn(( - // Name::new("Block"), - // BlockPhysicsBundle::default(), - // BlockMarker, - // Position::new(Vec3::new(-1.0, 1.0, 0.0)), - // block_replicate_component.clone(), - // )); + commands.spawn(( + Name::new("Block"), + BlockPhysicsBundle::default(), + BlockMarker, + Position::new(Vec3::new(1.0, 1.0, 0.0)), + block_replicate_component.clone(), + )); + commands.spawn(( + Name::new("Block"), + BlockPhysicsBundle::default(), + BlockMarker, + Position::new(Vec3::new(-1.0, 1.0, 0.0)), + block_replicate_component.clone(), + )); } pub(crate) fn replicate_inputs( diff --git a/lightyear/src/client/interpolation/visual_interpolation.rs b/lightyear/src/client/interpolation/visual_interpolation.rs index ec567e166..2aba9b7b0 100644 --- a/lightyear/src/client/interpolation/visual_interpolation.rs +++ b/lightyear/src/client/interpolation/visual_interpolation.rs @@ -63,10 +63,7 @@ impl Plugin for VisualInterpolationPlugin { ) .chain(), ); - app.configure_sets( - FixedPostUpdate, - InterpolationSet::UpdateVisualInterpolationState, - ); + app.configure_sets(FixedLast, InterpolationSet::UpdateVisualInterpolationState); app.configure_sets( PostUpdate, InterpolationSet::VisualInterpolation @@ -82,7 +79,7 @@ impl Plugin for VisualInterpolationPlugin { .in_set(InterpolationSet::RestoreVisualInterpolation), ); app.add_systems( - FixedPostUpdate, + FixedLast, update_visual_interpolation_status:: .in_set(InterpolationSet::UpdateVisualInterpolationState), ); diff --git a/lightyear/src/utils/avian3d.rs b/lightyear/src/utils/avian3d.rs index dab191d88..893d4c1b0 100644 --- a/lightyear/src/utils/avian3d.rs +++ b/lightyear/src/utils/avian3d.rs @@ -38,7 +38,6 @@ impl Plugin for Avian3dPlugin { // run physics before updating the prediction history PredictionSet::UpdateHistory, PredictionSet::IncrementRollbackTick, - InterpolationSet::UpdateVisualInterpolationState, ) .after(PhysicsSet::StepSimulation) .after(PhysicsSet::Sync),