diff --git a/.cargo/config.toml b/.cargo/config.toml index 64886e837..7fad8e1ac 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,3 +5,11 @@ rustflags = ["--cfg", "web_sys_unstable_apis"] [target.wasm32-unknown-unknown] runner = "wasm-server-runner" + +# Enable max optimizations for dependencies, but not for our code: +[profile.dev.package."*"] +opt-level = 3 + +# Enable only a small amount of optimization in debug mode +[profile.dev] +opt-level = 1 diff --git a/README.md b/README.md index 7bdb797f1..c7b2da6f0 100644 --- a/README.md +++ b/README.md @@ -70,10 +70,9 @@ You can also find more information in this WIP [book](https://cbournhonesque.git - Examples - *Lightyear* has plenty of examples demonstrating all these features, as well as the integration with other bevy crates such as `bevy_xpbd_2d` -## Planned features +## Supported bevy version -- Metrics - - Improve the metrics/logs -- Serialization - - Improve the serialization code to be more ergonomic, and to have fewer copies. -- Correctness: add more unit tests for replication edge-cases +| Lightyear | Bevy | +|-----------|------| +| 0.10 | 0.13 | +| 0.9 | 0.12 | diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 2276cbb28..01b385c21 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -16,7 +16,7 @@ license = "MIT OR Apache-2.0" lightyear = { path = "../lightyear" } crossbeam-channel = "0.5.10" anyhow = { version = "1.0.75", features = [] } -bevy = { version = "0.12", features = ["bevy_core_pipeline"] } +bevy = { version = "0.13", features = ["bevy_core_pipeline"] } derive_more = { version = "0.99", features = ["add", "mul"] } divan = "0.1.11" serde = { version = "1.0.188", features = ["derive"] } diff --git a/benches/spawn.rs b/benches/spawn.rs index c4a6b02e6..f0a610ab1 100644 --- a/benches/spawn.rs +++ b/benches/spawn.rs @@ -9,7 +9,7 @@ use bevy::utils::Duration; use divan::{AllocProfiler, Bencher}; use lightyear::client::sync::SyncConfig; use lightyear::prelude::client::{InterpolationConfig, PredictionConfig}; -use lightyear::prelude::{ClientId, LogConfig, NetworkTarget, SharedConfig, TickConfig}; +use lightyear::prelude::{ClientId, NetworkTarget, SharedConfig, TickConfig}; use lightyear_benches::local_stepper::{LocalBevyStepper, Step as LocalStep}; use lightyear_benches::protocol::*; use lightyear_benches::stepper::{BevyStepper, Step}; @@ -36,10 +36,6 @@ fn spawn_local(bencher: Bencher, n: usize) { let tick_duration = Duration::from_millis(10); let shared_config = SharedConfig { tick: TickConfig::new(tick_duration), - log: LogConfig { - level: Level::WARN, - ..default() - }, ..default() }; let mut stepper = LocalBevyStepper::new( @@ -100,10 +96,6 @@ fn spawn(bencher: Bencher, n: usize) { let tick_duration = Duration::from_millis(10); let shared_config = SharedConfig { tick: TickConfig::new(tick_duration), - log: LogConfig { - level: Level::WARN, - ..default() - }, ..default() }; let mut stepper = LocalBevyStepper::new( diff --git a/benches/src/protocol.rs b/benches/src/protocol.rs index 55b2f9c30..9e71daca0 100644 --- a/benches/src/protocol.rs +++ b/benches/src/protocol.rs @@ -2,6 +2,7 @@ use bevy::prelude::Component; use bevy::utils::default; use derive_more::{Add, Mul}; use serde::{Deserialize, Serialize}; +use std::ops::Mul; use lightyear::prelude::*; @@ -22,6 +23,14 @@ pub enum MyMessageProtocol { #[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq, Add, Mul)] pub struct Component1(pub f32); +impl Mul for &Component1 { + type Output = Component1; + + fn mul(self, rhs: f32) -> Self::Output { + Component1(self.0 * rhs) + } +} + #[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq, Add, Mul)] pub struct Component2(pub f32); diff --git a/book/src/concepts/advanced_replication/prespawning.md b/book/src/concepts/advanced_replication/prespawning.md index 181e6c55a..1cb68e894 100644 --- a/book/src/concepts/advanced_replication/prespawning.md +++ b/book/src/concepts/advanced_replication/prespawning.md @@ -40,7 +40,7 @@ That's it! The various system-sets for prespawning are: - PreUpdate schedule: - - PredictionSet::SpawnPrediction: we first run the prespawn match system to match the pre-spawned entities with their corresponding server entity. + - `PredictionSet::SpawnPrediction`: we first run the prespawn match system to match the pre-spawned entities with their corresponding server entity. If there is a match, we remove the PreSpawnedPlayerObject component and add the Predicted/Confirmed components. We then run an apply_deferred, and we run the normal predicted spawn system, which will skip all confirmed entities that already have a `predicted` counterpart (i.e. were matched) diff --git a/book/src/concepts/advanced_replication/visual_interpolation.md b/book/src/concepts/advanced_replication/visual_interpolation.md index 8e65a9077..fb87eba9e 100644 --- a/book/src/concepts/advanced_replication/visual_interpolation.md +++ b/book/src/concepts/advanced_replication/visual_interpolation.md @@ -39,7 +39,7 @@ Currently lightyear only provides option 1: interpolate between the previous tic ## How does it work? There are 3 main systems: -- during FixedUpdate, we run `update_visual_interpolation_status` after the FixedUpdate::Main set (meaning that the simulation has run). +- during FixedUpdate, we run `update_visual_interpolation_status` after the `FixedUpdate::Main` set (meaning that the simulation has run). In that system we keep track of the value of the component on the current tick and the previous tick - during PostUpdate, we run `visual_interpolation` which will interpolate the value of the component between the last two tick values stored in the `update_visual_interpolation_status` system. We use the `Time::overstep_percentage()` to determine how much to interpolate diff --git a/book/src/tutorial/advanced_systems.md b/book/src/tutorial/advanced_systems.md index 8e0a1081b..7b753d5f6 100644 --- a/book/src/tutorial/advanced_systems.md +++ b/book/src/tutorial/advanced_systems.md @@ -70,9 +70,8 @@ pub(crate) fn movement( } } } -app.add_systems(FixedUpdate, movement.in_set(FixedUpdateSet::Main)); +app.add_systems(FixedMain, movement); ``` -Don't forget that fixed-timestep simulation systems must run in the `FixedUpdateSet::Main` `SystemSet`. You might be wondering what the `ComponentSyncMode` is. This crate specifies 3 different modes for synchronizing components between the `Confirmed` and `Predicted` entities: diff --git a/book/src/tutorial/basic_systems.md b/book/src/tutorial/basic_systems.md index 88e78af5b..de6234ead 100644 --- a/book/src/tutorial/basic_systems.md +++ b/book/src/tutorial/basic_systems.md @@ -254,10 +254,10 @@ pub(crate) fn movement( } } } -app.add_systems(FixedUpdate, movement.in_set(FixedUpdateSet::Main)); +app.add_systems(FixedMain, movement); ``` -Any fixed-update simulation system (physics, etc.) must run in the `FixedUpdateSet::Main` `SystemSet` to behave correctly. +Any fixed-update simulation system (physics, etc.) must run in the `FixedMain` `Schedule` to behave correctly. ## Displaying entities diff --git a/examples/bullet_prespawn/Cargo.toml b/examples/bullet_prespawn/Cargo.toml index f3d32b7b4..1df5f6761 100644 --- a/examples/bullet_prespawn/Cargo.toml +++ b/examples/bullet_prespawn/Cargo.toml @@ -11,9 +11,9 @@ metrics = ["lightyear/metrics", "dep:metrics-exporter-prometheus"] mock_time = ["lightyear/mock_time"] [dependencies] -bevy_framepace = "0.14.1" -bevy_screen_diagnostics = "0.4.0" -leafwing-input-manager = "0.11.2" +bevy_framepace = "0.15" +bevy_screen_diagnostics = "0.5.0" +leafwing-input-manager = "0.13" lightyear = { path = "../../lightyear", features = [ "webtransport", "websocket", @@ -25,11 +25,11 @@ serde = { version = "1.0.188", features = ["derive"] } anyhow = { version = "1.0.75", features = [] } tracing = "0.1" tracing-subscriber = "0.3.17" -bevy = { version = "0.12", features = ["bevy_core_pipeline"] } +bevy = { version = "0.13", features = ["bevy_core_pipeline"] } derive_more = { version = "0.99", features = ["add", "mul"] } rand = "0.8.1" clap = { version = "4.4", features = ["derive"] } mock_instant = "0.3" metrics-exporter-prometheus = { version = "0.13.0", optional = true } -bevy-inspector-egui = "0.22.1" +#bevy-inspector-egui = "0.22.1" cfg-if = "1.0.0" diff --git a/examples/bullet_prespawn/src/client.rs b/examples/bullet_prespawn/src/client.rs index 0367c31ef..c76d5eb61 100644 --- a/examples/bullet_prespawn/src/client.rs +++ b/examples/bullet_prespawn/src/client.rs @@ -5,7 +5,7 @@ use bevy::app::PluginGroupBuilder; use bevy::ecs::schedule::{LogLevel, ScheduleBuildSettings}; use bevy::prelude::*; use bevy::utils::Duration; -use leafwing_input_manager::action_state::ActionDiff; +use leafwing_input_manager::action_state::ActionData; use leafwing_input_manager::axislike::DualAxisData; use leafwing_input_manager::buttonlike::ButtonState::Pressed; use leafwing_input_manager::orientation::Orientation; @@ -93,7 +93,7 @@ impl PluginGroup for ClientPluginGroup { .add(shared::SharedPlugin) .add(LeafwingInputPlugin::::new( LeafwingInputConfig:: { - send_diffs_only: false, + send_diffs_only: true, ..default() }, )) @@ -120,16 +120,16 @@ impl Plugin for ExampleClientPlugin { // To send global inputs, insert the ActionState and the InputMap as Resources app.init_resource::>(); app.insert_resource(InputMap::::new([ - (KeyCode::M, AdminActions::SendMessage), - (KeyCode::R, AdminActions::Reset), + (AdminActions::SendMessage, KeyCode::KeyM), + (AdminActions::Reset, KeyCode::KeyR), ])); app.insert_resource(ClientIdResource { client_id: self.client_id, }); app.add_systems(Startup, init); - // all actions related-system that can be rolled back should be in FixedUpdateSet::Main - // app.add_systems(FixedUpdate, player_movement.in_set(FixedUpdateSet::Main)); + // all actions related-system that can be rolled back should be in the `FixedUpdate` schdule + // app.add_systems(FixedUpdate, player_movement); // we update the ActionState manually from cursor, so we need to put it in the ManualControl set app.add_systems( PreUpdate, @@ -162,11 +162,11 @@ pub(crate) fn init(mut commands: Commands, mut client: ClientMut, plugin: Res { if !headless { - app.add_plugins(DefaultPlugins.build().disable::()); + app.add_plugins(DefaultPlugins.build().set(LogPlugin { + level: Level::INFO, + filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), + })); } else { app.add_plugins(MinimalPlugins); } if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } // this is async because we need to load the certificate from io // we need async_compat because wtransport expects a tokio reactor @@ -173,12 +178,13 @@ fn setup_client(app: &mut App, cli: Cli) { // NOTE: create the default plugins first so that the async task pools are initialized // use the default bevy logger for now // (the lightyear logger doesn't handle wasm) - app.add_plugins(DefaultPlugins.set(LogPlugin { + app.add_plugins(DefaultPlugins.build().set(LogPlugin { level: Level::INFO, filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), })); if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } let server_addr = SocketAddr::new(server_addr.into(), server_port); let client_plugin_group = diff --git a/examples/bullet_prespawn/src/protocol.rs b/examples/bullet_prespawn/src/protocol.rs index 0d2356f77..392055848 100644 --- a/examples/bullet_prespawn/src/protocol.rs +++ b/examples/bullet_prespawn/src/protocol.rs @@ -1,5 +1,4 @@ use bevy::prelude::*; -use bevy::utils::EntityHashSet; use derive_more::{Add, Mul}; use leafwing_input_manager::prelude::*; use lightyear::client::components::LerpFn; diff --git a/examples/bullet_prespawn/src/server.rs b/examples/bullet_prespawn/src/server.rs index 66d6566b5..d2edfbb2f 100644 --- a/examples/bullet_prespawn/src/server.rs +++ b/examples/bullet_prespawn/src/server.rs @@ -29,11 +29,8 @@ impl ServerPluginGroup { Certificate::load("../certificates/cert.pem", "../certificates/key.pem") .await .unwrap(); - let digest = certificate.hashes()[0].fmt_as_dotted_hex(); - dbg!( - "Generated self-signed certificate with digest: {:?}", - digest - ); + let digest = &certificate.hashes()[0]; + println!("Generated self-signed certificate with digest: {}", digest); TransportConfig::WebTransportServer { server_addr, certificate, @@ -87,10 +84,10 @@ impl Plugin for ExampleServerPlugin { // Re-adding Replicate components to client-replicated entities must be done in this set for proper handling. app.add_systems( PreUpdate, - (replicate_players).in_set(MainSet::ClientReplication), + replicate_players.in_set(MainSet::ClientReplication), ); - // the physics/FixedUpdates systems that consume inputs should be run in this set - // app.add_systems(FixedUpdate, (player_movement).in_set(FixedUpdateSet::Main)); + // the physics/FixedUpdates systems that consume inputs should be run in the `FixedUpdate` schedule + // app.add_systems(FixedUpdate, player_movement); app.add_systems(Update, handle_disconnections); } } diff --git a/examples/bullet_prespawn/src/shared.rs b/examples/bullet_prespawn/src/shared.rs index adf2bfd92..0a1f688de 100644 --- a/examples/bullet_prespawn/src/shared.rs +++ b/examples/bullet_prespawn/src/shared.rs @@ -28,11 +28,6 @@ pub fn shared_config() -> SharedConfig { tick: TickConfig { tick_duration: Duration::from_secs_f64(1.0 / FIXED_TIMESTEP_HZ), }, - log: LogConfig { - level: Level::INFO, - filter: "wgpu=error,wgpu_hal=error,naga=warn,bevy_app=info,bevy_render=warn,quinn=warn" - .to_string(), - }, } } @@ -72,9 +67,8 @@ impl Plugin for SharedPlugin { // registry types for reflection app.register_type::(); - app.add_systems(FixedUpdate, fixed_update_log.after(FixedUpdateSet::Main)); - // every system that is physics-based and can be rolled-back has to be scheduled - // in FixedUpdateSet::Main + app.add_systems(FixedPostUpdate, fixed_update_log); + // every system that is physics-based and can be rolled-back has to be in the `FixedUpdate` schedule app.add_systems( FixedUpdate, // ideally, during rollback, we'd despawn the pre-predicted player objects and then respawn them during shoot_bullet. @@ -87,8 +81,7 @@ impl Plugin for SharedPlugin { // shoot_bullet.run_if(not(is_in_rollback)), move_bullet, ) - .chain() - .in_set(FixedUpdateSet::Main), + .chain(), ); // NOTE: we need to create prespawned entities in FixedUpdate, because only then are inputs correctly associated with a tick // Example: @@ -127,8 +120,10 @@ pub(crate) fn shared_player_movement( ) { const PLAYER_MOVE_SPEED: f32 = 10.0; // warn!(?action, "action state"); - let mouse_position = action - .action_data(PlayerActions::MoveCursor) + let Some(cursor_data) = action.action_data(&PlayerActions::MoveCursor) else { + return; + }; + let mouse_position = cursor_data .axis_pair .map(|axis| axis.xy()) .unwrap_or_default(); @@ -141,16 +136,16 @@ pub(crate) fn shared_player_movement( } // TODO: look_at should work // transform.look_at(Vec3::new(mouse_position.x, mouse_position.y, 0.0), Vec3::Y); - if action.pressed(PlayerActions::Up) { + if action.pressed(&PlayerActions::Up) { transform.translation.y += PLAYER_MOVE_SPEED; } - if action.pressed(PlayerActions::Down) { + if action.pressed(&PlayerActions::Down) { transform.translation.y -= PLAYER_MOVE_SPEED; } - if action.pressed(PlayerActions::Right) { + if action.pressed(&PlayerActions::Right) { transform.translation.x += PLAYER_MOVE_SPEED; } - if action.pressed(PlayerActions::Left) { + if action.pressed(&PlayerActions::Left) { transform.translation.x -= PLAYER_MOVE_SPEED; } } @@ -255,10 +250,10 @@ pub(crate) fn shoot_bullet( // NOTE: cannot spawn the bullet during FixedUpdate because then during rollback we spawn a new bullet! For now just set the system to // run when not in rollback // TODO: if we were running this in FixedUpdate, we would need to `consume` the action. (in case there are several fixed-update steps - // ine one frame). We also cannot use JustPressed, becuase we could have a frame with no FixedUpdate. + // ine one frame). We also cannot use JustPressed, because we could have a frame with no FixedUpdate. // NOTE: pressed lets you shoot many bullets, which can be cool - if action.pressed(PlayerActions::Shoot) { - action.consume(PlayerActions::Shoot); + if action.pressed(&PlayerActions::Shoot) { + action.consume(&PlayerActions::Shoot); info!(?tick, pos=?transform.translation.truncate(), rot=?transform.rotation.to_euler(EulerRot::XYZ).2, "spawn bullet"); diff --git a/examples/client_replication/Cargo.toml b/examples/client_replication/Cargo.toml index e780fdcdc..6e323663a 100644 --- a/examples/client_replication/Cargo.toml +++ b/examples/client_replication/Cargo.toml @@ -28,7 +28,7 @@ serde = { version = "1.0.188", features = ["derive"] } anyhow = { version = "1.0.75", features = [] } tracing = "0.1" tracing-subscriber = "0.3.17" -bevy = { version = "0.12", features = ["bevy_core_pipeline"] } +bevy = { version = "0.13", features = ["bevy_core_pipeline"] } derive_more = { version = "0.99", features = ["add", "mul"] } rand = "0.8.1" clap = { version = "4.4", features = ["derive"] } diff --git a/examples/client_replication/src/client.rs b/examples/client_replication/src/client.rs index c8705022c..e3c5d53a4 100644 --- a/examples/client_replication/src/client.rs +++ b/examples/client_replication/src/client.rs @@ -93,15 +93,13 @@ impl Plugin for ExampleClientPlugin { client_id: self.client_id, }); app.add_systems(Startup, init); + // Inputs need to be buffered in the `FixedPreUpdate` schedule app.add_systems( - FixedUpdate, + FixedPreUpdate, buffer_input.in_set(InputSystemSet::BufferInputs), ); - // all actions related-system that can be rolled back should be in FixedUpdateSet::Main - app.add_systems( - FixedUpdate, - (player_movement, delete_player).in_set(FixedUpdateSet::Main), - ); + // all actions related-system that can be rolled back should be in the `FixedUpdate` schedule + app.add_systems(FixedUpdate, (player_movement, delete_player)); app.add_systems( Update, ( @@ -137,29 +135,29 @@ pub(crate) fn init(mut commands: Commands, mut client: ClientMut, plugin: Res>) { +pub(crate) fn buffer_input(mut client: ClientMut, keypress: Res>) { let mut direction = Direction { up: false, down: false, left: false, right: false, }; - if keypress.pressed(KeyCode::W) || keypress.pressed(KeyCode::Up) { + if keypress.pressed(KeyCode::KeyW) || keypress.pressed(KeyCode::ArrowUp) { direction.up = true; } - if keypress.pressed(KeyCode::S) || keypress.pressed(KeyCode::Down) { + if keypress.pressed(KeyCode::KeyS) || keypress.pressed(KeyCode::ArrowDown) { direction.down = true; } - if keypress.pressed(KeyCode::A) || keypress.pressed(KeyCode::Left) { + if keypress.pressed(KeyCode::KeyA) || keypress.pressed(KeyCode::ArrowLeft) { direction.left = true; } - if keypress.pressed(KeyCode::D) || keypress.pressed(KeyCode::Right) { + if keypress.pressed(KeyCode::KeyD) || keypress.pressed(KeyCode::ArrowRight) { direction.right = true; } if !direction.is_none() { return client.add_input(Inputs::Direction(direction)); } - if keypress.pressed(KeyCode::K) { + if keypress.pressed(KeyCode::KeyK) { // currently, directions is an enum and we can only add one input per tick return client.add_input(Inputs::Delete); } @@ -304,9 +302,9 @@ pub(crate) fn receive_message(mut reader: EventReader>) { /// Send messages from server to clients pub(crate) fn send_message( mut client: ResMut, - input: Res>, + input: Res>, ) { - if input.pressed(KeyCode::M) { + if input.pressed(KeyCode::KeyM) { let message = Message1(5); info!("Send message: {:?}", message); // the message will be re-broadcasted by the server to all clients diff --git a/examples/client_replication/src/main.rs b/examples/client_replication/src/main.rs index 6e1fdd0eb..7650b7471 100644 --- a/examples/client_replication/src/main.rs +++ b/examples/client_replication/src/main.rs @@ -19,7 +19,7 @@ use bevy::log::{Level, LogPlugin}; use bevy::prelude::*; use bevy::tasks::IoTaskPool; use bevy::DefaultPlugins; -use bevy_inspector_egui::quick::WorldInspectorPlugin; +// use bevy_inspector_egui::quick::WorldInspectorPlugin; use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; @@ -28,6 +28,7 @@ use crate::client::ClientPluginGroup; use crate::server::ServerPluginGroup; use lightyear::connection::netcode::{ClientId, Key}; use lightyear::prelude::TransportConfig; +use lightyear::shared::log::add_log_layer; // Use a port of 0 to automatically select a port pub const CLIENT_PORT: u16 = 0; @@ -120,13 +121,17 @@ fn setup(app: &mut App, cli: Cli) { transport, } => { if !headless { - app.add_plugins(DefaultPlugins.build().disable::()); + app.add_plugins(DefaultPlugins.build().set(LogPlugin { + level: Level::INFO, + filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), + })); } else { app.add_plugins(MinimalPlugins); } if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } // this is async because we need to load the certificate from io // we need async_compat because wtransport expects a tokio reactor @@ -161,12 +166,13 @@ fn setup_client(app: &mut App, cli: Cli) { // NOTE: create the default plugins first so that the async task pools are initialized // use the default bevy logger for now // (the lightyear logger doesn't handle wasm) - app.add_plugins(DefaultPlugins.set(LogPlugin { + app.add_plugins(DefaultPlugins.build().set(LogPlugin { level: Level::INFO, filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), })); if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } let server_addr = SocketAddr::new(server_addr.into(), server_port); let client_plugin_group = diff --git a/examples/client_replication/src/protocol.rs b/examples/client_replication/src/protocol.rs index 943ca2997..2b7a08c33 100644 --- a/examples/client_replication/src/protocol.rs +++ b/examples/client_replication/src/protocol.rs @@ -1,5 +1,4 @@ use bevy::prelude::{default, Bundle, Color, Component, Deref, DerefMut, Entity, Vec2}; -use bevy::utils::EntityHashSet; use derive_more::{Add, Mul}; use lightyear::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/examples/client_replication/src/server.rs b/examples/client_replication/src/server.rs index f57b185b6..c04fa6fae 100644 --- a/examples/client_replication/src/server.rs +++ b/examples/client_replication/src/server.rs @@ -26,11 +26,8 @@ impl ServerPluginGroup { Certificate::load("../certificates/cert.pem", "../certificates/key.pem") .await .unwrap(); - let digest = certificate.hashes()[0].fmt_as_dotted_hex(); - dbg!( - "Generated self-signed certificate with digest: {:?}", - digest - ); + let digest = &certificate.hashes()[0]; + println!("Generated self-signed certificate with digest: {}", digest); TransportConfig::WebTransportServer { server_addr, certificate, @@ -85,10 +82,7 @@ impl Plugin for ExampleServerPlugin { (replicate_cursors, replicate_players).in_set(MainSet::ClientReplication), ); // the physics/FixedUpdates systems that consume inputs should be run in this set - app.add_systems( - FixedUpdate, - (movement, delete_player).in_set(FixedUpdateSet::Main), - ); + app.add_systems(FixedUpdate, (movement, delete_player)); app.add_systems(Update, handle_disconnections); } } diff --git a/examples/client_replication/src/shared.rs b/examples/client_replication/src/shared.rs index a753877f1..bbc32512e 100644 --- a/examples/client_replication/src/shared.rs +++ b/examples/client_replication/src/shared.rs @@ -15,11 +15,6 @@ pub fn shared_config() -> SharedConfig { tick: TickConfig { tick_duration: Duration::from_secs_f64(1.0 / 64.0), }, - log: LogConfig { - level: Level::INFO, - filter: "wgpu=error,wgpu_hal=error,naga=warn,bevy_app=info,bevy_render=warn,quinn=warn" - .to_string(), - }, } } diff --git a/examples/interest_management/Cargo.toml b/examples/interest_management/Cargo.toml index 31b607f66..97df0dde7 100644 --- a/examples/interest_management/Cargo.toml +++ b/examples/interest_management/Cargo.toml @@ -17,7 +17,7 @@ metrics = ["lightyear/metrics", "dep:metrics-exporter-prometheus"] mock_time = ["lightyear/mock_time"] [dependencies] -leafwing-input-manager = "0.11.2" +leafwing-input-manager = "0.13" lightyear = { path = "../../lightyear", features = [ "webtransport", "websocket", @@ -29,11 +29,11 @@ serde = { version = "1.0.188", features = ["derive"] } anyhow = { version = "1.0.75", features = [] } tracing = "0.1" tracing-subscriber = "0.3.17" -bevy = { version = "0.12", features = ["bevy_core_pipeline"] } +bevy = { version = "0.13", features = ["bevy_core_pipeline"] } derive_more = { version = "0.99", features = ["add", "mul"] } rand = "0.8.5" clap = { version = "4.4", features = ["derive"] } mock_instant = "0.3" metrics-exporter-prometheus = { version = "0.13.0", optional = true } -bevy-inspector-egui = "0.22.1" +#bevy-inspector-egui = "0.22.1" cfg-if = "1.0.0" diff --git a/examples/interest_management/src/client.rs b/examples/interest_management/src/client.rs index f91985b1f..bc1490ae7 100644 --- a/examples/interest_management/src/client.rs +++ b/examples/interest_management/src/client.rs @@ -102,7 +102,7 @@ impl Plugin for ExampleClientPlugin { client_id: self.client_id, }); app.add_systems(Startup, init); - app.add_systems(FixedUpdate, movement.in_set(FixedUpdateSet::Main)); + app.add_systems(FixedUpdate, movement); app.add_systems( Update, ( diff --git a/examples/interest_management/src/main.rs b/examples/interest_management/src/main.rs index ccc37b353..6e22e936d 100644 --- a/examples/interest_management/src/main.rs +++ b/examples/interest_management/src/main.rs @@ -22,7 +22,7 @@ use bevy::log::{Level, LogPlugin}; use bevy::prelude::*; use bevy::tasks::IoTaskPool; use bevy::DefaultPlugins; -use bevy_inspector_egui::quick::WorldInspectorPlugin; +// use bevy_inspector_egui::quick::WorldInspectorPlugin; use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; @@ -31,6 +31,7 @@ use crate::client::ClientPluginGroup; use crate::server::ServerPluginGroup; use lightyear::connection::netcode::{ClientId, Key}; use lightyear::prelude::TransportConfig; +use lightyear::shared::log::add_log_layer; // Use a port of 0 to automatically select a port pub const CLIENT_PORT: u16 = 0; @@ -122,13 +123,17 @@ fn setup(app: &mut App, cli: Cli) { transport, } => { if !headless { - app.add_plugins(DefaultPlugins.build().disable::()); + app.add_plugins(DefaultPlugins.build().set(LogPlugin { + level: Level::INFO, + filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), + })); } else { app.add_plugins(MinimalPlugins); } if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } // this is async because we need to load the certificate from io // we need async_compat because wtransport expects a tokio reactor @@ -163,13 +168,14 @@ fn setup_client(app: &mut App, cli: Cli) { // NOTE: create the default plugins first so that the async task pools are initialized // use the default bevy logger for now // (the lightyear logger doesn't handle wasm) - app.add_plugins(DefaultPlugins.set(LogPlugin { + app.add_plugins(DefaultPlugins.build().set(LogPlugin { level: Level::INFO, filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), })); if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } let server_addr = SocketAddr::new(server_addr.into(), server_port); let client_plugin_group = diff --git a/examples/interest_management/src/protocol.rs b/examples/interest_management/src/protocol.rs index 5b53c26e0..589a706fe 100644 --- a/examples/interest_management/src/protocol.rs +++ b/examples/interest_management/src/protocol.rs @@ -1,5 +1,5 @@ +use bevy::math::Vec2; use bevy::prelude::*; -use bevy::utils::EntityHashSet; use derive_more::{Add, Mul}; use leafwing_input_manager::action_state::ActionState; use leafwing_input_manager::input_map::InputMap; @@ -48,16 +48,16 @@ impl PlayerBundle { } pub(crate) fn get_input_map() -> InputMap { InputMap::new([ - (KeyCode::Right, Inputs::Right), - (KeyCode::D, Inputs::Right), - (KeyCode::Left, Inputs::Left), - (KeyCode::A, Inputs::Left), - (KeyCode::Up, Inputs::Up), - (KeyCode::W, Inputs::Up), - (KeyCode::Down, Inputs::Down), - (KeyCode::S, Inputs::Down), - (KeyCode::Delete, Inputs::Delete), - (KeyCode::Space, Inputs::Spawn), + (Inputs::Right, KeyCode::ArrowRight), + (Inputs::Right, KeyCode::KeyD), + (Inputs::Left, KeyCode::ArrowLeft), + (Inputs::Left, KeyCode::KeyA), + (Inputs::Up, KeyCode::ArrowUp), + (Inputs::Up, KeyCode::KeyW), + (Inputs::Down, KeyCode::ArrowDown), + (Inputs::Down, KeyCode::KeyS), + (Inputs::Delete, KeyCode::Backspace), + (Inputs::Spawn, KeyCode::Space), ]) } } @@ -85,7 +85,7 @@ pub struct PlayerColor(pub(crate) Color); #[derive(Component, Message, Deserialize, Serialize, Clone, Debug, PartialEq)] // Marker component -pub struct Circle; +pub struct CircleMarker; // Example of a component that contains an entity. // This component, when replicated, needs to have the inner entity mapped from the Server world @@ -96,15 +96,9 @@ pub struct Circle; #[message(custom_map)] pub struct PlayerParent(Entity); -impl<'a> MapEntities<'a> for PlayerParent { - fn map_entities(&mut self, entity_mapper: Box) { - info!("mapping parent entity {:?}", self.0); - self.0.map_entities(entity_mapper); - info!("After mapping: {:?}", self.0); - } - - fn entities(&self) -> EntityHashSet { - EntityHashSet::from_iter(vec![self.0]) +impl LightyearMapEntities for PlayerParent { + fn map_entities(&mut self, entity_mapper: &mut M) { + self.0 = entity_mapper.map_entity(self.0); } } @@ -117,7 +111,7 @@ pub enum Components { #[sync(once)] PlayerColor(PlayerColor), #[sync(once)] - Circle(Circle), + CircleMarker(CircleMarker), } // Channels diff --git a/examples/interest_management/src/server.rs b/examples/interest_management/src/server.rs index 46b885040..e3b70e05d 100644 --- a/examples/interest_management/src/server.rs +++ b/examples/interest_management/src/server.rs @@ -35,11 +35,8 @@ impl ServerPluginGroup { Certificate::load("../certificates/cert.pem", "../certificates/key.pem") .await .unwrap(); - let digest = certificate.hashes()[0].fmt_as_dotted_hex(); - println!( - "Generated self-signed certificate with digest: {:?}", - digest - ); + let digest = &certificate.hashes()[0]; + println!("Generated self-signed certificate with digest: {}", digest); TransportConfig::WebTransportServer { server_addr, certificate, @@ -91,7 +88,7 @@ impl Plugin for ExampleServerPlugin { app.init_resource::(); app.add_systems(Startup, init); // the physics/FixedUpdates systems that consume inputs should be run in this set - app.add_systems(FixedUpdate, movement.in_set(FixedUpdateSet::Main)); + app.add_systems(FixedUpdate, movement); app.add_systems( Update, ( @@ -126,7 +123,7 @@ pub(crate) fn init(mut commands: Commands) { for y in -NUM_CIRCLES..NUM_CIRCLES { commands.spawn(( Position(Vec2::new(x as f32 * GRID_SIZE, y as f32 * GRID_SIZE)), - Circle, + CircleMarker, Replicate { // use rooms for replication replication_mode: ReplicationMode::Room, @@ -194,8 +191,8 @@ pub(crate) fn receive_message(mut messages: EventReader>) /// - we will add/remove other entities from the player's room only if they are close pub(crate) fn interest_management( mut room_manager: ResMut, - player_query: Query<(&PlayerId, Ref), Without>, - circle_query: Query<(Entity, &Position), With>, + player_query: Query<(&PlayerId, Ref), Without>, + circle_query: Query<(Entity, &Position), With>, ) { for (client_id, position) in player_query.iter() { if position.is_changed() { diff --git a/examples/interest_management/src/shared.rs b/examples/interest_management/src/shared.rs index f8cee68cc..39f997fe0 100644 --- a/examples/interest_management/src/shared.rs +++ b/examples/interest_management/src/shared.rs @@ -3,7 +3,6 @@ use bevy::prelude::*; use bevy::render::RenderPlugin; use bevy::utils::Duration; -use bevy_inspector_egui::quick::WorldInspectorPlugin; use leafwing_input_manager::action_state::ActionState; use lightyear::prelude::client::Confirmed; use lightyear::prelude::*; @@ -21,11 +20,6 @@ pub fn shared_config() -> SharedConfig { // (otherwise we can send multiple packets for the same tick at different frames) tick_duration: Duration::from_secs_f64(1.0 / 64.0), }, - log: LogConfig { - level: Level::INFO, - filter: "wgpu=error,wgpu_hal=error,naga=warn,bevy_app=info,bevy_render=warn,quinn=warn" - .to_string(), - }, } } @@ -42,16 +36,16 @@ impl Plugin for SharedPlugin { // This system defines how we update the player's positions when we receive an input pub(crate) fn shared_movement_behaviour(mut position: Mut, input: &ActionState) { const MOVE_SPEED: f32 = 10.0; - if input.pressed(Inputs::Up) { + if input.pressed(&Inputs::Up) { position.y += MOVE_SPEED; } - if input.pressed(Inputs::Down) { + if input.pressed(&Inputs::Down) { position.y -= MOVE_SPEED; } - if input.pressed(Inputs::Left) { + if input.pressed(&Inputs::Left) { position.x -= MOVE_SPEED; } - if input.pressed(Inputs::Right) { + if input.pressed(&Inputs::Right) { position.x += MOVE_SPEED; } } @@ -75,7 +69,7 @@ pub(crate) fn draw_boxes( } /// System that draws circles -pub(crate) fn draw_circles(mut gizmos: Gizmos, circles: Query<&Position, With>) { +pub(crate) fn draw_circles(mut gizmos: Gizmos, circles: Query<&Position, With>) { for position in &circles { gizmos.circle_2d(*position.deref(), 1.0, Color::GREEN); } diff --git a/examples/leafwing_inputs/Cargo.toml b/examples/leafwing_inputs/Cargo.toml index d3e84a92a..b4d2b5ee8 100644 --- a/examples/leafwing_inputs/Cargo.toml +++ b/examples/leafwing_inputs/Cargo.toml @@ -11,11 +11,10 @@ metrics = ["lightyear/metrics", "dep:metrics-exporter-prometheus"] mock_time = ["lightyear/mock_time"] [dependencies] -bevy_framepace = "0.14.1" -bevy-fps-counter = "0.3.0" -bevy_screen_diagnostics = "0.4.0" -leafwing-input-manager = "0.11.2" -bevy_xpbd_2d = { version = "0.3.3", features = ["serialize"] } +bevy_framepace = "0.15" +bevy_screen_diagnostics = "0.5.0" +leafwing-input-manager = "0.13" +bevy_xpbd_2d = { version = "0.4", features = ["serialize"] } lightyear = { path = "../../lightyear", features = [ "webtransport", "websocket", @@ -28,7 +27,7 @@ serde = { version = "1.0.188", features = ["derive"] } anyhow = { version = "1.0.75", features = [] } tracing = "0.1" tracing-subscriber = "0.3.17" -bevy = { version = "0.12", features = ["bevy_core_pipeline"] } +bevy = { version = "0.13", features = ["bevy_core_pipeline"] } derive_more = { version = "0.99", features = ["add", "mul"] } rand = "0.8.1" clap = { version = "4.4", features = ["derive"] } diff --git a/examples/leafwing_inputs/README.md b/examples/leafwing_inputs/README.md index 0e23b170c..341f3672f 100644 --- a/examples/leafwing_inputs/README.md +++ b/examples/leafwing_inputs/README.md @@ -4,7 +4,7 @@ This example showcases several things: - how to integrate lightyear with `leafwing_input_manager`. In particular you can simply attach an `ActionState` and an `InputMap` to an `Entity`, and the `ActionState` for that `Entity` will be replicated automatically -- an example of how to integrate physics replication with `bevy_xpbd`. The physics sets have to be run in `FixedUpdateSet::Main` +- an example of how to integrate physics replication with `bevy_xpbd`. The physics sets have to be run in `FixedUpdate` schedule - an example of how to run prediction for entities that are controlled by other players. (this is similar to what RocketLeague does). There is going to be a frequent number of mispredictions because the client is predicting other players without knowing their inputs. The client will just consider that other players are doing the same thing as the last time it received their inputs. diff --git a/examples/leafwing_inputs/src/client.rs b/examples/leafwing_inputs/src/client.rs index 07cf1a30a..70390b5f8 100644 --- a/examples/leafwing_inputs/src/client.rs +++ b/examples/leafwing_inputs/src/client.rs @@ -1,5 +1,5 @@ use crate::protocol::*; -use crate::shared::{color_from_id, shared_config, shared_movement_behaviour}; +use crate::shared::{color_from_id, shared_config, shared_movement_behaviour, FixedSet}; use crate::{shared, Transports, KEY, PROTOCOL_ID}; use bevy::app::PluginGroupBuilder; use bevy::ecs::schedule::{LogLevel, ScheduleBuildSettings}; @@ -7,7 +7,6 @@ use bevy::prelude::*; use bevy::utils::Duration; use bevy_xpbd_2d::parry::shape::ShapeType::Ball; use bevy_xpbd_2d::prelude::*; -use leafwing_input_manager::action_state::ActionDiff; use leafwing_input_manager::prelude::*; use lightyear::inputs::native::input_buffer::InputBuffer; use lightyear::prelude::client::LeafwingInputPlugin; @@ -118,21 +117,16 @@ impl Plugin for ExampleClientPlugin { // To send global inputs, insert the ActionState and the InputMap as Resources app.init_resource::>(); app.insert_resource(InputMap::::new([ - (KeyCode::M, AdminActions::SendMessage), - (KeyCode::R, AdminActions::Reset), + (AdminActions::SendMessage, KeyCode::KeyM), + (AdminActions::Reset, KeyCode::KeyR), ])); app.insert_resource(Global { client_id: self.client_id, }); app.add_systems(Startup, init); - // all actions related-system that can be rolled back should be in FixedUpdateSet::Main - app.add_systems( - FixedUpdate, - player_movement - .in_set(FixedUpdateSet::Main) - .before(PhysicsSet::Prepare), - ); + // all actions related-system that can be rolled back should be in FixedUpdate schedule + app.add_systems(FixedUpdate, player_movement.in_set(FixedSet::Main)); app.add_systems( Update, ( @@ -171,10 +165,10 @@ pub(crate) fn init(mut commands: Commands, mut client: ClientMut, global: Res>) { - if action_state.just_pressed(AdminActions::SendMessage) { + if action_state.just_pressed(&AdminActions::SendMessage) { info!("Send message"); } } diff --git a/examples/leafwing_inputs/src/main.rs b/examples/leafwing_inputs/src/main.rs index 6f30bf13d..5609603af 100644 --- a/examples/leafwing_inputs/src/main.rs +++ b/examples/leafwing_inputs/src/main.rs @@ -19,7 +19,7 @@ use bevy::log::{Level, LogPlugin}; use bevy::prelude::*; use bevy::tasks::IoTaskPool; use bevy::DefaultPlugins; -use bevy_inspector_egui::quick::WorldInspectorPlugin; +// use bevy_inspector_egui::quick::WorldInspectorPlugin; use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; @@ -28,6 +28,7 @@ use crate::client::ClientPluginGroup; use crate::server::ServerPluginGroup; use lightyear::connection::netcode::{ClientId, Key}; use lightyear::prelude::TransportConfig; +use lightyear::shared::log::add_log_layer; // Use a port of 0 to automatically select a port pub const CLIENT_PORT: u16 = 0; @@ -132,13 +133,17 @@ fn setup(app: &mut App, cli: Cli) { predict, } => { if !headless { - app.add_plugins(DefaultPlugins.build().disable::()); + app.add_plugins(DefaultPlugins.build().set(LogPlugin { + level: Level::INFO, + filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), + })); } else { app.add_plugins(MinimalPlugins); } if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } // this is async because we need to load the certificate from io // we need async_compat because wtransport expects a tokio reactor @@ -176,10 +181,11 @@ fn setup_client(app: &mut App, cli: Cli) { app.add_plugins(DefaultPlugins.set(LogPlugin { level: Level::WARN, filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), })); if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } let server_addr = SocketAddr::new(server_addr.into(), server_port); let client_plugin_group = diff --git a/examples/leafwing_inputs/src/protocol.rs b/examples/leafwing_inputs/src/protocol.rs index 6390fa2d0..c38ddd705 100644 --- a/examples/leafwing_inputs/src/protocol.rs +++ b/examples/leafwing_inputs/src/protocol.rs @@ -1,5 +1,4 @@ use bevy::prelude::*; -use bevy::utils::EntityHashSet; use bevy_xpbd_2d::prelude::*; use derive_more::{Add, Mul}; use leafwing_input_manager::prelude::*; @@ -100,7 +99,7 @@ pub(crate) struct PhysicsBundle { impl PhysicsBundle { pub(crate) fn ball() -> Self { Self { - collider: Collider::ball(BALL_SIZE), + collider: Collider::circle(BALL_SIZE), collider_density: ColliderDensity(0.05), rigid_body: RigidBody::Dynamic, } @@ -108,7 +107,7 @@ impl PhysicsBundle { pub(crate) fn player() -> Self { Self { - collider: Collider::cuboid(PLAYER_SIZE, PLAYER_SIZE), + collider: Collider::rectangle(PLAYER_SIZE, PLAYER_SIZE), collider_density: ColliderDensity(0.2), rigid_body: RigidBody::Dynamic, } diff --git a/examples/leafwing_inputs/src/server.rs b/examples/leafwing_inputs/src/server.rs index 872ebb5d4..594cbc98a 100644 --- a/examples/leafwing_inputs/src/server.rs +++ b/examples/leafwing_inputs/src/server.rs @@ -1,5 +1,5 @@ use crate::protocol::*; -use crate::shared::{color_from_id, shared_config, shared_movement_behaviour}; +use crate::shared::{color_from_id, shared_config, shared_movement_behaviour, FixedSet}; use crate::{shared, Transports, KEY, PROTOCOL_ID}; use bevy::app::PluginGroupBuilder; use bevy::prelude::*; @@ -37,11 +37,8 @@ impl ServerPluginGroup { Certificate::load("../certificates/cert.pem", "../certificates/key.pem") .await .unwrap(); - let digest = certificate.hashes()[0].fmt_as_dotted_hex(); - dbg!( - "Generated self-signed certificate with digest: {:?}", - digest - ); + let digest = &certificate.hashes()[0]; + println!("Generated self-signed certificate with digest: {}", digest); TransportConfig::WebTransportServer { server_addr, certificate, @@ -112,12 +109,7 @@ impl Plugin for ExampleServerPlugin { (replicate_players).in_set(MainSet::ClientReplication), ); // the physics/FixedUpdates systems that consume inputs should be run in this set - app.add_systems( - FixedUpdate, - (movement) - .in_set(FixedUpdateSet::Main) - .before(PhysicsSet::Prepare), - ); + app.add_systems(FixedUpdate, movement.in_set(FixedSet::Main)); app.add_systems(Update, handle_disconnections); } } diff --git a/examples/leafwing_inputs/src/shared.rs b/examples/leafwing_inputs/src/shared.rs index 2d50e639a..fcf371222 100644 --- a/examples/leafwing_inputs/src/shared.rs +++ b/examples/leafwing_inputs/src/shared.rs @@ -29,14 +29,17 @@ pub fn shared_config() -> SharedConfig { tick: TickConfig { tick_duration: Duration::from_secs_f64(1.0 / FIXED_TIMESTEP_HZ), }, - log: LogConfig { - level: Level::WARN, - filter: "wgpu=error,wgpu_hal=error,naga=warn,bevy_app=info,bevy_render=warn,quinn=warn" - .to_string(), - }, } } +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub enum FixedSet { + // main fixed update systems (handle inputs) + Main, + // apply physics steps + Physics, +} + pub struct SharedPlugin; impl Plugin for SharedPlugin { @@ -79,19 +82,22 @@ impl Plugin for SharedPlugin { .insert_resource(Gravity(Vec2::ZERO)); app.configure_sets( FixedUpdate, - // make sure that any physics simulation happens after the Main SystemSet - // (where we apply user's actions) ( - PhysicsSet::Prepare, - PhysicsSet::StepSimulation, - PhysicsSet::Sync, - ) - .in_set(FixedUpdateSet::Main), + // make sure that any physics simulation happens after the Main SystemSet + // (where we apply user's actions) + ( + PhysicsSet::Prepare, + PhysicsSet::StepSimulation, + PhysicsSet::Sync, + ) + .in_set(FixedSet::Physics), + (FixedSet::Main, FixedSet::Physics).chain(), + ), ); // add a log at the start of the physics schedule app.add_systems(PhysicsSchedule, log.in_set(PhysicsStepSet::BroadPhase)); - app.add_systems(FixedUpdate, after_physics_log.after(FixedUpdateSet::Main)); + app.add_systems(FixedPostUpdate, after_physics_log); app.add_systems(Last, last_log); // registry types for reflection @@ -101,11 +107,11 @@ impl Plugin for SharedPlugin { fn setup_diagnostic(mut onscreen: ResMut) { onscreen - .add("bytes_in".to_string(), IoDiagnosticsPlugin::BYTES_IN) + .add("KB_in".to_string(), IoDiagnosticsPlugin::BYTES_IN) .aggregate(Aggregate::Average) .format(|v| format!("{v:.0}")); onscreen - .add("bytes_out".to_string(), IoDiagnosticsPlugin::BYTES_OUT) + .add("KB_out".to_string(), IoDiagnosticsPlugin::BYTES_OUT) .aggregate(Aggregate::Average) .format(|v| format!("{v:.0}")); } @@ -147,16 +153,16 @@ pub(crate) fn shared_movement_behaviour( action: &ActionState, ) { const MOVE_SPEED: f32 = 10.0; - if action.pressed(PlayerActions::Up) { + if action.pressed(&PlayerActions::Up) { velocity.y += MOVE_SPEED; } - if action.pressed(PlayerActions::Down) { + if action.pressed(&PlayerActions::Down) { velocity.y -= MOVE_SPEED; } - if action.pressed(PlayerActions::Left) { + if action.pressed(&PlayerActions::Left) { velocity.x -= MOVE_SPEED; } - if action.pressed(PlayerActions::Right) { + if action.pressed(&PlayerActions::Right) { velocity.x += MOVE_SPEED; } *velocity = LinearVelocity(velocity.clamp_length_max(MAX_VELOCITY)); diff --git a/examples/priority/Cargo.toml b/examples/priority/Cargo.toml index c47557acd..06806d07c 100644 --- a/examples/priority/Cargo.toml +++ b/examples/priority/Cargo.toml @@ -18,8 +18,8 @@ metrics = ["lightyear/metrics", "dep:metrics-exporter-prometheus"] mock_time = ["lightyear/mock_time"] [dependencies] -bevy_screen_diagnostics = "0.4.0" -leafwing-input-manager = "0.11.2" +bevy_screen_diagnostics = "0.5.0" +leafwing-input-manager = "0.13" lightyear = { path = "../../lightyear", features = [ "webtransport", "websocket", @@ -31,11 +31,11 @@ serde = { version = "1.0.188", features = ["derive"] } anyhow = { version = "1.0.75", features = [] } tracing = "0.1" tracing-subscriber = "0.3.17" -bevy = { version = "0.12", features = ["bevy_core_pipeline"] } +bevy = { version = "0.13", features = ["bevy_core_pipeline"] } derive_more = { version = "0.99", features = ["add", "mul"] } rand = "0.8.1" clap = { version = "4.4", features = ["derive"] } mock_instant = "0.3" metrics-exporter-prometheus = { version = "0.13.0", optional = true } -bevy-inspector-egui = "0.22.1" +#bevy-inspector-egui = "0.22.1" cfg-if = "1.0.0" diff --git a/examples/priority/src/main.rs b/examples/priority/src/main.rs index 350a03f83..f6d248a2a 100644 --- a/examples/priority/src/main.rs +++ b/examples/priority/src/main.rs @@ -19,16 +19,16 @@ use bevy::log::{Level, LogPlugin}; use bevy::prelude::*; use bevy::tasks::IoTaskPool; use bevy::DefaultPlugins; -use bevy_inspector_egui::quick::WorldInspectorPlugin; +// use bevy_inspector_egui::quick::WorldInspectorPlugin; use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; -use tracing_subscriber::fmt::format::FmtSpan; use crate::client::ClientPluginGroup; #[cfg(not(target_family = "wasm"))] use crate::server::ServerPluginGroup; use lightyear::connection::netcode::{ClientId, Key}; use lightyear::prelude::TransportConfig; +use lightyear::shared::log::add_log_layer; // Use a port of 0 to automatically select a port pub const CLIENT_PORT: u16 = 0; @@ -121,13 +121,17 @@ fn setup(app: &mut App, cli: Cli) { transport, } => { if !headless { - app.add_plugins(DefaultPlugins.build().disable::()); + app.add_plugins(DefaultPlugins.build().set(LogPlugin { + level: Level::INFO, + filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), + })); } else { app.add_plugins(MinimalPlugins); } if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } // this is async because we need to load the certificate from io // we need async_compat because wtransport expects a tokio reactor @@ -165,10 +169,11 @@ fn setup_client(app: &mut App, cli: Cli) { app.add_plugins(DefaultPlugins.set(LogPlugin { level: Level::INFO, filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), })); if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } let server_addr = SocketAddr::new(server_addr.into(), server_port); let client_plugin_group = diff --git a/examples/priority/src/protocol.rs b/examples/priority/src/protocol.rs index e1ab78a8a..b9d3408cd 100644 --- a/examples/priority/src/protocol.rs +++ b/examples/priority/src/protocol.rs @@ -1,5 +1,4 @@ use bevy::prelude::*; -use bevy::utils::EntityHashSet; use derive_more::{Add, Mul}; use leafwing_input_manager::action_state::ActionState; use leafwing_input_manager::input_map::InputMap; @@ -43,17 +42,17 @@ impl PlayerBundle { } pub(crate) fn get_input_map() -> InputMap { InputMap::new([ - (KeyCode::Right, Inputs::Right), - (KeyCode::D, Inputs::Right), - (KeyCode::Left, Inputs::Left), - (KeyCode::A, Inputs::Left), - (KeyCode::Up, Inputs::Up), - (KeyCode::W, Inputs::Up), - (KeyCode::Down, Inputs::Down), - (KeyCode::S, Inputs::Down), - (KeyCode::Delete, Inputs::Delete), - (KeyCode::Space, Inputs::Spawn), - (KeyCode::M, Inputs::Message), + (Inputs::Right, KeyCode::ArrowRight), + (Inputs::Right, KeyCode::KeyD), + (Inputs::Left, KeyCode::ArrowLeft), + (Inputs::Left, KeyCode::KeyA), + (Inputs::Up, KeyCode::ArrowUp), + (Inputs::Up, KeyCode::KeyW), + (Inputs::Down, KeyCode::ArrowDown), + (Inputs::Down, KeyCode::KeyS), + (Inputs::Delete, KeyCode::Backspace), + (Inputs::Spawn, KeyCode::Space), + (Inputs::Message, KeyCode::KeyM), ]) } } diff --git a/examples/priority/src/server.rs b/examples/priority/src/server.rs index 0760cb64e..0649673f2 100644 --- a/examples/priority/src/server.rs +++ b/examples/priority/src/server.rs @@ -29,11 +29,8 @@ impl ServerPluginGroup { Certificate::load("../certificates/cert.pem", "../certificates/key.pem") .await .unwrap(); - let digest = certificate.hashes()[0].fmt_as_dotted_hex(); - dbg!( - "Generated self-signed certificate with digest: {:?}", - digest - ); + let digest = &certificate.hashes()[0]; + println!("Generated self-signed certificate with digest: {}", digest); TransportConfig::WebTransportServer { server_addr, certificate, diff --git a/examples/priority/src/shared.rs b/examples/priority/src/shared.rs index 56a19974e..d9b0b9e96 100644 --- a/examples/priority/src/shared.rs +++ b/examples/priority/src/shared.rs @@ -24,11 +24,6 @@ pub fn shared_config() -> SharedConfig { // (otherwise we can send multiple packets for the same tick at different frames) tick_duration: Duration::from_secs_f64(1.0 / 64.0), }, - log: LogConfig { - level: Level::INFO, - filter: "wgpu=error,wgpu_hal=error,naga=warn,bevy_app=info,bevy_render=warn,quinn=warn" - .to_string(), - }, } } @@ -44,7 +39,7 @@ impl Plugin for SharedPlugin { } // movement - app.add_systems(FixedUpdate, player_movement.in_set(FixedUpdateSet::Main)); + app.add_systems(FixedUpdate, player_movement); } } @@ -64,16 +59,16 @@ pub(crate) fn player_movement( mut position_query: Query<(&mut Position, &ActionState), Without>, ) { for (mut position, input) in position_query.iter_mut() { - if input.pressed(Inputs::Up) { + if input.pressed(&Inputs::Up) { position.y += MOVE_SPEED; } - if input.pressed(Inputs::Down) { + if input.pressed(&Inputs::Down) { position.y -= MOVE_SPEED; } - if input.pressed(Inputs::Left) { + if input.pressed(&Inputs::Left) { position.x -= MOVE_SPEED; } - if input.pressed(Inputs::Right) { + if input.pressed(&Inputs::Right) { position.x += MOVE_SPEED; } } diff --git a/examples/replication_groups/Cargo.toml b/examples/replication_groups/Cargo.toml index 2d7f7b76f..b78161409 100644 --- a/examples/replication_groups/Cargo.toml +++ b/examples/replication_groups/Cargo.toml @@ -28,11 +28,11 @@ serde = { version = "1.0.188", features = ["derive"] } anyhow = { version = "1.0.75", features = [] } tracing = "0.1" tracing-subscriber = "0.3.17" -bevy = { version = "0.12", features = ["bevy_core_pipeline"] } +bevy = { version = "0.13", features = ["bevy_core_pipeline"] } derive_more = { version = "0.99", features = ["add", "mul"] } rand = "0.8.1" clap = { version = "4.4", features = ["derive"] } mock_instant = "0.3" metrics-exporter-prometheus = { version = "0.13.0", optional = true } -bevy-inspector-egui = "0.22.1" +#bevy-inspector-egui = "0.22.1" cfg-if = "1.0.0" diff --git a/examples/replication_groups/src/client.rs b/examples/replication_groups/src/client.rs index a8b241498..810399fc8 100644 --- a/examples/replication_groups/src/client.rs +++ b/examples/replication_groups/src/client.rs @@ -122,15 +122,10 @@ impl Plugin for ExampleClientPlugin { interpolate.in_set(InterpolationSet::Interpolate), ); app.add_systems( - FixedUpdate, + FixedPreUpdate, buffer_input.in_set(InputSystemSet::BufferInputs), ); - app.add_systems( - FixedUpdate, - (movement, shared_tail_behaviour) - .chain() - .in_set(FixedUpdateSet::Main), - ); + app.add_systems(FixedUpdate, (movement, shared_tail_behaviour).chain()); app.add_systems(Update, (handle_predicted_spawn, handle_interpolated_spawn)); // add visual interpolation for the predicted snake (which gets updated in the FixedUpdate schedule) @@ -161,20 +156,20 @@ pub(crate) fn init( } // System that reads from peripherals and adds inputs to the buffer -pub(crate) fn buffer_input(mut client: ClientMut, keypress: Res>) { - if keypress.pressed(KeyCode::W) || keypress.pressed(KeyCode::Up) { +pub(crate) fn buffer_input(mut client: ClientMut, keypress: Res>) { + if keypress.pressed(KeyCode::KeyW) || keypress.pressed(KeyCode::ArrowUp) { return client.add_input(Inputs::Direction(Direction::Up)); } - if keypress.pressed(KeyCode::S) || keypress.pressed(KeyCode::Down) { + if keypress.pressed(KeyCode::KeyS) || keypress.pressed(KeyCode::ArrowDown) { return client.add_input(Inputs::Direction(Direction::Down)); } - if keypress.pressed(KeyCode::A) || keypress.pressed(KeyCode::Left) { + if keypress.pressed(KeyCode::KeyA) || keypress.pressed(KeyCode::ArrowLeft) { return client.add_input(Inputs::Direction(Direction::Left)); } - if keypress.pressed(KeyCode::D) || keypress.pressed(KeyCode::Right) { + if keypress.pressed(KeyCode::KeyD) || keypress.pressed(KeyCode::ArrowRight) { return client.add_input(Inputs::Direction(Direction::Right)); } - if keypress.pressed(KeyCode::Delete) { + if keypress.pressed(KeyCode::Backspace) { // currently, inputs is an enum and we can only add one input per tick return client.add_input(Inputs::Delete); } diff --git a/examples/replication_groups/src/main.rs b/examples/replication_groups/src/main.rs index 796798b21..1a12e08d4 100644 --- a/examples/replication_groups/src/main.rs +++ b/examples/replication_groups/src/main.rs @@ -19,7 +19,7 @@ use bevy::log::{Level, LogPlugin}; use bevy::prelude::*; use bevy::tasks::IoTaskPool; use bevy::DefaultPlugins; -use bevy_inspector_egui::quick::WorldInspectorPlugin; +// use bevy_inspector_egui::quick::WorldInspectorPlugin; use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; @@ -28,6 +28,7 @@ use crate::client::ClientPluginGroup; use crate::server::ServerPluginGroup; use lightyear::connection::netcode::{ClientId, Key}; use lightyear::prelude::TransportConfig; +use lightyear::shared::log::add_log_layer; // Use a port of 0 to automatically select a port pub const CLIENT_PORT: u16 = 0; @@ -120,13 +121,17 @@ fn setup(app: &mut App, cli: Cli) { transport, } => { if !headless { - app.add_plugins(DefaultPlugins.build().disable::()); + app.add_plugins(DefaultPlugins.build().set(LogPlugin { + level: Level::INFO, + filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), + })); } else { app.add_plugins(MinimalPlugins); } if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } // this is async because we need to load the certificate from io // we need async_compat because wtransport expects a tokio reactor @@ -164,10 +169,11 @@ fn setup_client(app: &mut App, cli: Cli) { app.add_plugins(DefaultPlugins.set(LogPlugin { level: Level::INFO, filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: None, })); if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } let server_addr = SocketAddr::new(server_addr.into(), server_port); let client_plugin_group = diff --git a/examples/replication_groups/src/protocol.rs b/examples/replication_groups/src/protocol.rs index c437e1783..4e7c8a0e5 100644 --- a/examples/replication_groups/src/protocol.rs +++ b/examples/replication_groups/src/protocol.rs @@ -1,5 +1,6 @@ -use bevy::prelude::{default, Bundle, Color, Component, Deref, DerefMut, Entity, Reflect, Vec2}; -use bevy::utils::EntityHashSet; +use bevy::prelude::{ + default, Bundle, Color, Component, Deref, DerefMut, Entity, EntityMapper, Reflect, Vec2, +}; use derive_more::{Add, Mul}; use lightyear::prelude::client::LerpFn; use lightyear::prelude::*; @@ -194,15 +195,9 @@ impl TailPoints { #[message(custom_map)] pub struct PlayerParent(pub(crate) Entity); -impl<'a> MapEntities<'a> for PlayerParent { - fn map_entities(&mut self, entity_mapper: Box) { - debug!("mapping parent entity {:?}", self.0); - self.0.map_entities(entity_mapper); - debug!("After mapping: {:?}", self.0); - } - - fn entities(&self) -> EntityHashSet { - EntityHashSet::from_iter(vec![self.0]) +impl LightyearMapEntities for PlayerParent { + fn map_entities(&mut self, entity_mapper: &mut M) { + self.0 = entity_mapper.map_entity(self.0); } } diff --git a/examples/replication_groups/src/server.rs b/examples/replication_groups/src/server.rs index 8cab7aaab..d7a9bfbcd 100644 --- a/examples/replication_groups/src/server.rs +++ b/examples/replication_groups/src/server.rs @@ -26,11 +26,8 @@ impl ServerPluginGroup { Certificate::load("../certificates/cert.pem", "../certificates/key.pem") .await .unwrap(); - let digest = certificate.hashes()[0].fmt_as_dotted_hex(); - dbg!( - "Generated self-signed certificate with digest: {:?}", - digest - ); + let digest = &certificate.hashes()[0]; + println!("Generated self-signed certificate with digest: {}", digest); TransportConfig::WebTransportServer { server_addr, certificate, @@ -81,12 +78,7 @@ impl Plugin for ExampleServerPlugin { app.init_resource::(); app.add_systems(Startup, init); // the physics/FixedUpdates systems that consume inputs should be run in this set - app.add_systems( - FixedUpdate, - (movement, shared_tail_behaviour) - .chain() - .in_set(FixedUpdateSet::Main), - ); + app.add_systems(FixedUpdate, (movement, shared_tail_behaviour).chain()); app.add_systems(Update, handle_connections); // app.add_systems(Update, debug_inputs); } diff --git a/examples/replication_groups/src/shared.rs b/examples/replication_groups/src/shared.rs index 96e249056..21231c675 100644 --- a/examples/replication_groups/src/shared.rs +++ b/examples/replication_groups/src/shared.rs @@ -3,7 +3,7 @@ use crate::protocol::*; use bevy::prelude::*; use bevy::render::RenderPlugin; use bevy::utils::Duration; -use bevy_inspector_egui::quick::WorldInspectorPlugin; +// use bevy_inspector_egui::quick::WorldInspectorPlugin; use lightyear::prelude::client::{Confirmed, Interpolated}; use lightyear::prelude::*; use tracing::Level; @@ -17,11 +17,6 @@ pub fn shared_config() -> SharedConfig { tick: TickConfig { tick_duration: Duration::from_secs_f64(1.0 / 64.0), }, - log: LogConfig { - level: Level::INFO, - filter: "wgpu=error,wgpu_hal=error,naga=warn,bevy_app=info,bevy_render=warn,quinn=warn" - .to_string(), - }, } } diff --git a/examples/simple_box/Cargo.toml b/examples/simple_box/Cargo.toml index 057752b7b..852077547 100644 --- a/examples/simple_box/Cargo.toml +++ b/examples/simple_box/Cargo.toml @@ -27,11 +27,11 @@ serde = { version = "1.0.188", features = ["derive"] } anyhow = { version = "1.0.75", features = [] } tracing = "0.1" tracing-subscriber = "0.3.17" -bevy = { version = "0.12", features = ["bevy_core_pipeline"] } +bevy = { version = "0.13", features = ["bevy_core_pipeline"] } derive_more = { version = "0.99", features = ["add", "mul"] } rand = "0.8.1" clap = { version = "4.4", features = ["derive"] } mock_instant = "0.3" metrics-exporter-prometheus = { version = "0.13.0", optional = true } -bevy-inspector-egui = "0.22.1" +#bevy-inspector-egui = "0.22.1" cfg-if = "1.0.0" diff --git a/examples/simple_box/src/client.rs b/examples/simple_box/src/client.rs index 584823062..492cb4922 100644 --- a/examples/simple_box/src/client.rs +++ b/examples/simple_box/src/client.rs @@ -97,11 +97,12 @@ impl Plugin for ExampleClientPlugin { client_id: self.client_id, }); app.add_systems(Startup, init); + // Inputs have to be buffered in the FixedPreUpdate schedule app.add_systems( - FixedUpdate, + FixedPreUpdate, buffer_input.in_set(InputSystemSet::BufferInputs), ); - app.add_systems(FixedUpdate, player_movement.in_set(FixedUpdateSet::Main)); + app.add_systems(FixedUpdate, player_movement); app.add_systems( Update, ( @@ -164,29 +165,29 @@ pub(crate) fn init(mut commands: Commands, mut client: ClientMut, global: Res>) { +pub(crate) fn buffer_input(mut client: ClientMut, keypress: Res>) { let mut direction = Direction { up: false, down: false, left: false, right: false, }; - if keypress.pressed(KeyCode::W) || keypress.pressed(KeyCode::Up) { + if keypress.pressed(KeyCode::KeyW) || keypress.pressed(KeyCode::ArrowUp) { direction.up = true; } - if keypress.pressed(KeyCode::S) || keypress.pressed(KeyCode::Down) { + if keypress.pressed(KeyCode::KeyS) || keypress.pressed(KeyCode::ArrowDown) { direction.down = true; } - if keypress.pressed(KeyCode::A) || keypress.pressed(KeyCode::Left) { + if keypress.pressed(KeyCode::KeyA) || keypress.pressed(KeyCode::ArrowLeft) { direction.left = true; } - if keypress.pressed(KeyCode::D) || keypress.pressed(KeyCode::Right) { + if keypress.pressed(KeyCode::KeyD) || keypress.pressed(KeyCode::ArrowRight) { direction.right = true; } if !direction.is_none() { return client.add_input(Inputs::Direction(direction)); } - if keypress.pressed(KeyCode::Delete) { + if keypress.pressed(KeyCode::Backspace) { // currently, inputs is an enum and we can only add one input per tick return client.add_input(Inputs::Delete); } diff --git a/examples/simple_box/src/main.rs b/examples/simple_box/src/main.rs index 3a3b712e1..005619e85 100644 --- a/examples/simple_box/src/main.rs +++ b/examples/simple_box/src/main.rs @@ -20,7 +20,7 @@ use bevy::log::{Level, LogPlugin}; use bevy::prelude::*; use bevy::tasks::IoTaskPool; use bevy::DefaultPlugins; -use bevy_inspector_egui::quick::WorldInspectorPlugin; +// use bevy_inspector_egui::quick::WorldInspectorPlugin; use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; @@ -29,6 +29,7 @@ use crate::client::ClientPluginGroup; use crate::server::ServerPluginGroup; use lightyear::connection::netcode::{ClientId, Key}; use lightyear::prelude::TransportConfig; +use lightyear::shared::log::add_log_layer; // Use a port of 0 to automatically select a port pub const CLIENT_PORT: u16 = 0; @@ -121,13 +122,17 @@ fn setup(app: &mut App, cli: Cli) { transport, } => { if !headless { - app.add_plugins(DefaultPlugins.build().disable::()); + app.add_plugins(DefaultPlugins.build().set(LogPlugin { + level: Level::INFO, + filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: Some(add_log_layer), + })); } else { app.add_plugins(MinimalPlugins); } if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } // this is async because we need to load the certificate from io // we need async_compat because wtransport expects a tokio reactor @@ -165,10 +170,12 @@ fn setup_client(app: &mut App, cli: Cli) { app.add_plugins(DefaultPlugins.set(LogPlugin { level: Level::INFO, filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), + update_subscriber: None, + // update_subscriber: Some(add_log_layer), })); if inspector { - app.add_plugins(WorldInspectorPlugin::new()); + // app.add_plugins(WorldInspectorPlugin::new()); } let server_addr = SocketAddr::new(server_addr.into(), server_port); let client_plugin_group = diff --git a/examples/simple_box/src/protocol.rs b/examples/simple_box/src/protocol.rs index be8f562b8..51a8b6aa0 100644 --- a/examples/simple_box/src/protocol.rs +++ b/examples/simple_box/src/protocol.rs @@ -1,5 +1,7 @@ -use bevy::prelude::{default, Bundle, Color, Component, Deref, DerefMut, Entity, Vec2}; -use bevy::utils::EntityHashSet; +use bevy::ecs::entity::MapEntities; +use bevy::prelude::{ + default, Bundle, Color, Component, Deref, DerefMut, Entity, EntityMapper, Vec2, +}; use derive_more::{Add, Mul}; use lightyear::prelude::*; use serde::{Deserialize, Serialize}; @@ -60,13 +62,9 @@ pub struct PlayerColor(pub(crate) Color); #[message(custom_map)] pub struct PlayerParent(Entity); -impl<'a> MapEntities<'a> for PlayerParent { - fn map_entities(&mut self, entity_mapper: Box) { - self.0.map_entities(entity_mapper); - } - - fn entities(&self) -> EntityHashSet { - EntityHashSet::from_iter(vec![self.0]) +impl LightyearMapEntities for PlayerParent { + fn map_entities(&mut self, entity_mapper: &mut M) { + self.0 = entity_mapper.map_entity(self.0); } } diff --git a/examples/simple_box/src/server.rs b/examples/simple_box/src/server.rs index 3e613b135..2de4258ec 100644 --- a/examples/simple_box/src/server.rs +++ b/examples/simple_box/src/server.rs @@ -27,11 +27,8 @@ impl ServerPluginGroup { Certificate::load("../certificates/cert.pem", "../certificates/key.pem") .await .unwrap(); - let digest = certificate.hashes()[0].fmt_as_dotted_hex(); - println!( - "Generated self-signed certificate with digest: {:?}", - digest - ); + let digest = &certificate.hashes()[0]; + println!("Generated self-signed certificate with digest: {}", digest); TransportConfig::WebTransportServer { server_addr, certificate, @@ -90,7 +87,7 @@ impl Plugin for ExampleServerPlugin { }); app.add_systems(Startup, init); // the physics/FixedUpdates systems that consume inputs should be run in this set - app.add_systems(FixedUpdate, movement.in_set(FixedUpdateSet::Main)); + app.add_systems(FixedUpdate, movement); if !self.headless { app.add_systems(Update, send_message); } @@ -180,9 +177,9 @@ pub(crate) fn movement( /// and cannot do input handling) pub(crate) fn send_message( mut server: ResMut, - input: Res>, + input: Res>, ) { - if input.pressed(KeyCode::M) { + if input.pressed(KeyCode::KeyM) { // TODO: add way to send message to all let message = Message1(5); info!("Send message: {:?}", message); diff --git a/examples/simple_box/src/shared.rs b/examples/simple_box/src/shared.rs index f459e8898..6f2e8af11 100644 --- a/examples/simple_box/src/shared.rs +++ b/examples/simple_box/src/shared.rs @@ -3,10 +3,9 @@ use bevy::diagnostic::LogDiagnosticsPlugin; use bevy::prelude::*; use bevy::render::RenderPlugin; use bevy::utils::Duration; -use bevy_inspector_egui::quick::WorldInspectorPlugin; +// use bevy_inspector_egui::quick::WorldInspectorPlugin; use lightyear::prelude::*; use lightyear::transport::io::IoDiagnosticsPlugin; -use tracing::Level; pub fn shared_config() -> SharedConfig { SharedConfig { @@ -17,11 +16,6 @@ pub fn shared_config() -> SharedConfig { tick: TickConfig { tick_duration: Duration::from_secs_f64(1.0 / 64.0), }, - log: LogConfig { - level: Level::INFO, - filter: "wgpu=error,wgpu_hal=error,naga=warn,bevy_app=info,bevy_render=warn,quinn=warn" - .to_string(), - }, } } diff --git a/examples/stepper/Cargo.toml b/examples/stepper/Cargo.toml index b8c93545b..c343a2d4d 100644 --- a/examples/stepper/Cargo.toml +++ b/examples/stepper/Cargo.toml @@ -33,7 +33,7 @@ metrics = ["lightyear/metrics", "dep:metrics-exporter-prometheus"] mock_time = ["lightyear/mock_time"] [dependencies] -leafwing-input-manager = "0.11.2" +leafwing-input-manager = "0.13" bevy_xpbd_2d = "0.3.2" lightyear = { path = "../../lightyear", features = ["webtransport", "render"] } @@ -41,7 +41,7 @@ serde = { version = "1.0.188", features = ["derive"] } anyhow = { version = "1.0.75", features = [] } tracing = "0.1" tracing-subscriber = "0.3.17" -bevy = { version = "0.12", features = ["bevy_core_pipeline"] } +bevy = { version = "0.13", features = ["bevy_core_pipeline"] } derive_more = { version = "0.99", features = ["add", "mul"] } rand = "0.8.1" clap = { version = "4.4", features = ["derive"] } diff --git a/examples/stepper/src/bin/input_buffer.rs b/examples/stepper/src/bin/input_buffer.rs index f92d42d3a..d2757f0b0 100644 --- a/examples/stepper/src/bin/input_buffer.rs +++ b/examples/stepper/src/bin/input_buffer.rs @@ -103,10 +103,10 @@ fn main() -> anyhow::Result<()> { ); stepper .client_app - .add_systems(FixedUpdate, client_read_input.in_set(FixedUpdateSet::Main)); + .add_systems(FixedUpdate, client_read_input); stepper .server_app - .add_systems(FixedUpdate, server_read_input.in_set(FixedUpdateSet::Main)); + .add_systems(FixedUpdate, server_read_input); // tick a bit, and check the input buffer received on server for i in 0..400 { diff --git a/examples/stepper/src/bin/prediction.rs b/examples/stepper/src/bin/prediction.rs index cc5c2c72a..7ad4f7758 100644 --- a/examples/stepper/src/bin/prediction.rs +++ b/examples/stepper/src/bin/prediction.rs @@ -134,10 +134,10 @@ fn main() -> anyhow::Result<()> { ); stepper .client_app - .add_systems(FixedUpdate, client_read_input.in_set(FixedUpdateSet::Main)); + .add_systems(FixedUpdate, client_read_input); stepper .server_app - .add_systems(FixedUpdate, server_read_input.in_set(FixedUpdateSet::Main)); + .add_systems(FixedUpdate, server_read_input); // tick a bit, and check the input buffer received on server for i in 0..200 { diff --git a/examples/stepper/src/client.rs b/examples/stepper/src/client.rs index 8c40636a9..a699a2e8f 100644 --- a/examples/stepper/src/client.rs +++ b/examples/stepper/src/client.rs @@ -3,6 +3,7 @@ use std::net::SocketAddr; use std::str::FromStr; use bevy::app::App; +use bevy::prelude::default; use lightyear::prelude::client::*; use lightyear::prelude::*; @@ -12,7 +13,6 @@ use crate::protocol::*; pub fn bevy_setup(app: &mut App, auth: Authentication) { // create udp-socket based io let addr = SocketAddr::from_str("127.0.0.1:0").unwrap(); - let io = Io::from_config(IoConfig::from_transport(TransportConfig::UdpSocket(addr))); let config = ClientConfig { shared: SharedConfig { enable_replication: false, @@ -20,13 +20,18 @@ pub fn bevy_setup(app: &mut App, auth: Authentication) { ..Default::default() }, input: InputConfig::default(), - netcode: Default::default(), + net: NetConfig::Netcode { + config: NetcodeConfig::default(), + auth, + io: IoConfig::from_transport(TransportConfig::UdpSocket(addr)), + }, ping: PingConfig::default(), sync: SyncConfig::default(), prediction: PredictionConfig::default(), interpolation: InterpolationConfig::default(), + ..default() }; - let plugin_config = PluginConfig::new(config, io, protocol(), auth); + let plugin_config = PluginConfig::new(config, protocol()); let plugin = ClientPlugin::new(plugin_config); app.add_plugins(plugin); } diff --git a/examples/stepper/src/server.rs b/examples/stepper/src/server.rs index 70f664e6d..ed590c293 100644 --- a/examples/stepper/src/server.rs +++ b/examples/stepper/src/server.rs @@ -4,6 +4,7 @@ use std::net::SocketAddr; use std::str::FromStr; use bevy::app::App; +use bevy::prelude::default; use crate::protocol::*; use lightyear::prelude::server::*; @@ -14,17 +15,19 @@ pub fn bevy_setup(app: &mut App, addr: SocketAddr, protocol_id: u64, private_key let netcode_config = NetcodeConfig::default() .with_protocol_id(protocol_id) .with_key(private_key); - let io = Io::from_config(IoConfig::from_transport(TransportConfig::UdpSocket(addr))); let config = ServerConfig { shared: SharedConfig { enable_replication: false, tick: TickConfig::new(Duration::from_millis(10)), ..Default::default() }, - netcode: netcode_config, - ping: PingConfig::default(), + net: NetConfig::Netcode { + config: netcode_config, + io: IoConfig::from_transport(TransportConfig::UdpSocket(addr)), + }, + ..default() }; - let plugin_config = PluginConfig::new(config, io, protocol()); + let plugin_config = PluginConfig::new(config, protocol()); let plugin = ServerPlugin::new(plugin_config); app.add_plugins(plugin); } diff --git a/examples/stepper/src/stepper.rs b/examples/stepper/src/stepper.rs index a02a40b0e..758f10bc2 100644 --- a/examples/stepper/src/stepper.rs +++ b/examples/stepper/src/stepper.rs @@ -47,10 +47,10 @@ impl BevyStepper { conditioner: LinkConditionerConfig, frame_duration: Duration, ) -> Self { - tracing_subscriber::FmtSubscriber::builder() - .with_span_events(FmtSpan::ENTER) - .with_max_level(tracing::Level::DEBUG) - .init(); + // tracing_subscriber::FmtSubscriber::builder() + // .with_span_events(FmtSpan::ENTER) + // .with_max_level(tracing::Level::DEBUG) + // .init(); // Shared config let server_addr = SocketAddr::from_str("127.0.0.1:5000").unwrap(); diff --git a/lightyear/Cargo.toml b/lightyear/Cargo.toml index 09fa128c5..7367a6897 100644 --- a/lightyear/Cargo.toml +++ b/lightyear/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lightyear" -version = "0.9.1" +version = "0.10.0" authors = ["Charles Bournhonesque "] edition = "2021" rust-version = "1.65" @@ -49,6 +49,7 @@ cfg-if = "1.0" derive_more = "0.99.17" enum_delegate = "0.2" enum_dispatch = "0.3" +hashbrown = "0.14" # used to have the same instant in wasm and native. (maybe can be replaced by bevy_utils in 0.13) instant = "0.1.12" governor = "0.6.0" @@ -61,10 +62,10 @@ thiserror = "1.0.50" seahash = "4.1.0" # input -leafwing-input-manager = { version = "0.11.2", optional = true } +leafwing-input-manager = { version = "0.13", optional = true } # physics -bevy_xpbd_2d = { version = "0.3.3", optional = true } +bevy_xpbd_2d = { version = "0.4", optional = true } # serialization bitcode = { version = "0.4.1", package = "bitcode_lightyear_patch", path = "../vendor/bitcode", features = [ @@ -79,11 +80,11 @@ chacha20poly1305 = { version = "0.10", features = ["std"] } byteorder = "1.5.0" # derive -lightyear_macros = { version = "0.9.1", path = "../macros" } +lightyear_macros = { version = "0.10.0", path = "../macros" } # tracing tracing = "0.1.40" -tracing-log = "0.1.3" +tracing-log = "0.2.0" tracing-subscriber = { version = "0.3.17", features = [ "registry", "env-filter", @@ -101,7 +102,7 @@ metrics-exporter-prometheus = { version = "0.13.0", optional = true, default-fea ] } # bevy -bevy = { version = "0.12", default-features = false, features = [ +bevy = { version = "0.13", default-features = false, features = [ "multi-threaded", ] } diff --git a/lightyear/src/channel/builder.rs b/lightyear/src/channel/builder.rs index 97e1423fc..aa9a7cfcd 100644 --- a/lightyear/src/channel/builder.rs +++ b/lightyear/src/channel/builder.rs @@ -40,7 +40,7 @@ pub trait Channel: 'static + Named { } #[doc(hidden)] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ChannelBuilder { // TODO: this has been made public just for testing integration tests pub settings: ChannelSettings, @@ -96,7 +96,7 @@ impl ChannelContainer { } /// [`ChannelSettings`] are used to specify how the [`Channel`] behaves (reliability, ordering, direction) -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ChannelSettings { // TODO: split into Ordering and Reliability? Or not because we might to add new modes like TickBuffered pub mode: ChannelMode, diff --git a/lightyear/src/client/components.rs b/lightyear/src/client/components.rs index aecc84132..257859ecb 100644 --- a/lightyear/src/client/components.rs +++ b/lightyear/src/client/components.rs @@ -5,7 +5,7 @@ use std::fmt::Debug; use bevy::prelude::{Component, Entity}; -use crate::prelude::{MapEntities, Named, Tick}; +use crate::prelude::{Message, Named, Tick}; /// Marks an entity that directly applies the replication updates from the remote /// @@ -25,8 +25,8 @@ pub struct Confirmed { } // TODO: add TypeNamed as well -pub trait SyncComponent: Component + Clone + PartialEq + Named + for<'a> MapEntities<'a> {} -impl SyncComponent for T where T: Component + Clone + PartialEq + Named + for<'a> MapEntities<'a> {} +pub trait SyncComponent: Component + Clone + PartialEq + Message {} +impl SyncComponent for T where T: Component + Clone + PartialEq + Message {} // NOTE: we use these traits that the Protocol will implement so that we don't implement // external traits on external types and break the orphan rule diff --git a/lightyear/src/client/connection.rs b/lightyear/src/client/connection.rs index aa1839c71..4152792a1 100644 --- a/lightyear/src/client/connection.rs +++ b/lightyear/src/client/connection.rs @@ -15,7 +15,7 @@ use crate::client::sync::SyncConfig; use crate::inputs::native::input_buffer::InputBuffer; use crate::packet::message_manager::MessageManager; use crate::packet::packet_manager::Payload; -use crate::prelude::{Channel, ChannelKind, MapEntities, Message, NetworkTarget}; +use crate::prelude::{Channel, ChannelKind, LightyearMapEntities, Message, NetworkTarget}; use crate::protocol::channel::ChannelRegistry; use crate::protocol::Protocol; use crate::serialize::reader::ReadBuffer; @@ -343,9 +343,7 @@ impl ConnectionManager

{ match message { ServerMessage::Message(mut message) => { // map any entities inside the message - message.map_entities(Box::new( - &self.replication_receiver.remote_entity_map, - )); + message.map_entities(&mut self.replication_receiver.remote_entity_map); // buffer the message self.events.push_message(channel_kind, message); } diff --git a/lightyear/src/client/input.rs b/lightyear/src/client/input.rs index e1a29d8d2..d61b457e9 100644 --- a/lightyear/src/client/input.rs +++ b/lightyear/src/client/input.rs @@ -24,8 +24,8 @@ //! That module is more up-to-date and has more features. //! This module is kept for simplicity but might get removed in the future. use bevy::prelude::{ - not, App, EventReader, EventWriter, FixedUpdate, IntoSystemConfigs, IntoSystemSetConfigs, - Plugin, PostUpdate, Res, ResMut, SystemSet, + not, App, EventReader, EventWriter, FixedPostUpdate, FixedPreUpdate, FixedUpdate, + IntoSystemConfigs, IntoSystemSetConfigs, Plugin, PostUpdate, Res, ResMut, SystemSet, }; use tracing::{debug, error, trace}; @@ -95,17 +95,15 @@ impl Plugin for InputPlugin

{ app.add_event::>(); // SETS app.configure_sets( - FixedUpdate, + FixedPreUpdate, ( - FixedUpdateSet::TickUpdate, // no need to keep buffering inputs during rollback InputSystemSet::BufferInputs.run_if(not(is_in_rollback)), InputSystemSet::WriteInputEvent, - FixedUpdateSet::Main, - InputSystemSet::ClearInputEvent, ) .chain(), ); + app.configure_sets(FixedPostUpdate, InputSystemSet::ClearInputEvent); app.configure_sets( PostUpdate, ( @@ -124,11 +122,11 @@ impl Plugin for InputPlugin

{ // SYSTEMS app.add_systems( - FixedUpdate, + FixedPreUpdate, write_input_event::

.in_set(InputSystemSet::WriteInputEvent), ); app.add_systems( - FixedUpdate, + FixedPostUpdate, clear_input_events::

.in_set(InputSystemSet::ClearInputEvent), ); // in case the framerate is faster than fixed-update interval, we also write/clear the events at frame limits diff --git a/lightyear/src/client/input_leafwing.rs b/lightyear/src/client/input_leafwing.rs index 4646d7ed9..0c37d3630 100644 --- a/lightyear/src/client/input_leafwing.rs +++ b/lightyear/src/client/input_leafwing.rs @@ -22,7 +22,7 @@ //! ## Usage //! //! The networking of inputs is completely handled for you. You just need to add the `LeafwingInputPlugin` to your app. -//! Make sure that all your systems that depend on user inputs are added to the [`FixedUpdateSet::Main`] [`SystemSet`]. +//! Make sure that all your systems that depend on user inputs are added to the [`FixedUpdate`] [`Schedule`]. //! //! Currently, global inputs (that are stored in a [`Resource`] instead of being attached to a specific [`Entity`] are not supported) //! @@ -129,16 +129,16 @@ impl LeafwingInputConfig { /// Adds a plugin to handle inputs using the LeafwingInputManager pub struct LeafwingInputPlugin { config: LeafwingInputConfig, - _protocol_marker: std::marker::PhantomData

, - _action_marker: std::marker::PhantomData, + _protocol_marker: PhantomData

, + _action_marker: PhantomData, } impl LeafwingInputPlugin { pub fn new(config: LeafwingInputConfig) -> Self { Self { config, - _protocol_marker: std::marker::PhantomData, - _action_marker: std::marker::PhantomData, + _protocol_marker: PhantomData, + _action_marker: PhantomData, } } } @@ -147,8 +147,8 @@ impl Default for LeafwingInputPlugin { fn default() -> Self { Self { config: LeafwingInputConfig::default(), - _protocol_marker: std::marker::PhantomData, - _action_marker: std::marker::PhantomData, + _protocol_marker: PhantomData, + _action_marker: PhantomData, } } } @@ -187,15 +187,8 @@ where app.init_resource::>>(); // SETS // app.configure_sets(PreUpdate, InputManagerSystem::Tick.run_if(should_tick::)); - app.configure_sets( - FixedUpdate, - ( - FixedUpdateSet::TickUpdate, - InputSystemSet::BufferInputs, - FixedUpdateSet::Main, - ) - .chain(), - ); + app.configure_sets(FixedFirst, FixedUpdateSet::TickUpdate); + app.configure_sets(FixedPreUpdate, InputSystemSet::BufferInputs); app.configure_sets( PostUpdate, // we send inputs only every send_interval @@ -231,35 +224,34 @@ where // - handle `JustPressed` actions in the Update schedule, where they can only happen once // - `consume` the action when you read it, so that it can only happen once app.add_systems( - FixedUpdate, + FixedPreUpdate, ( ( - ( - (write_action_diffs::, buffer_action_state::), - // get the action-state corresponding to the current tick (which we need to get from the buffer - // because it was added to the buffer input_delay ticks ago) - get_non_rollback_action_state::.run_if(is_input_delay), - ) - .chain() - .run_if(run_if_enabled::.and_then(not(is_in_rollback))), - get_rollback_action_state:: - .run_if(run_if_enabled::.and_then(is_in_rollback)), + (write_action_diffs::, buffer_action_state::), + // get the action-state corresponding to the current tick (which we need to get from the buffer + // because it was added to the buffer input_delay ticks ago) + get_non_rollback_action_state::.run_if(is_input_delay), ) - .in_set(InputSystemSet::BufferInputs), - // TODO: think about how we can avoid this, maybe have a separate DelayedActionState component? - // we want: - // - to write diffs for the delayed tick (in the next FixedUpdate run), so re-fetch the delayed action-state - // this is required in case the FixedUpdate schedule runs multiple times in a frame, - // - next frame's input-map (in PreUpdate) to act on the delayed tick, so re-fetch the delayed action-state - get_delayed_action_state:: - .run_if( - is_input_delay - .and_then(not(is_in_rollback)) - .and_then(run_if_enabled::), - ) - .after(FixedUpdateSet::Main), + .chain() + .run_if(run_if_enabled::.and_then(not(is_in_rollback))), + get_rollback_action_state::.run_if(run_if_enabled::.and_then(is_in_rollback)), + ) + .in_set(InputSystemSet::BufferInputs), + ); + app.add_systems( + FixedPostUpdate, + // TODO: think about how we can avoid this, maybe have a separate DelayedActionState component? + // we want: + // - to write diffs for the delayed tick (in the next FixedUpdate run), so re-fetch the delayed action-state + // this is required in case the FixedUpdate schedule runs multiple times in a frame, + // - next frame's input-map (in PreUpdate) to act on the delayed tick, so re-fetch the delayed action-state + get_delayed_action_state::.run_if( + is_input_delay + .and_then(not(is_in_rollback)) + .and_then(run_if_enabled::), ), ); + // in case the framerate is faster than fixed-update interval, we also write/clear the events at frame limits // TODO: should we also write the events at PreUpdate? // app.add_systems(PostUpdate, clear_input_events::

); @@ -765,21 +757,24 @@ pub fn generate_action_diffs( // TODO: optimize config.send_diffs_only at compile time? if config.send_diffs_only { for action in action_state.get_just_pressed() { - match action_state.action_data(action.clone()).axis_pair { + trace!(?action, consumed=?action_state.consumed(&action), "action is JustPressed!"); + let Some(action_data) = action_state.action_data(&action) else { + warn!("Action in ActionDiff has no data: was it generated correctly?"); + continue; + }; + match action_data.axis_pair { Some(axis_pair) => { diffs.push(ActionDiff::AxisPairChanged { action: action.clone(), axis_pair: axis_pair.into(), }); previous_axis_pairs - .raw_entry_mut() - .from_key(&action) - .or_insert_with(|| (action.clone(), HashMap::default())) - .1 + .entry(action) + .or_default() .insert(maybe_entity, axis_pair.xy()); } None => { - let value = action_state.value(action.clone()); + let value = action_data.value; diffs.push(if value == 1. { ActionDiff::Pressed { action: action.clone(), @@ -791,10 +786,8 @@ pub fn generate_action_diffs( } }); previous_values - .raw_entry_mut() - .from_key(&action) - .or_insert_with(|| (action.clone(), HashMap::default())) - .1 + .entry(action) + .or_default() .insert(maybe_entity, value); } } @@ -802,11 +795,17 @@ pub fn generate_action_diffs( } for action in action_state.get_pressed() { if config.send_diffs_only { - if action_state.just_pressed(action.clone()) { + // we already handled these cases above + if action_state.just_pressed(&action) { continue; } } - match action_state.action_data(action.clone()).axis_pair { + trace!(?action, consumed=?action_state.consumed(&action), "action is pressed!"); + let Some(action_data) = action_state.action_data(&action) else { + warn!("Action in ActionState has no data: was it generated correctly?"); + continue; + }; + match action_data.axis_pair { Some(axis_pair) => { if config.send_diffs_only { let previous_axis_pairs = @@ -825,7 +824,7 @@ pub fn generate_action_diffs( }); } None => { - let value = action_state.value(action.clone()); + let value = action_data.value; if config.send_diffs_only { let previous_values = previous_values.entry(action.clone()).or_default(); @@ -850,28 +849,41 @@ pub fn generate_action_diffs( } } } - let release_diffs = if config.send_diffs_only { - // TODO: issue for consumed keys: https://github.com/Leafwing-Studios/leafwing-input-manager/issues/443 - action_state.get_just_released() - } else { - action_state.get_released() - }; - for action in release_diffs { + for action in action_state + .get_released() + .iter() + // If we only send diffs, just keep the JustReleased keys. + // Consumed keys are marked as 'Release' so we need to handle them separately + // (see https://github.com/Leafwing-Studios/leafwing-input-manager/issues/443) + .filter(|action| { + !config.send_diffs_only + || action_state.just_released(*action) + || action_state.consumed(*action) + }) + { + let just_released = action_state.just_released(action); + let consumed = action_state.consumed(action); + trace!( + send_diffs=?config.send_diffs_only, + ?just_released, + ?consumed, + "action released: {:?}", action + ); diffs.push(ActionDiff::Released { action: action.clone(), }); if config.send_diffs_only { - if let Some(previous_axes) = previous_axis_pairs.get_mut(&action) { + if let Some(previous_axes) = previous_axis_pairs.get_mut(action) { previous_axes.remove(&maybe_entity); } - if let Some(previous_values) = previous_values.get_mut(&action) { + if let Some(previous_values) = previous_values.get_mut(action) { previous_values.remove(&maybe_entity); } } } if !diffs.is_empty() { - debug!(?maybe_entity, "writing action diffs: {:?}", diffs); + trace!(send_diffs_only = ?config.send_diffs_only, ?maybe_entity, "writing action diffs: {:?}", diffs); action_diffs.send(ActionDiffEvent { owner: maybe_entity, action_diff: diffs, @@ -939,7 +951,7 @@ mod tests { .server_app .world .spawn(( - InputMap::::new([(KeyCode::A, LeafwingInput1::Jump)]), + InputMap::::new([(LeafwingInput1::Jump, KeyCode::KeyA)]), ActionState::::default(), Replicate::default(), )) @@ -970,8 +982,8 @@ mod tests { .world .entity_mut(client_entity) .insert(InputMap::::new([( - KeyCode::A, LeafwingInput1::Jump, + KeyCode::KeyA, )])); assert!(stepper .client_app @@ -991,8 +1003,8 @@ mod tests { stepper .client_app .world - .resource_mut::>() - .press(KeyCode::A); + .resource_mut::>() + .press(KeyCode::KeyA); stepper.frame_step(); // listen to the ActionDiff event diff --git a/lightyear/src/client/interpolation/interpolation_history.rs b/lightyear/src/client/interpolation/interpolation_history.rs index 1ae89cb5c..b90c5f589 100644 --- a/lightyear/src/client/interpolation/interpolation_history.rs +++ b/lightyear/src/client/interpolation/interpolation_history.rs @@ -77,7 +77,8 @@ 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, + // TODO: unfortunately we need this to be mutable because of the MapEntities trait even though it's not actually needed... + mut manager: ResMut, tick_manager: Res, mut commands: Commands, connection: Res>, @@ -103,7 +104,7 @@ pub(crate) fn add_component_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)); + new_component.map_entities(&mut manager.interpolated_entity_map); match P::Components::mode() { ComponentSyncMode::Full => { trace!(?interpolated_entity, tick=?tick_manager.tick(), "spawn interpolation history"); @@ -134,7 +135,8 @@ pub(crate) fn add_component_history( /// When we receive a server update for an interpolated component, we need to store it in the confirmed history, pub(crate) fn apply_confirmed_update_mode_full( - manager: ResMut, + // TODO: unfortunately we need this to be mutable because of the MapEntities trait even though it's not actually needed... + mut manager: ResMut, mut interpolated_entities: Query< &mut ConfirmedHistory, (With, Without), @@ -161,7 +163,7 @@ pub(crate) fn apply_confirmed_update_mode_full( // map any entities from confirmed to predicted let mut component = confirmed_component.deref().clone(); - component.map_entities(Box::new(&manager.interpolated_entity_map)); + component.map_entities(&mut manager.interpolated_entity_map); trace!(component = ?component.name(), tick = ?tick, "adding confirmed update to history"); // update the history at the value that the entity currently is history.buffer.add_item(tick, component); @@ -175,7 +177,8 @@ pub(crate) fn apply_confirmed_update_mode_full( /// When we receive a server update for a simple component, we just update the entity directly pub(crate) fn apply_confirmed_update_mode_simple( - manager: ResMut, + // TODO: unfortunately we need this to be mutable because of the MapEntities trait even though it's not actually needed... + mut manager: ResMut, mut interpolated_entities: Query<&mut C, (With, Without)>, confirmed_entities: Query<(Entity, &Confirmed, Ref)>, ) where @@ -188,7 +191,7 @@ pub(crate) fn apply_confirmed_update_mode_simple( // for sync-components, we just match the confirmed component // map any entities from confirmed to interpolated first let mut component = confirmed_component.deref().clone(); - component.map_entities(Box::new(&manager.interpolated_entity_map)); + component.map_entities(&mut manager.interpolated_entity_map); *interpolated_component = component; } } diff --git a/lightyear/src/client/interpolation/plugin.rs b/lightyear/src/client/interpolation/plugin.rs index 6e35c59ef..f9e0b8846 100644 --- a/lightyear/src/client/interpolation/plugin.rs +++ b/lightyear/src/client/interpolation/plugin.rs @@ -156,6 +156,7 @@ pub fn add_prepare_interpolation_systems(app: &mu where P::Components: SyncMetadata, { + // TODO: maybe run this in PostUpdate? // TODO: maybe create an overarching prediction set that contains all others? app.add_systems( Update, diff --git a/lightyear/src/client/interpolation/visual_interpolation.rs b/lightyear/src/client/interpolation/visual_interpolation.rs index 2b9176319..43fc5b588 100644 --- a/lightyear/src/client/interpolation/visual_interpolation.rs +++ b/lightyear/src/client/interpolation/visual_interpolation.rs @@ -66,8 +66,8 @@ where // SETS app.configure_sets(PreUpdate, InterpolationSet::RestoreVisualInterpolation); app.configure_sets( - FixedUpdate, - InterpolationSet::UpdateVisualInterpolationState.after(FixedUpdateSet::MainFlush), + FixedPostUpdate, + InterpolationSet::UpdateVisualInterpolationState, ); app.configure_sets(PostUpdate, InterpolationSet::VisualInterpolation); @@ -79,7 +79,7 @@ where .in_set(InterpolationSet::RestoreVisualInterpolation), ); app.add_systems( - FixedUpdate, + FixedPostUpdate, update_visual_interpolation_status:: .in_set(InterpolationSet::UpdateVisualInterpolationState), ); @@ -226,10 +226,9 @@ mod tests { link_conditioner, frame_duration, ); - stepper.client_app.add_systems( - FixedUpdate, - fixed_update_increment.in_set(FixedUpdateSet::Main), - ); + stepper + .client_app + .add_systems(FixedUpdate, fixed_update_increment); stepper.client_app.world.insert_resource(Toggle(true)); stepper .client_app diff --git a/lightyear/src/client/plugin.rs b/lightyear/src/client/plugin.rs index 9dd079d5b..98f39d479 100644 --- a/lightyear/src/client/plugin.rs +++ b/lightyear/src/client/plugin.rs @@ -142,8 +142,9 @@ impl Plugin for ClientPlugin

{ app // PLUGINS // - .add_plugins(SharedPlugin { + .add_plugins(SharedPlugin::

{ config: config.client_config.shared.clone(), + ..default() }) .add_plugins(InputPlugin::

::default()) .add_plugins(PredictionPlugin::

::new(config.client_config.prediction)) @@ -169,15 +170,7 @@ impl Plugin for ClientPlugin

{ .insert_resource(config.protocol) // SYSTEM SETS // .configure_sets(PreUpdate, (MainSet::Receive, MainSet::ReceiveFlush).chain()) - .configure_sets( - FixedUpdate, - ( - FixedUpdateSet::TickUpdate, - FixedUpdateSet::Main, - FixedUpdateSet::MainFlush, - ) - .chain(), - ) + .configure_sets(FixedFirst, FixedUpdateSet::TickUpdate) // TODO: revisit the ordering of systems here. I believe all systems in ReplicationSet::All can run in parallel, // but maybe that's not the case and we need to run them in a certain order // NOTE: it's ok to run the replication systems less frequently than every frame diff --git a/lightyear/src/client/prediction/despawn.rs b/lightyear/src/client/prediction/despawn.rs index 0293edab6..47e72cd13 100644 --- a/lightyear/src/client/prediction/despawn.rs +++ b/lightyear/src/client/prediction/despawn.rs @@ -74,7 +74,7 @@ impl Command for PredictionDespawnCommand

{ pub trait PredictionDespawnCommandsExt { fn prediction_despawn(&mut self); } -impl PredictionDespawnCommandsExt for EntityCommands<'_, '_, '_> { +impl PredictionDespawnCommandsExt for EntityCommands<'_> { fn prediction_despawn(&mut self) { let entity = self.id(); self.commands().add(PredictionDespawnCommand { @@ -253,7 +253,7 @@ pub(crate) fn remove_despawn_marker( // stepper.client_mut().set_synced(); // stepper.client_app.add_systems( // FixedUpdate, -// increment_component_and_despawn.in_set(FixedUpdateSet::Main), +// increment_component_and_despawn // ); // // // Create a confirmed entity @@ -442,7 +442,7 @@ pub(crate) fn remove_despawn_marker( // ); // stepper.client_app.add_systems( // FixedUpdate, -// increment_component_and_despawn_both.in_set(FixedUpdateSet::Main), +// increment_component_and_despawn_both // ); // // // Create a confirmed entity diff --git a/lightyear/src/client/prediction/mod.rs b/lightyear/src/client/prediction/mod.rs index 7cb2bff5b..0c71665ea 100644 --- a/lightyear/src/client/prediction/mod.rs +++ b/lightyear/src/client/prediction/mod.rs @@ -63,7 +63,7 @@ pub(crate) fn clean_pre_predicted_entity( pre_predicted_entities: Query, Without)>, ) { for entity in pre_predicted_entities.iter() { - info!( + trace!( ?entity, "removing replicate from pre-spawned player-controlled entity" ); diff --git a/lightyear/src/client/prediction/plugin.rs b/lightyear/src/client/prediction/plugin.rs index e416b4df7..1d092bb6f 100644 --- a/lightyear/src/client/prediction/plugin.rs +++ b/lightyear/src/client/prediction/plugin.rs @@ -1,8 +1,8 @@ use std::marker::PhantomData; use bevy::prelude::{ - apply_deferred, App, FixedUpdate, IntoSystemConfigs, IntoSystemSetConfigs, Plugin, PostUpdate, - PreUpdate, Res, SystemSet, + apply_deferred, App, FixedPostUpdate, IntoSystemConfigs, IntoSystemSetConfigs, Plugin, + PostUpdate, PreUpdate, Res, SystemSet, }; use bevy::transform::TransformSystem; @@ -133,7 +133,7 @@ pub enum PredictionSet { Rollback, // NOTE: no need to add RollbackFlush because running a schedule (which we do for rollback) will flush all commands at the end of each run - // FixedUpdate Sets + // FixedPostUpdate Sets /// Increment the rollback tick after the main fixed-update physics loop has run IncrementRollbackTick, /// Set to deal with predicted/confirmed entities getting despawned @@ -190,7 +190,7 @@ where ), ); app.add_systems( - FixedUpdate, + FixedPostUpdate, ( add_prespawned_component_history::.in_set(PredictionSet::SpawnHistory), // we need to run this during fixed update to know accurately the history for each tick @@ -227,7 +227,7 @@ where _ => {} }; app.add_systems( - FixedUpdate, + FixedPostUpdate, remove_component_for_despawn_predicted::.in_set(PredictionSet::EntityDespawn), ); } @@ -306,10 +306,8 @@ impl Plugin for PredictionPlugin

{ // 3. Increment rollback tick (only run in fallback) // 4. Update predicted history app.configure_sets( - FixedUpdate, + FixedPostUpdate, ( - FixedUpdateSet::Main, - FixedUpdateSet::MainFlush, // we run the prespawn hash at FixedUpdate AND PostUpdate (to handle entities spawned during Update) // TODO: entities spawned during update might have a tick that is off by 1 or more... // account for this when setting the hash? @@ -328,7 +326,7 @@ impl Plugin for PredictionPlugin

{ .chain(), ); app.add_systems( - FixedUpdate, + FixedPostUpdate, ( // compute hashes for all pre-spawned player objects compute_prespawn_hash::

.in_set(ReplicationSet::SetPreSpawnedHash), diff --git a/lightyear/src/client/prediction/predicted_history.rs b/lightyear/src/client/prediction/predicted_history.rs index da36ae241..3d5cc905c 100644 --- a/lightyear/src/client/prediction/predicted_history.rs +++ b/lightyear/src/client/prediction/predicted_history.rs @@ -1,8 +1,8 @@ use std::ops::Deref; use bevy::prelude::{ - Commands, Component, DetectChanges, Entity, Or, Query, Ref, RemovedComponents, Res, With, - Without, + Commands, Component, DetectChanges, Entity, Or, Query, Ref, RemovedComponents, Res, ResMut, + With, Without, }; use tracing::{debug, error}; @@ -117,7 +117,8 @@ impl PredictionHistory { // TODO: only run this for SyncComponent where SyncMode != None #[allow(clippy::type_complexity)] pub(crate) fn add_component_history( - manager: Res, + // TODO: unfortunately we need this to be mutable because of the MapEntities trait even though it's not actually needed... + mut manager: ResMut, mut commands: Commands, tick_manager: Res, predicted_entities: Query< @@ -150,7 +151,7 @@ pub(crate) fn add_component_history( 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)); + new_component.map_entities(&mut manager.predicted_entity_map); match P::Components::mode() { ComponentSyncMode::Full => { // insert history, it will be quickly filled by a rollback (since it starts empty before the current client tick) @@ -312,7 +313,8 @@ 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, + // TODO: unfortunately we need this to be mutable because of the MapEntities trait even though it's not actually needed... + mut manager: ResMut, mut predicted_entities: Query< &mut C, ( @@ -343,7 +345,7 @@ pub(crate) fn apply_confirmed_update( ComponentSyncMode::Simple => { // map any entities from confirmed to predicted let mut component = confirmed_component.deref().clone(); - component.map_entities(Box::new(&manager.predicted_entity_map)); + component.map_entities(&mut manager.predicted_entity_map); *predicted_component = component; } _ => {} diff --git a/lightyear/src/client/prediction/prespawn.rs b/lightyear/src/client/prediction/prespawn.rs index e5f91a112..be95ed236 100644 --- a/lightyear/src/client/prediction/prespawn.rs +++ b/lightyear/src/client/prediction/prespawn.rs @@ -366,7 +366,8 @@ pub(crate) fn spawn_pre_spawned_player_object( #[cfg(test)] mod tests { use bevy::prelude::Entity; - use bevy::utils::{Duration, EntityHashMap}; + use bevy::utils::Duration; + use hashbrown::HashMap; use crate::_reexport::ItemWithReadyKey; use crate::client::prediction::resource::PredictionManager; @@ -416,11 +417,12 @@ mod tests { let current_tick = stepper.client_app.world.resource::().tick(); let prediction_manager = stepper.client_app.world.resource::(); let expected_hash: u64 = 11844036307541615334; + dbg!(&prediction_manager.prespawn_hash_to_entities); assert_eq!( prediction_manager.prespawn_hash_to_entities, - EntityHashMap::from_iter(vec![( + HashMap::from_iter(vec![( expected_hash, - vec![Entity::from_bits(0), Entity::from_bits(1)] + vec![Entity::from_raw(0), Entity::from_raw(1)] )]) ); assert_eq!( @@ -436,7 +438,7 @@ mod tests { stepper .client_app .world - .entity(Entity::from_bits(0)) + .entity(Entity::from_raw(0)) .get::>() .unwrap() .buffer diff --git a/lightyear/src/client/prediction/resource.rs b/lightyear/src/client/prediction/resource.rs index 236e6f245..fee4927db 100644 --- a/lightyear/src/client/prediction/resource.rs +++ b/lightyear/src/client/prediction/resource.rs @@ -1,12 +1,14 @@ //! Defines bevy resources needed for Prediction +use bevy::ecs::entity::EntityHash; use bevy::prelude::{Entity, Resource}; -use bevy::utils::EntityHashMap; use crate::_reexport::ReadyBuffer; use crate::prelude::Tick; use crate::shared::replication::entity_map::PredictedEntityMap; +type EntityHashMap = hashbrown::HashMap; + #[derive(Resource, Default, Debug)] pub(crate) struct PredictionManager { /// Map between remote and predicted entities diff --git a/lightyear/src/client/prediction/rollback.rs b/lightyear/src/client/prediction/rollback.rs index 5f925689e..444698c24 100644 --- a/lightyear/src/client/prediction/rollback.rs +++ b/lightyear/src/client/prediction/rollback.rs @@ -1,10 +1,11 @@ +use bevy::app::FixedMain; use std::fmt::Debug; +use bevy::ecs::entity::EntityHashSet; use bevy::prelude::{ - Commands, DespawnRecursiveExt, DetectChanges, Entity, FixedUpdate, Query, Ref, Res, ResMut, - With, Without, World, + Commands, DespawnRecursiveExt, DetectChanges, Entity, Query, Ref, Res, ResMut, With, Without, + World, }; -use bevy::utils::EntityHashSet; use tracing::{debug, error, info, trace, trace_span}; use crate::_reexport::{ComponentProtocol, FromType}; @@ -443,7 +444,7 @@ pub(crate) fn run_rollback(world: &mut World) { for i in 0..num_rollback_ticks { // TODO: if we are in rollback, there are some FixedUpdate systems that we don't want to re-run ?? // for example we only want to run the physics on non-confirmed entities - world.run_schedule(FixedUpdate) + world.run_schedule(FixedMain) } debug!("Finished rollback. Current tick: {:?}", current_tick); } @@ -526,7 +527,7 @@ pub(crate) fn increment_rollback_tick(mut rollback: ResMut) { // stepper.client_mut().set_synced(); // stepper.client_app.add_systems( // FixedUpdate, -// increment_component.in_set(FixedUpdateSet::Main), +// increment_component // ); // stepper // } diff --git a/lightyear/src/client/resource.rs b/lightyear/src/client/resource.rs index 604f4d32b..d1b2ab725 100644 --- a/lightyear/src/client/resource.rs +++ b/lightyear/src/client/resource.rs @@ -4,10 +4,10 @@ use std::str::FromStr; use anyhow::Result; use bevy::ecs::component::Tick as BevyTick; +use bevy::ecs::entity::EntityHashMap; use bevy::ecs::system::SystemParam; use bevy::prelude::{Entity, Mut, Res, ResMut, Resource, World}; use bevy::utils::Duration; -use bevy::utils::EntityHashMap; use tracing::{debug, trace, trace_span}; use crate::_reexport::ReplicationSend; @@ -453,7 +453,7 @@ impl ReplicationSend

for ConnectionManager

{ let _span = trace_span!("buffer_replication_messages").entered(); self.buffer_replication_messages(tick, bevy_tick) } - fn get_mut_replicate_component_cache(&mut self) -> &mut EntityHashMap> { + fn get_mut_replicate_component_cache(&mut self) -> &mut EntityHashMap> { &mut self.replication_sender.replicate_component_cache } } diff --git a/lightyear/src/client/sync.rs b/lightyear/src/client/sync.rs index 1f08f4b71..9c1065f28 100644 --- a/lightyear/src/client/sync.rs +++ b/lightyear/src/client/sync.rs @@ -597,12 +597,10 @@ mod tests { let new_time = WrappedTime::from_duration(tick_duration * (new_tick.0 as u32)); stepper.client_app.add_systems( - FixedUpdate, + FixedPreUpdate, press_input.in_set(InputSystemSet::BufferInputs), ); - stepper - .server_app - .add_systems(FixedUpdate, increment.in_set(FixedUpdateSet::Main)); + stepper.server_app.add_systems(FixedUpdate, increment); stepper .server_app diff --git a/lightyear/src/connection/netcode/utils.rs b/lightyear/src/connection/netcode/utils.rs index 0dbdfb099..9d0b5a10b 100644 --- a/lightyear/src/connection/netcode/utils.rs +++ b/lightyear/src/connection/netcode/utils.rs @@ -1,7 +1,7 @@ /// Return the number of seconds since unix epoch pub(crate) fn now() -> u64 { // number of seconds since unix epoch - instant::SystemTime::now() + bevy::utils::SystemTime::now() .duration_since(instant::SystemTime::UNIX_EPOCH) .unwrap() .as_secs_f64() as u64 diff --git a/lightyear/src/inputs/leafwing/input_buffer.rs b/lightyear/src/inputs/leafwing/input_buffer.rs index f8bbc48cd..ca35b0b68 100644 --- a/lightyear/src/inputs/leafwing/input_buffer.rs +++ b/lightyear/src/inputs/leafwing/input_buffer.rs @@ -2,9 +2,11 @@ use std::collections::VecDeque; use std::fmt::{Debug, Formatter}; use bevy::math::Vec2; -use bevy::prelude::{Component, Entity, Event, FromReflect, Reflect, Resource, TypePath}; +use bevy::prelude::{ + Component, Entity, EntityMapper, Event, FromReflect, Reflect, Resource, TypePath, +}; use bevy::reflect::DynamicTypePath; -use bevy::utils::{EntityHashSet, HashMap}; +use bevy::utils::HashMap; use leafwing_input_manager::axislike::DualAxisData; use leafwing_input_manager::prelude::ActionState; use leafwing_input_manager::Actionlike; @@ -12,7 +14,7 @@ use serde::{Deserialize, Serialize}; use tracing::trace; use crate::prelude::client::SyncComponent; -use crate::prelude::{EntityMapper, MapEntities, Message, Named}; +use crate::prelude::{LightyearMapEntities, Message, Named}; use crate::protocol::BitSerializable; use crate::shared::tick_manager::Tick; @@ -32,12 +34,10 @@ use super::LeafwingUserAction; // - we apply the ticks on the right tick to the entity/resource // - no need to maintain our inputbuffer on the server -impl<'a, A: LeafwingUserAction> MapEntities<'a> for ActionState { - fn map_entities(&mut self, entity_mapper: Box) {} - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() - } +impl LightyearMapEntities for ActionState { + fn map_entities(&mut self, entity_mapper: &mut M) {} } + impl Named for ActionState { // const NAME: &'static str = formatcp!("ActionState<{}>", A::short_type_path()); const NAME: &'static str = "ActionState"; @@ -186,22 +186,26 @@ impl ActionDiff { pub(crate) fn apply(self, action_state: &mut ActionState) { match self { ActionDiff::Pressed { action } => { - action_state.press(action.clone()); - action_state.action_data_mut(action.clone()).value = 1.; + action_state.press(&action); + // Pressing will initialize the ActionData if it doesn't exist + action_state.action_data_mut(&action).unwrap().value = 1.0; } ActionDiff::Released { action } => { - action_state.release(action.clone()); - let action_data = action_state.action_data_mut(action.clone()); + action_state.release(&action); + // Releasing will initialize the ActionData if it doesn't exist + let action_data = action_state.action_data_mut(&action).unwrap(); action_data.value = 0.; action_data.axis_pair = None; } ActionDiff::ValueChanged { action, value } => { - action_state.press(action.clone()); - action_state.action_data_mut(action.clone()).value = value; + action_state.press(&action); + // Pressing will initialize the ActionData if it doesn't exist + action_state.action_data_mut(&action).unwrap().value = value; } ActionDiff::AxisPairChanged { action, axis_pair } => { - action_state.press(action.clone()); - let action_data = action_state.action_data_mut(action.clone()); + action_state.press(&action); + // Pressing will initialize the ActionData if it doesn't exist + let action_data = action_state.action_data_mut(&action).unwrap(); action_data.axis_pair = Some(DualAxisData::from_xy(axis_pair)); action_data.value = axis_pair.length(); } @@ -235,13 +239,13 @@ impl Named for InputMessage { // const NAME: &'static str = ::short_type_path(); } -impl<'a, A: LeafwingUserAction> MapEntities<'a> for InputMessage { +impl LightyearMapEntities for InputMessage { // NOTE: we do NOT map the entities for input-message because when already convert // the entities on the message to the corresponding client entities when we write them // in the input message // NOTE: we only map the inputs for the pre-predicted entities - fn map_entities(&mut self, entity_mapper: Box) { + fn map_entities(&mut self, entity_mapper: &mut M) { self.diffs .iter_mut() .filter_map(|(entity, _)| { @@ -251,24 +255,7 @@ impl<'a, A: LeafwingUserAction> MapEntities<'a> for InputMessage { return None; } }) - .for_each(|entity| { - if let Some(new_entity) = entity_mapper.map(*entity) { - *entity = new_entity; - } - }); - } - - fn entities(&self) -> EntityHashSet { - self.diffs - .iter() - .filter_map(|(entity, _)| { - if let InputTarget::PrePredictedEntity(e) = entity { - Some(*e) - } else { - None - } - }) - .collect() + .for_each(|entity| *entity = entity_mapper.map_entity(*entity)); } } @@ -578,11 +565,11 @@ mod tests { let mut input_buffer = InputBuffer::default(); let mut a1 = ActionState::default(); - a1.press(Action::Jump); - a1.action_data_mut(Action::Jump).value = 0.0; + a1.press(&Action::Jump); + a1.action_data_mut(&Action::Jump).unwrap().value = 0.0; let mut a2 = ActionState::default(); - a2.press(Action::Jump); - a1.action_data_mut(Action::Jump).value = 1.0; + a2.press(&Action::Jump); + a1.action_data_mut(&Action::Jump).unwrap().value = 1.0; input_buffer.set(Tick(3), &a1); input_buffer.set(Tick(6), &a2); input_buffer.set(Tick(7), &a2); diff --git a/lightyear/src/lib.rs b/lightyear/src/lib.rs index 452819a0d..1438d81ed 100644 --- a/lightyear/src/lib.rs +++ b/lightyear/src/lib.rs @@ -82,13 +82,13 @@ pub mod prelude { pub use crate::protocol::Protocol; pub use crate::protocolize; pub use crate::shared::config::SharedConfig; - pub use crate::shared::log::LogConfig; pub use crate::shared::ping::manager::PingConfig; pub use crate::shared::plugin::{NetworkIdentity, SharedPlugin}; pub use crate::shared::replication::components::{ NetworkTarget, ReplicationGroup, ReplicationMode, ShouldBePredicted, }; - pub use crate::shared::replication::entity_map::{EntityMapper, MapEntities, RemoteEntityMap}; + pub use crate::shared::replication::entity_map::{LightyearMapEntities, RemoteEntityMap}; + pub use crate::shared::replication::hierarchy::ParentSync; pub use crate::shared::sets::{FixedUpdateSet, MainSet, ReplicationSet}; pub use crate::shared::tick_manager::TickManager; pub use crate::shared::tick_manager::{Tick, TickConfig}; diff --git a/lightyear/src/packet/message.rs b/lightyear/src/packet/message.rs index 2dde50221..e058d521b 100644 --- a/lightyear/src/packet/message.rs +++ b/lightyear/src/packet/message.rs @@ -5,13 +5,14 @@ use bytes::Bytes; use bitcode::encoding::{Fixed, Gamma}; use crate::packet::packet::FRAGMENT_SIZE; +use crate::prelude::LightyearMapEntities; use crate::protocol::EventContext; use crate::serialize::reader::ReadBuffer; 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::wrapping_id; +use bevy::ecs::entity::MapEntities; // strategies to avoid copying: // - have a net_id for each message or component @@ -288,8 +289,8 @@ impl MessageContainer { } // TODO: for now messages must be able to be used as events, since we output them in our message events -pub trait Message: EventContext + Named + for<'a> MapEntities<'a> {} -impl MapEntities<'a>> Message for T {} +pub trait Message: EventContext + Named + LightyearMapEntities {} +impl Message for T {} #[cfg(test)] mod tests { diff --git a/lightyear/src/protocol/channel.rs b/lightyear/src/protocol/channel.rs index 07a3f191f..8c858ab62 100644 --- a/lightyear/src/protocol/channel.rs +++ b/lightyear/src/protocol/channel.rs @@ -27,7 +27,7 @@ impl From for ChannelKind { } } -#[derive(Default, Clone, Debug)] +#[derive(Default, Clone, Debug, PartialEq)] pub struct ChannelRegistry { // we only store the ChannelBuilder because we might want to create multiple instances of the same channel pub(in crate::protocol) builder_map: HashMap, diff --git a/lightyear/src/protocol/component.rs b/lightyear/src/protocol/component.rs index efc8dc16c..e1a96e201 100644 --- a/lightyear/src/protocol/component.rs +++ b/lightyear/src/protocol/component.rs @@ -3,7 +3,7 @@ use std::fmt::{Debug, Display}; use std::hash::Hash; use bevy::prelude::{App, Component, Entity, EntityWorldMut, World}; -use bevy::utils::{EntityHashSet, HashMap}; +use bevy::utils::HashMap; use cfg_if::cfg_if; use crate::_reexport::{InstantCorrector, NullInterpolator}; @@ -11,7 +11,7 @@ use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use crate::client::components::{ComponentSyncMode, LerpFn, SyncMetadata}; -use crate::prelude::{EntityMapper, MapEntities, Message, Named, PreSpawnedPlayerObject}; +use crate::prelude::{LightyearMapEntities, Message, Named, PreSpawnedPlayerObject}; use crate::protocol::{BitSerializable, EventContext, Protocol}; use crate::shared::events::{ IterComponentInsertEvent, IterComponentRemoveEvent, IterComponentUpdateEvent, @@ -29,7 +29,7 @@ pub trait ComponentProtocol: BitSerializable + Serialize + DeserializeOwned - + for<'a> MapEntities<'a> + + LightyearMapEntities + ComponentBehaviour + Debug + Send @@ -189,7 +189,7 @@ cfg_if!( BitSerializable + Serialize + DeserializeOwned - + for<'a> MapEntities<'a> + + LightyearMapEntities + PartialEq + Eq + PartialOrd @@ -216,7 +216,7 @@ cfg_if!( BitSerializable + Serialize + DeserializeOwned - + for<'a> MapEntities<'a> + + LightyearMapEntities + PartialEq + Eq + PartialOrd diff --git a/lightyear/src/protocol/message.rs b/lightyear/src/protocol/message.rs index 08e4ea4ae..ae5ffd319 100644 --- a/lightyear/src/protocol/message.rs +++ b/lightyear/src/protocol/message.rs @@ -7,7 +7,7 @@ use serde::Serialize; use crate::inputs::native::input_buffer::InputMessage; use crate::packet::message::Message; -use crate::prelude::MapEntities; +use crate::prelude::LightyearMapEntities; use crate::protocol::registry::TypeKind; use crate::protocol::{BitSerializable, EventContext, Protocol}; #[cfg(feature = "leafwing")] @@ -31,7 +31,7 @@ pub trait MessageProtocol: + Serialize + DeserializeOwned + Clone - + for<'a> MapEntities<'a> + + LightyearMapEntities + Debug + Send + Sync diff --git a/lightyear/src/protocol/mod.rs b/lightyear/src/protocol/mod.rs index c23ab4f48..1f87a3bcf 100644 --- a/lightyear/src/protocol/mod.rs +++ b/lightyear/src/protocol/mod.rs @@ -111,7 +111,7 @@ macro_rules! protocolize { use $shared_crate_name::prelude::*; use $shared_crate_name::_reexport::*; - #[derive(Debug, Clone, Resource)] + #[derive(Debug, Clone, Resource, PartialEq)] pub struct $protocol { channel_registry: ChannelRegistry, } @@ -210,7 +210,7 @@ macro_rules! protocolize { use $shared_crate_name::_reexport::*; use $shared_crate_name::inputs::leafwing::{NoAction1, NoAction2}; - #[derive(Debug, Clone, Resource)] + #[derive(Debug, Clone, Resource, PartialEq)] pub struct $protocol { channel_registry: ChannelRegistry, } diff --git a/lightyear/src/protocol/registry.rs b/lightyear/src/protocol/registry.rs index 0e93d20d4..7bf4b2ee2 100644 --- a/lightyear/src/protocol/registry.rs +++ b/lightyear/src/protocol/registry.rs @@ -14,7 +14,7 @@ pub trait TypeKind: From + Copy + PartialEq + Eq + Hash {} // type TypeKind = From + Copy + PartialEq + Eq + Hash {}; /// Struct to map a type to an id that can be serialized over the network -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct TypeMapper { pub(in crate::protocol) next_net_id: NetId, pub(in crate::protocol) kind_map: HashMap, diff --git a/lightyear/src/server/connection.rs b/lightyear/src/server/connection.rs index dbd8fa325..6c585a537 100644 --- a/lightyear/src/server/connection.rs +++ b/lightyear/src/server/connection.rs @@ -3,8 +3,9 @@ use bevy::utils::Duration; use anyhow::{Context, Result}; use bevy::ecs::component::Tick as BevyTick; +use bevy::ecs::entity::EntityHashMap; use bevy::prelude::{Entity, Res, ResMut, Resource, World}; -use bevy::utils::{EntityHashMap, Entry, HashMap, HashSet}; +use bevy::utils::{Entry, HashMap, HashSet}; use serde::Serialize; use tracing::{debug, debug_span, info, trace, trace_span}; @@ -15,7 +16,7 @@ use crate::connection::netcode::ClientId; use crate::inputs::native::input_buffer::InputBuffer; use crate::packet::message_manager::MessageManager; use crate::packet::packet_manager::Payload; -use crate::prelude::{Channel, ChannelKind, MapEntities, Message}; +use crate::prelude::{Channel, ChannelKind, LightyearMapEntities, Message}; use crate::protocol::channel::ChannelRegistry; use crate::protocol::Protocol; use crate::serialize::reader::ReadBuffer; @@ -43,7 +44,7 @@ pub struct ConnectionManager { // NOTE: we put this here because we only need one per world, not one per connection /// Stores the last `Replicate` component for each replicated entity owned by the current world (the world that sends replication updates) /// Needed to know the value of the Replicate component after the entity gets despawned, to know how we replicate the EntityDespawn - pub replicate_component_cache: EntityHashMap>, + pub replicate_component_cache: EntityHashMap>, // list of clients that connected since the last time we sent replication messages // (we want to keep track of them because we need to replicate the entire world state to them) @@ -500,9 +501,7 @@ impl Connection

{ self.replication_receiver.remote_entity_map ); // map any entities inside the message - message.map_entities(Box::new( - &self.replication_receiver.remote_entity_map, - )); + message.map_entities(&mut self.replication_receiver.remote_entity_map); if target != NetworkTarget::None { self.messages_to_rebroadcast.push(( message.clone(), diff --git a/lightyear/src/server/input.rs b/lightyear/src/server/input.rs index a355810a5..f9245725b 100644 --- a/lightyear/src/server/input.rs +++ b/lightyear/src/server/input.rs @@ -1,7 +1,7 @@ //! Handles client-generated inputs use bevy::prelude::{ - App, EventReader, EventWriter, FixedUpdate, IntoSystemConfigs, IntoSystemSetConfigs, Plugin, - Res, ResMut, SystemSet, + App, EventReader, EventWriter, FixedPostUpdate, FixedPreUpdate, IntoSystemConfigs, + IntoSystemSetConfigs, Plugin, Res, ResMut, SystemSet, }; use crate::connection::netcode::ClientId; @@ -54,24 +54,16 @@ impl Plugin for InputPlugin

{ // EVENTS app.add_event::>(); // SETS - app.configure_sets( - FixedUpdate, - ( - FixedUpdateSet::TickUpdate, - InputSystemSet::WriteInputEvents, - FixedUpdateSet::Main, - InputSystemSet::ClearInputEvents, - ) - .chain(), - ); + app.configure_sets(FixedPreUpdate, InputSystemSet::WriteInputEvents); + app.configure_sets(FixedPostUpdate, InputSystemSet::ClearInputEvents); // insert the input buffer resource app.add_systems( - FixedUpdate, + FixedPreUpdate, write_input_event::

.in_set(InputSystemSet::WriteInputEvents), ); app.add_systems( - FixedUpdate, + FixedPostUpdate, bevy::ecs::event::event_update_system::> .in_set(InputSystemSet::ClearInputEvents), ); diff --git a/lightyear/src/server/input_leafwing.rs b/lightyear/src/server/input_leafwing.rs index 47bc2a9e3..9eb730cdc 100644 --- a/lightyear/src/server/input_leafwing.rs +++ b/lightyear/src/server/input_leafwing.rs @@ -63,15 +63,7 @@ where // NOTE: we need to add the leafwing server plugin because it ticks Action-States (so just-pressed become pressed) app.add_plugins(InputManagerPlugin::::server()); // SETS - app.configure_sets( - FixedUpdate, - ( - FixedUpdateSet::TickUpdate, - InputSystemSet::Update, - FixedUpdateSet::Main, - ) - .chain(), - ); + app.configure_sets(FixedPreUpdate, InputSystemSet::Update); // SYSTEMS app.add_systems( PreUpdate, @@ -86,7 +78,7 @@ where .after(MainSet::ReceiveFlush), ); app.add_systems( - FixedUpdate, + FixedPreUpdate, update_action_state::.in_set(InputSystemSet::Update), ); } @@ -291,8 +283,8 @@ mod tests { .world .entity_mut(client_entity) .insert(InputMap::::new([( - KeyCode::A, LeafwingInput1::Jump, + KeyCode::KeyA, )])); stepper.frame_step(); // check that the client entity got an InputBuffer added to it @@ -307,16 +299,16 @@ mod tests { stepper .client_app .world - .resource_mut::>() - .press(KeyCode::A); + .resource_mut::>() + .press(KeyCode::KeyA); stepper.frame_step(); // client tick when we send the Jump action let client_tick = stepper.client_tick(); stepper .client_app .world - .resource_mut::>() - .release(KeyCode::A); + .resource_mut::>() + .release(KeyCode::KeyA); stepper.frame_step(); // we should have sent an InputMessage from client to server diff --git a/lightyear/src/server/plugin.rs b/lightyear/src/server/plugin.rs index 2ab271cae..269cb4faf 100644 --- a/lightyear/src/server/plugin.rs +++ b/lightyear/src/server/plugin.rs @@ -5,8 +5,8 @@ use std::sync::Mutex; use crate::_reexport::ShouldBeInterpolated; use bevy::prelude::{ - apply_deferred, App, FixedUpdate, IntoSystemConfigs, IntoSystemSetConfigs, - Plugin as PluginType, PostUpdate, PreUpdate, + apply_deferred, default, App, IntoSystemConfigs, IntoSystemSetConfigs, Plugin as PluginType, + PostUpdate, PreUpdate, }; use bevy::time::common_conditions::on_timer; @@ -84,9 +84,10 @@ impl PluginType for ServerPlugin

{ app // PLUGINS - .add_plugins(SharedPlugin { + .add_plugins(SharedPlugin::

{ // TODO: move shared config out of server_config config: config.server_config.shared.clone(), + ..default() }) .add_plugins(InputPlugin::

::default()) .add_plugins(RoomPlugin::

::default()) diff --git a/lightyear/src/server/resource.rs b/lightyear/src/server/resource.rs index 2e7446812..19d878b6a 100644 --- a/lightyear/src/server/resource.rs +++ b/lightyear/src/server/resource.rs @@ -6,9 +6,10 @@ use std::net::SocketAddr; use anyhow::{Context, Result}; use bevy::ecs::component::Tick as BevyTick; +use bevy::ecs::entity::EntityHashMap; use bevy::ecs::system::SystemParam; use bevy::prelude::{Entity, Res, ResMut, Resource, World}; -use bevy::utils::{EntityHashMap, HashSet}; +use bevy::utils::HashSet; use crossbeam_channel::Sender; use tracing::{debug, debug_span, error, info, trace, trace_span}; @@ -450,7 +451,7 @@ impl ReplicationSend

for ConnectionManager

{ self.buffer_replication_messages(tick, bevy_tick) } - fn get_mut_replicate_component_cache(&mut self) -> &mut EntityHashMap> { + fn get_mut_replicate_component_cache(&mut self) -> &mut EntityHashMap> { &mut self.replicate_component_cache } } diff --git a/lightyear/src/shared/config.rs b/lightyear/src/shared/config.rs index de0ad3f3a..a48feaec1 100644 --- a/lightyear/src/shared/config.rs +++ b/lightyear/src/shared/config.rs @@ -1,7 +1,6 @@ //! Configuration that has to be the same between the server and the client. use bevy::utils::Duration; -use crate::shared::log::LogConfig; use crate::shared::tick_manager::TickConfig; /// Configuration that has to be the same between the server and the client. @@ -14,8 +13,6 @@ pub struct SharedConfig { pub server_send_interval: Duration, /// configuration for the [`FixedUpdate`](bevy::prelude::FixedUpdate) schedule pub tick: TickConfig, - /// logging configuration - pub log: LogConfig, } impl Default for SharedConfig { @@ -26,7 +23,6 @@ impl Default for SharedConfig { client_send_interval: Duration::from_millis(0), server_send_interval: Duration::from_millis(0), tick: TickConfig::new(Duration::from_millis(16)), - log: LogConfig::default(), } } } diff --git a/lightyear/src/shared/log.rs b/lightyear/src/shared/log.rs index 61c52d036..a2df9d274 100644 --- a/lightyear/src/shared/log.rs +++ b/lightyear/src/shared/log.rs @@ -1,107 +1,51 @@ #![allow(clippy::type_complexity)] //! Log plugin that also potentially emits metrics to Prometheus. //! This cannot be used in conjunction with Bevy's `LogPlugin` +use bevy::log::BoxedSubscriber; use bevy::prelude::{App, Plugin}; #[cfg(feature = "metrics")] use metrics_tracing_context::{MetricsLayer, TracingContextLayer}; +use tracing::instrument::WithSubscriber; use tracing::Level; use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter}; -/// Adds logging to Apps. -/// -/// # Panics -/// -/// This plugin should not be added multiple times in the same process. This plugin -/// sets up global logging configuration for **all** Apps in a given process, and -/// rerunning the same initialization multiple times will lead to a panic. -// TODO: take directly log config? -pub struct LogPlugin { - pub config: LogConfig, -} - -#[derive(Clone, Debug)] -/// Configuration to setup logging/metrics -pub struct LogConfig { - /// Filters logs using the [`EnvFilter`] format - pub filter: String, - - /// Filters out logs that are "less than" the given level. - /// This can be further filtered using the `filter` setting. - pub level: Level, -} - -impl Default for LogConfig { - fn default() -> Self { - Self { - filter: "wgpu=error,wgpu_hal=error,naga=warn,bevy_app=info".to_string(), - level: Level::INFO, - } - } -} - -impl Plugin for LogPlugin { - fn build(&self, app: &mut App) { - let finished_subscriber; - let default_filter = { format!("{},{}", self.config.level, self.config.filter) }; - let filter_layer = EnvFilter::try_from_default_env() - .or_else(|_| EnvFilter::try_new(&default_filter)) - .unwrap(); - // println!("Log filter: {:?}", default_filter); - let subscriber = Registry::default().with(filter_layer); - - let fmt_layer = tracing_subscriber::fmt::Layer::default() - // log span enters - .with_span_events(tracing_subscriber::fmt::format::FmtSpan::ENTER) - // .with_max_level(self.level) - .with_writer(std::io::stderr); - - // bevy_render::renderer logs a `tracy.frame_mark` event every frame - // at Level::INFO. Formatted logs should omit it. - let subscriber = subscriber.with(fmt_layer); - - // add metrics_tracing_context support - cfg_if::cfg_if! { - if #[cfg(feature = "metrics")] { - let subscriber = subscriber.with(MetricsLayer::new()); - // create a prometheus exporter with tracing context support - let builder = metrics_exporter_prometheus::PrometheusBuilder::new(); - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - let (recorder, exporter) = { - let _g = runtime.enter(); - builder.build().unwrap() - }; - // add extra metrics layers - // Stack::new(recorder) - // .push(TracingContextLayer::all()) - // .install(); - // runtime.spawn(exporter); - - // Add in tracing - let traced_recorder = TracingContextLayer::all().layer(recorder); - std::thread::Builder::new() - .spawn(move || runtime.block_on(exporter)) - .unwrap(); - metrics::set_boxed_recorder(Box::new(traced_recorder)); - } else { - } +pub fn add_log_layer(subscriber: BoxedSubscriber) -> BoxedSubscriber { + // let fmt_layer = tracing_subscriber::fmt::Layer::default() + // // log span enters + // .with_span_events(tracing_subscriber::fmt::format::FmtSpan::ENTER) + // // .with_max_level(self.level) + // .with_writer(std::io::stderr); + + // add metrics_tracing_context support + cfg_if::cfg_if! { + if #[cfg(feature = "metrics")] { + let subscriber = subscriber.with(MetricsLayer::new()); + // create a prometheus exporter with tracing context support + let builder = metrics_exporter_prometheus::PrometheusBuilder::new(); + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + let (recorder, exporter) = { + let _g = runtime.enter(); + builder.build().unwrap() + }; + // add extra metrics layers + // Stack::new(recorder) + // .push(TracingContextLayer::all()) + // .install(); + // runtime.spawn(exporter); + + // Add in tracing + let traced_recorder = TracingContextLayer::all().layer(recorder); + std::thread::Builder::new() + .spawn(move || runtime.block_on(exporter)) + .unwrap(); + metrics::set_boxed_recorder(Box::new(traced_recorder)); + } else { } - - finished_subscriber = subscriber; - - // let logger_already_set = LogTracer::init().is_err(); - let subscriber_already_set = - tracing::subscriber::set_global_default(finished_subscriber).is_err(); - - // match (logger_already_set, subscriber_already_set) { - // (true, true) => warn!( - // "Could not set global logger and tracing subscriber as they are already set. Consider disabling LogPlugin." - // ), - // (true, _) => warn!("Could not set global logger as it is already set. Consider disabling LogPlugin."), - // (_, true) => warn!("Could not set global tracing subscriber as it is already set. Consider disabling LogPlugin."), - // _ => (), - // } } + // let new_subscriber = tracing_subscriber::Layer::with_subscriber(fmt_layer, subscriber); + // Box::new(new_subscriber) + Box::new(subscriber) } diff --git a/lightyear/src/shared/mod.rs b/lightyear/src/shared/mod.rs index 821ebcb10..58302de58 100644 --- a/lightyear/src/shared/mod.rs +++ b/lightyear/src/shared/mod.rs @@ -4,7 +4,7 @@ pub mod config; pub mod events; -pub(crate) mod log; +pub mod log; pub mod ping; diff --git a/lightyear/src/shared/plugin.rs b/lightyear/src/shared/plugin.rs index 0a2c69a03..aa906e55f 100644 --- a/lightyear/src/shared/plugin.rs +++ b/lightyear/src/shared/plugin.rs @@ -3,12 +3,24 @@ use bevy::ecs::system::SystemParam; use bevy::prelude::*; use crate::client::config::ClientConfig; +use crate::prelude::Protocol; use crate::shared::config::SharedConfig; -use crate::shared::log; use crate::shared::tick_manager::TickManagerPlugin; +use crate::shared::{log, replication}; +use replication::hierarchy::HierarchySyncPlugin; -pub struct SharedPlugin { +pub struct SharedPlugin { pub config: SharedConfig, + pub _marker: std::marker::PhantomData

, +} + +impl Default for SharedPlugin

{ + fn default() -> Self { + Self { + config: SharedConfig::default(), + _marker: std::marker::PhantomData, + } + } } /// You can use this as a SystemParam to identify whether you're running on the client or the server @@ -27,7 +39,7 @@ impl<'w, 's> NetworkIdentity<'w, 's> { } } -impl Plugin for SharedPlugin { +impl Plugin for SharedPlugin

{ fn build(&self, app: &mut App) { // RESOURCES // NOTE: this tick duration must be the same as any previous existing fixed timesteps @@ -38,8 +50,7 @@ impl Plugin for SharedPlugin { // PLUGINS // TODO: increment_tick should be shared // app.add_systems(FixedUpdate, increment_tick); - let log_config = self.config.log.clone(); - app.add_plugins(log::LogPlugin { config: log_config }); + app.add_plugins(HierarchySyncPlugin::

::default()); app.add_plugins(TickManagerPlugin { config: self.config.tick.clone(), }); diff --git a/lightyear/src/shared/replication/components.rs b/lightyear/src/shared/replication/components.rs index c59475a27..cac26510e 100644 --- a/lightyear/src/shared/replication/components.rs +++ b/lightyear/src/shared/replication/components.rs @@ -10,7 +10,6 @@ use crate::_reexport::FromType; use crate::channel::builder::Channel; use crate::client::components::SyncComponent; use crate::connection::netcode::ClientId; -use crate::prelude::{EntityMapper, MapEntities}; use crate::protocol::Protocol; use crate::server::room::ClientVisibility; @@ -20,7 +19,7 @@ pub struct DespawnTracker; /// Component that indicates that an entity should be replicated. Added to the entity when it is spawned /// in the world that sends replication updates. -#[derive(Component, Clone)] +#[derive(Component, Clone, PartialEq, Debug)] pub struct Replicate { /// Which clients should this entity be replicated to pub replication_target: NetworkTarget, @@ -40,6 +39,10 @@ pub struct Replicate { // TODO: currently, if the host removes Replicate, then the entity is not removed in the remote // it just keeps living but doesn't receive any updates. Should we make this configurable? pub replication_group: ReplicationGroup, + /// If true, recursively add `Replicate` and `ParentSync` components to all children to make sure they are replicated + /// If false, you can still replicate hierarchies, but in a more fine-grained manner. You will have to add the `Replicate` + /// and `ParentSync` components to the children yourself + pub replicate_hierarchy: bool, /// Lets you override the replication modalities for a specific component pub per_component_metadata: HashMap, @@ -188,7 +191,7 @@ impl Replicate

{ } } -#[derive(Debug, Default, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub enum ReplicationGroupIdBuilder { // the group id is the entity id #[default] @@ -200,7 +203,7 @@ pub enum ReplicationGroupIdBuilder { Group(u64), } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct ReplicationGroup { id_builder: ReplicationGroupIdBuilder, /// the priority of the accumulation group @@ -278,6 +281,7 @@ impl Default for Replicate

{ replication_clients_cache: HashMap::new(), replication_mode: ReplicationMode::default(), replication_group: Default::default(), + replicate_hierarchy: true, per_component_metadata: HashMap::default(), }; // those metadata components should only be replicated once diff --git a/lightyear/src/shared/replication/entity_map.rs b/lightyear/src/shared/replication/entity_map.rs index 14762bed1..0ffa1a125 100644 --- a/lightyear/src/shared/replication/entity_map.rs +++ b/lightyear/src/shared/replication/entity_map.rs @@ -1,47 +1,36 @@ //! Map between local and remote entities use anyhow::Context; +use bevy::ecs::entity::{EntityHashMap, EntityHashSet, EntityMapper, MapEntities}; use bevy::prelude::{Entity, EntityWorldMut, World}; use bevy::utils::hashbrown::hash_map::Entry; -use bevy::utils::{EntityHashMap, EntityHashSet}; use tracing::error; -pub trait EntityMapper { - /// Map an entity - fn map(&self, entity: Entity) -> Option; -} - -impl EntityMapper for &T { - #[inline] - fn map(&self, entity: Entity) -> Option { - (*self).map(entity) - } -} - -impl EntityMapper for EntityHashMap { - #[inline] - fn map(&self, entity: Entity) -> Option { - self.get(&entity).copied() - } +// TODO: another solution to avoid the orphan rule would be to implement MapEntities directly on the enum type? +// Wrapper trait to avoid the orphan rule (so that I can implement MapEntities for external types) +pub trait LightyearMapEntities { + fn map_entities(&mut self, entity_mapper: &mut M); } #[derive(Default, Debug)] /// Map between local and remote entities. (used mostly on client because it's when we receive entity updates) pub struct RemoteEntityMap { - remote_to_local: EntityHashMap, - local_to_remote: EntityHashMap, + remote_to_local: EntityHashMap, + local_to_remote: EntityHashMap, } #[derive(Default, Debug)] pub struct PredictedEntityMap { // map from the confirmed entity to the predicted entity // useful for despawning, as we won't have access to the Confirmed/Predicted components anymore - pub(crate) confirmed_to_predicted: EntityHashMap, + pub(crate) confirmed_to_predicted: EntityHashMap, } impl EntityMapper for PredictedEntityMap { - #[inline] - fn map(&self, entity: Entity) -> Option { - self.confirmed_to_predicted.get(&entity).copied() + fn map_entity(&mut self, entity: Entity) -> Entity { + self.confirmed_to_predicted + .get(&entity) + .copied() + .unwrap_or(entity) } } @@ -49,13 +38,15 @@ impl EntityMapper for PredictedEntityMap { pub struct InterpolatedEntityMap { // map from the confirmed entity to the interpolated entity // useful for despawning, as we won't have access to the Confirmed/Interpolated components anymore - pub(crate) confirmed_to_interpolated: EntityHashMap, + pub(crate) confirmed_to_interpolated: EntityHashMap, } impl EntityMapper for InterpolatedEntityMap { - #[inline] - fn map(&self, entity: Entity) -> Option { - self.confirmed_to_interpolated.get(&entity).copied() + fn map_entity(&mut self, entity: Entity) -> Entity { + self.confirmed_to_interpolated + .get(&entity) + .copied() + .unwrap_or(entity) } } @@ -66,14 +57,14 @@ impl RemoteEntityMap { self.local_to_remote.insert(local_entity, remote_entity); } - pub(crate) fn get_to_remote_mapper(&self) -> Box { - Box::new(&self.local_to_remote) - } - - // TODO: makke sure all calls to remote entity map use this to get the exact mapper - pub(crate) fn get_to_local_mapper(&self) -> Box { - Box::new(&self.remote_to_local) - } + // pub(crate) fn get_to_remote_mapper(&self) -> Box { + // Box::new(&self.local_to_remote) + // } + // + // // TODO: make sure all calls to remote entity map use this to get the exact mapper + // pub(crate) fn get_to_local_mapper(&self) -> Box { + // Box::new(&self.remote_to_local) + // } #[inline] pub(crate) fn get_local(&self, remote_entity: Entity) -> Option<&Entity> { @@ -123,12 +114,12 @@ impl RemoteEntityMap { } #[inline] - pub fn to_local(&self) -> &EntityHashMap { + pub fn to_local(&self) -> &EntityHashMap { &self.remote_to_local } #[inline] - pub fn to_remote(&self) -> &EntityHashMap { + pub fn to_remote(&self) -> &EntityHashMap { &self.local_to_remote } @@ -143,37 +134,8 @@ impl RemoteEntityMap { } impl EntityMapper for RemoteEntityMap { - #[inline] - fn map(&self, entity: Entity) -> Option { - self.get_local(entity).copied() - } -} - -/// Trait that Messages or Components must implement to be able to map entities -pub trait MapEntities<'a> { - /// Map the entities inside the message or component from the remote World to the local World - fn map_entities(&mut self, entity_mapper: Box); - - /// Get all the entities that are present in that message or component - fn entities(&self) -> EntityHashSet; -} - -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; - } else { - error!( - "cannot map entity {:?} because it doesn't exist in the entity map!", - self - ); - } - } - - #[inline] - fn entities(&self) -> EntityHashSet { - EntityHashSet::from_iter(vec![*self]) + fn map_entity(&mut self, entity: Entity) -> Entity { + self.get_local(entity).copied().unwrap_or(entity) } } diff --git a/lightyear/src/shared/replication/hierarchy.rs b/lightyear/src/shared/replication/hierarchy.rs new file mode 100644 index 000000000..94dec599a --- /dev/null +++ b/lightyear/src/shared/replication/hierarchy.rs @@ -0,0 +1,365 @@ +//! This module is responsible for making sure that parent-children hierarchies are replicated correctly. +use crate::prelude::{LightyearMapEntities, MainSet, ReplicationGroup, ReplicationSet}; +use crate::protocol::Protocol; +use crate::shared::replication::components::Replicate; +use bevy::prelude::*; +use lightyear_macros::MessageInternal; +use serde::{Deserialize, Serialize}; + +/// This component can be added to an entity to replicate the entity's hierarchy to the remote world. +/// The `ParentSync` component will be updated automatically when the `Parent` component changes, +/// and the entity's hierarchy will automatically be updated when the `ParentSync` component changes. +/// +/// Updates entity's `Parent` component on change. +/// Removes the parent if `None`. +#[derive( + MessageInternal, + Component, + Default, + Reflect, + Clone, + Copy, + Serialize, + Deserialize, + Debug, + PartialEq, +)] +#[message(custom_map)] +pub struct ParentSync(Option); + +impl LightyearMapEntities for ParentSync { + fn map_entities(&mut self, entity_mapper: &mut M) { + if let Some(entity) = &mut self.0 { + *entity = entity_mapper.map_entity(*entity); + } + } +} + +pub struct HierarchySyncPlugin

{ + _marker: std::marker::PhantomData

, +} + +impl

Default for HierarchySyncPlugin

{ + fn default() -> Self { + Self { + _marker: std::marker::PhantomData, + } + } +} + +impl HierarchySyncPlugin

{ + /// If `replicate.replicate_hierarchy` is true, replicate the entire hierarchy of the entity + fn propagate_replicate( + mut commands: Commands, + // query the root parent of the hierarchy + parent_query: Query<(Entity, Ref>), (Without, With)>, + children_query: Query<&Children>, + ) { + for (parent_entity, replicate) in parent_query.iter() { + // TODO: we only want to do this if the `replicate_hierarchy` field has changed, not other fields! + // maybe use a different component? + if replicate.is_changed() && replicate.replicate_hierarchy { + // iterate through all descendents of the entity + for child in children_query.iter_descendants(parent_entity) { + let mut replicate = replicate.clone(); + // the entire hierarchy is replicated as a single group, that uses the parent's entity as the group id + replicate.replication_group = ReplicationGroup::new_id(parent_entity.to_bits()); + // no need to set the correct parent as it will be set later in the `update_parent_sync` system + commands.entity(child).insert((replicate, ParentSync(None))); + } + } + } + } + + /// Update parent/children hierarchy if parent_sync changed + /// + /// This only runs on the receiving side + fn update_parent( + mut commands: Commands, + hierarchy: Query< + (Entity, &ParentSync, Option<&Parent>), + (Changed, Without>), + >, + ) { + for (entity, parent_sync, parent) in &hierarchy { + trace!( + "update_parent: entity: {:?}, parent_sync: {:?}, parent: {:?}", + entity, + parent_sync, + parent + ); + if let Some(new_parent) = parent_sync.0 { + if parent.filter(|&parent| **parent == new_parent).is_none() { + commands.entity(entity).set_parent(new_parent); + } + } else if parent.is_some() { + commands.entity(entity).remove_parent(); + } + } + } + + /// Update ParentSync if the hierarchy changed + /// (run this in post-update before replicating, to account for any hierarchy changed initiated by the user) + /// + /// This only runs on the sending side + fn update_parent_sync(mut query: Query<(Ref, &mut ParentSync), With>>) { + for (parent, mut parent_sync) in query.iter_mut() { + if parent.is_changed() || parent_sync.is_added() { + trace!( + ?parent, + ?parent_sync, + "Update parent sync because hierarchy has changed" + ); + parent_sync.set_if_neq(ParentSync(Some(**parent))); + } + } + } + + /// Update ParentSync if the parent has been removed + /// + /// This only runs on the sending side + fn removal_system( + mut removed_parents: RemovedComponents, + mut hierarchy: Query<&mut ParentSync, With>>, + ) { + for entity in removed_parents.read() { + if let Ok(mut parent_sync) = hierarchy.get_mut(entity) { + parent_sync.0 = None; + } + } + } +} + +impl Plugin for HierarchySyncPlugin

{ + fn build(&self, app: &mut App) { + // TODO: does this work for client replication? (client replicating to other clients via the server?) + // when we receive a ParentSync update from the remote, update the hierarchy + app.add_systems(PreUpdate, Self::update_parent.after(MainSet::ReceiveFlush)) + .add_systems( + PostUpdate, + ( + (Self::propagate_replicate, Self::update_parent_sync).chain(), + Self::removal_system, + ) + .before(ReplicationSet::SendComponentUpdates), + ); + } +} + +#[cfg(test)] +mod tests { + use crate::client::interpolation::{VisualInterpolateStatus, VisualInterpolationPlugin}; + use crate::client::sync::SyncConfig; + use crate::prelude::client::{InterpolationConfig, PredictionConfig}; + use crate::prelude::{ + FixedUpdateSet, LinkConditionerConfig, ReplicationGroup, SharedConfig, TickConfig, + }; + use crate::shared::replication::hierarchy::ParentSync; + use crate::tests::protocol::*; + use crate::tests::stepper::{BevyStepper, Step}; + use bevy::hierarchy::{BuildWorldChildren, Children, Parent}; + use bevy::prelude::{default, Entity, With}; + use std::ops::Deref; + use std::time::Duration; + + fn setup_hierarchy() -> (BevyStepper, Entity, Entity, Entity) { + let tick_duration = Duration::from_millis(10); + let frame_duration = Duration::from_millis(10); + let shared_config = SharedConfig { + enable_replication: true, + tick: TickConfig::new(tick_duration), + ..Default::default() + }; + let link_conditioner = LinkConditionerConfig { + incoming_latency: Duration::from_millis(0), + incoming_jitter: Duration::from_millis(0), + incoming_loss: 0.0, + }; + let sync_config = SyncConfig::default().speedup_factor(1.0); + let prediction_config = PredictionConfig::default().disable(false); + let interpolation_config = InterpolationConfig::default(); + let mut stepper = BevyStepper::new( + shared_config, + sync_config, + prediction_config, + interpolation_config, + link_conditioner, + frame_duration, + ); + stepper.init(); + let child = stepper.server_app.world.spawn(Component3(0.0)).id(); + let parent = stepper + .server_app + .world + .spawn(Component2(0.0)) + .add_child(child) + .id(); + let grandparent = stepper + .server_app + .world + .spawn(Component1(0.0)) + .add_child(parent) + .id(); + (stepper, grandparent, parent, child) + } + + #[test] + fn test_update_parent() { + let (mut stepper, grandparent, parent, child) = setup_hierarchy(); + + let replicate = Replicate { + replicate_hierarchy: false, + // make sure that child and parent are replicated in the same group, so that both entities are spawned + // before entity mapping is done + replication_group: ReplicationGroup::new_id(0), + ..default() + }; + stepper + .server_app + .world + .entity_mut(parent) + .insert((replicate.clone(), ParentSync::default())); + stepper + .server_app + .world + .entity_mut(grandparent) + .insert(replicate.clone()); + stepper.frame_step(); + stepper.frame_step(); + + // check that the parent got replicated, along with the hierarchy information + let client_grandparent = stepper + .client_app + .world + .query_filtered::>() + .get_single(&stepper.client_app.world) + .unwrap(); + let (client_parent, client_parent_sync, client_parent_component) = stepper + .client_app + .world + .query_filtered::<(Entity, &ParentSync, &Parent), With>() + .get_single(&stepper.client_app.world) + .unwrap(); + + assert_eq!(client_parent_sync.0, Some(client_grandparent)); + assert_eq!(*client_parent_component.deref(), client_grandparent); + + // remove the hierarchy on the sender side + stepper.server_app.world.entity_mut(parent).remove_parent(); + stepper.frame_step(); + stepper.frame_step(); + // 1. make sure that parent sync has been updated on the sender side + assert_eq!( + stepper + .server_app + .world + .entity_mut(parent) + .get::(), + Some(&ParentSync(None)) + ); + + // 2. make sure that the parent has been removed on the receiver side, and that ParentSync has been updated + assert_eq!( + stepper + .client_app + .world + .entity_mut(client_parent) + .get::(), + Some(&ParentSync(None)) + ); + assert_eq!( + stepper + .client_app + .world + .entity_mut(client_parent) + .get::(), + None, + ); + assert!(stepper + .client_app + .world + .entity_mut(client_grandparent) + .get::() + .is_none()); + } + + #[test] + fn test_propagate_hierarchy() { + let (mut stepper, grandparent, parent, child) = setup_hierarchy(); + + stepper + .server_app + .world + .entity_mut(grandparent) + .insert(Replicate::default()); + + stepper.frame_step(); + stepper.frame_step(); + + // 1. check that the parent and child have been replicated + let client_grandparent = stepper + .client_app + .world + .query_filtered::>() + .get_single(&stepper.client_app.world) + .unwrap(); + let client_parent = stepper + .client_app + .world + .query_filtered::>() + .get_single(&stepper.client_app.world) + .unwrap(); + let client_child = stepper + .client_app + .world + .query_filtered::>() + .get_single(&stepper.client_app.world) + .unwrap(); + + // 2. check that the hierarchies have been replicated + assert_eq!( + stepper + .client_app + .world + .entity_mut(client_parent) + .get::() + .unwrap() + .deref(), + &client_grandparent + ); + assert_eq!( + stepper + .client_app + .world + .entity_mut(client_child) + .get::() + .unwrap() + .deref(), + &client_parent + ); + + // 3. check that the replication group has been set correctly + assert_eq!( + stepper + .server_app + .world + .entity_mut(client_parent) + .get::(), + Some(&Replicate { + replication_group: ReplicationGroup::new_id(grandparent.to_bits()), + ..Default::default() + }) + ); + assert_eq!( + stepper + .server_app + .world + .entity_mut(client_child) + .get::(), + Some(&Replicate { + replication_group: ReplicationGroup::new_id(grandparent.to_bits()), + ..Default::default() + }) + ); + } +} diff --git a/lightyear/src/shared/replication/mod.rs b/lightyear/src/shared/replication/mod.rs index 5cf591ab2..28251c6a5 100644 --- a/lightyear/src/shared/replication/mod.rs +++ b/lightyear/src/shared/replication/mod.rs @@ -3,22 +3,24 @@ use std::hash::Hash; use anyhow::Result; use bevy::ecs::component::Tick as BevyTick; +use bevy::ecs::entity::EntityHashMap; use bevy::prelude::{Component, Entity, Resource}; use bevy::reflect::Map; -use bevy::utils::{EntityHashMap, HashSet}; +use bevy::utils::HashSet; use serde::{Deserialize, Serialize}; use crate::_reexport::{ComponentProtocol, ComponentProtocolKind}; use crate::channel::builder::Channel; use crate::connection::netcode::ClientId; use crate::packet::message::MessageId; -use crate::prelude::{EntityMapper, MapEntities, NetworkTarget, Tick}; +use crate::prelude::{NetworkTarget, Tick}; use crate::protocol::Protocol; use crate::shared::replication::components::{Replicate, ReplicationGroupId}; pub mod components; pub mod entity_map; +pub(crate) mod hierarchy; pub(crate) mod receive; pub(crate) mod send; pub mod systems; @@ -177,7 +179,7 @@ pub trait ReplicationSend: Resource { /// But the receiving systems might expect both components to be present at the same time. fn buffer_replication_messages(&mut self, tick: Tick, bevy_tick: BevyTick) -> Result<()>; - fn get_mut_replicate_component_cache(&mut self) -> &mut EntityHashMap>; + fn get_mut_replicate_component_cache(&mut self) -> &mut EntityHashMap>; } #[cfg(test)] diff --git a/lightyear/src/shared/replication/receive.rs b/lightyear/src/shared/replication/receive.rs index 05920d628..fea95e17e 100644 --- a/lightyear/src/shared/replication/receive.rs +++ b/lightyear/src/shared/replication/receive.rs @@ -3,15 +3,16 @@ use std::collections::BTreeMap; use std::iter::Extend; use anyhow::Context; +use bevy::ecs::entity::EntityHash; use bevy::prelude::{DespawnRecursiveExt, Entity, World}; use bevy::reflect::Reflect; use bevy::utils::petgraph::data::ElementIterator; -use bevy::utils::{EntityHashMap, HashSet}; +use bevy::utils::HashSet; use tracing::{debug, error, trace, trace_span, warn}; use crate::packet::message::MessageId; use crate::prelude::client::Confirmed; -use crate::prelude::{MapEntities, Tick}; +use crate::prelude::{LightyearMapEntities, Tick}; use crate::protocol::component::ComponentProtocol; use crate::protocol::component::{ComponentBehaviour, ComponentKindBehaviour}; use crate::protocol::Protocol; @@ -23,6 +24,10 @@ use super::{ EntityActionMessage, EntityUpdatesMessage, ReplicationMessage, ReplicationMessageData, }; +type EntityHashMap = hashbrown::HashMap; + +type EntityHashSet = hashbrown::HashSet; + pub(crate) struct ReplicationReceiver { /// Map between local and remote entities. (used mostly on client because it's when we receive entity updates) pub remote_entity_map: RemoteEntityMap, @@ -265,7 +270,7 @@ impl ReplicationReceiver

{ debug!(remote_entity = ?entity, ?kinds, "Received InsertComponent"); for mut component in actions.insert { // map any entities inside the component - component.map_entities(Box::new(&self.remote_entity_map)); + component.map_entities(&mut self.remote_entity_map); // TODO: figure out what to do with tick here events.push_insert_component( local_entity_mut.id(), @@ -302,7 +307,7 @@ impl ReplicationReceiver

{ debug!(remote_entity = ?entity, ?kinds, "Received UpdateComponent"); for mut component in actions.updates { // map any entities inside the component - component.map_entities(Box::new(&self.remote_entity_map)); + component.map_entities(&mut self.remote_entity_map); events.push_update_component( local_entity_mut.id(), (&component).into(), @@ -322,7 +327,7 @@ impl ReplicationReceiver

{ { for mut component in components { // map any entities inside the component - component.map_entities(Box::new(&self.remote_entity_map)); + component.map_entities(&mut self.remote_entity_map); events.push_update_component( local_entity.id(), (&component).into(), diff --git a/lightyear/src/shared/replication/send.rs b/lightyear/src/shared/replication/send.rs index 0bf47b7d6..e63252adf 100644 --- a/lightyear/src/shared/replication/send.rs +++ b/lightyear/src/shared/replication/send.rs @@ -3,15 +3,16 @@ use std::iter::Extend; use anyhow::Context; use bevy::ecs::component::Tick as BevyTick; +use bevy::ecs::entity::EntityHash; use bevy::prelude::{Entity, Reflect}; use bevy::utils::petgraph::data::ElementIterator; -use bevy::utils::{EntityHashMap, HashMap, HashSet}; +use bevy::utils::{hashbrown, HashMap, HashSet}; use crossbeam_channel::Receiver; use tracing::{debug, error, trace, warn}; use crate::_reexport::{EntityActionsChannel, EntityUpdatesChannel, FromType}; use crate::packet::message::MessageId; -use crate::prelude::{MapEntities, ShouldBePredicted, Tick}; +use crate::prelude::{ShouldBePredicted, Tick}; use crate::protocol::channel::ChannelKind; use crate::protocol::component::ComponentProtocol; use crate::protocol::component::{ComponentBehaviour, ComponentKindBehaviour}; @@ -20,6 +21,10 @@ use crate::shared::replication::components::{Replicate, ReplicationGroupId}; use super::{EntityActionMessage, EntityActions, EntityUpdatesMessage, ReplicationMessageData}; +type EntityHashMap = hashbrown::HashMap; + +type EntityHashSet = hashbrown::HashSet; + pub(crate) struct ReplicationSender { // TODO: this is unused by server-send, should we just move it to client-connection? // in general, we should have some parts of replication-sender/receiver that are shared across all connections! @@ -37,12 +42,13 @@ pub(crate) struct ReplicationSender { /// are being buffered individually but we want to group them inside a message pub pending_actions: EntityHashMap< ReplicationGroupId, - HashMap>, + EntityHashMap>, >, - pub pending_updates: EntityHashMap>>, + pub pending_updates: + EntityHashMap>>, // Set of unique components for each entity, to avoid sending multiple updates/inserts for the same component pub pending_unique_components: - EntityHashMap>>, + EntityHashMap>>, /// Buffer to so that we have an ordered receiver per group pub group_channels: EntityHashMap, diff --git a/lightyear/src/shared/sets.rs b/lightyear/src/shared/sets.rs index 077ddfc9b..95e59a9ef 100644 --- a/lightyear/src/shared/sets.rs +++ b/lightyear/src/shared/sets.rs @@ -53,9 +53,6 @@ pub enum MainSet { /// SystemSet that run during the FixedUpdate schedule #[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum FixedUpdateSet { - /// System that runs at the very start of the FixedUpdate schedule to increment the ticks + /// System that runs in the FixedFirst schedule to increment the ticks TickUpdate, - /// Main loop (with physics, game logic) during FixedUpdate - Main, - MainFlush, } diff --git a/lightyear/src/shared/tick_manager.rs b/lightyear/src/shared/tick_manager.rs index edabec254..0a21e752b 100644 --- a/lightyear/src/shared/tick_manager.rs +++ b/lightyear/src/shared/tick_manager.rs @@ -36,14 +36,11 @@ impl Plugin for TickManagerPlugin { .insert_resource(TickManager::from_config(self.config.clone())) // SYSTEMS .add_systems( - FixedUpdate, - ( - increment_tick - .in_set(FixedUpdateSet::TickUpdate) - // run if there is no rollback resource, or if we are not in rollback - .run_if(not(resource_exists::()).or_else(not(is_in_rollback))), - apply_deferred.in_set(FixedUpdateSet::MainFlush), - ), + FixedFirst, + (increment_tick + .in_set(FixedUpdateSet::TickUpdate) + // run if there is no rollback resource, or if we are not in rollback + .run_if(not(resource_exists::).or_else(not(is_in_rollback))),), ); } } diff --git a/lightyear/src/shared/time_manager.rs b/lightyear/src/shared/time_manager.rs index 366f00d2d..c1961105e 100644 --- a/lightyear/src/shared/time_manager.rs +++ b/lightyear/src/shared/time_manager.rs @@ -12,7 +12,7 @@ This module contains some helper functions to compute the difference between two use std::fmt::Formatter; use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; -use bevy::app::{App, RunFixedUpdateLoop}; +use bevy::app::{App, RunFixedMainLoop}; use bevy::prelude::{IntoSystemConfigs, Plugin, Res, ResMut, Resource, Time, Timer, TimerMode}; use bevy::time::Fixed; use bevy::utils::Duration; @@ -43,14 +43,14 @@ impl Plugin for TimePlugin { app.insert_resource(TimeManager::new(self.send_interval)); // SYSTEMS app.add_systems( - RunFixedUpdateLoop, - update_overstep.after(bevy::time::run_fixed_update_schedule), + RunFixedMainLoop, + update_overstep.after(bevy::time::run_fixed_main_schedule), ); } } fn update_overstep(mut time_manager: ResMut, fixed_time: Res>) { - time_manager.update_overstep(fixed_time.overstep_percentage()); + time_manager.update_overstep(fixed_time.overstep_fraction()); } #[derive(Resource)] diff --git a/lightyear/src/tests/integration/tick_wrapping.rs b/lightyear/src/tests/integration/tick_wrapping.rs index b3d9c1ef9..92a7beb02 100644 --- a/lightyear/src/tests/integration/tick_wrapping.rs +++ b/lightyear/src/tests/integration/tick_wrapping.rs @@ -58,12 +58,10 @@ fn test_sync_after_tick_wrap() { .set_tick_to(new_tick); stepper.client_app.add_systems( - FixedUpdate, + FixedPreUpdate, press_input.in_set(InputSystemSet::BufferInputs), ); - stepper - .server_app - .add_systems(FixedUpdate, increment.in_set(FixedUpdateSet::Main)); + stepper.server_app.add_systems(FixedUpdate, increment); let server_entity = stepper .server_app @@ -152,12 +150,10 @@ fn test_sync_after_tick_half_wrap() { .set_tick_to(new_tick); stepper.client_app.add_systems( - FixedUpdate, + FixedPreUpdate, press_input.in_set(InputSystemSet::BufferInputs), ); - stepper - .server_app - .add_systems(FixedUpdate, increment.in_set(FixedUpdateSet::Main)); + stepper.server_app.add_systems(FixedUpdate, increment); let server_entity = stepper .server_app diff --git a/lightyear/src/tests/protocol.rs b/lightyear/src/tests/protocol.rs index 4121ae8e1..4136211d1 100644 --- a/lightyear/src/tests/protocol.rs +++ b/lightyear/src/tests/protocol.rs @@ -1,5 +1,5 @@ -use bevy::prelude::{default, Component, Entity, Reflect}; -use bevy::utils::EntityHashSet; +use bevy::ecs::entity::MapEntities; +use bevy::prelude::{default, Component, Entity, EntityMapper, Reflect}; use cfg_if::cfg_if; use derive_more::{Add, Mul}; use std::ops::Mul; @@ -43,13 +43,9 @@ pub struct Component3(pub f32); #[message(custom_map)] pub struct Component4(pub Entity); -impl<'a> MapEntities<'a> for Component4 { - fn map_entities(&mut self, entity_mapper: Box) { - self.0.map_entities(entity_mapper); - } - - fn entities(&self) -> EntityHashSet { - EntityHashSet::from_iter(vec![self.0]) +impl LightyearMapEntities for Component4 { + fn map_entities(&mut self, entity_mapper: &mut M) { + self.0 = entity_mapper.map_entity(self.0); } } diff --git a/lightyear/src/transport/io.rs b/lightyear/src/transport/io.rs index a0aa878cc..f1b6c74f4 100644 --- a/lightyear/src/transport/io.rs +++ b/lightyear/src/transport/io.rs @@ -1,7 +1,7 @@ //! Wrapper around a transport, that can perform additional transformations such as //! bandwidth monitoring or compression use bevy::app::{App, Plugin}; -use bevy::diagnostic::{Diagnostic, DiagnosticId, Diagnostics, RegisterDiagnostic}; +use bevy::diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic}; use bevy::prelude::{Real, Res, Resource, Time}; use crossbeam_channel::{Receiver, Sender}; use std::fmt::{Debug, Formatter}; @@ -319,18 +319,14 @@ pub struct IoDiagnosticsPlugin; impl IoDiagnosticsPlugin { /// How many bytes do we receive per second - pub const BYTES_IN: DiagnosticId = - DiagnosticId::from_u128(272724337309910272967747412065116587937); + pub const BYTES_IN: DiagnosticPath = DiagnosticPath::const_new("KB received per second"); /// How many bytes do we send per second - pub const BYTES_OUT: DiagnosticId = - DiagnosticId::from_u128(55304262539591435450305383702521958293); + pub const BYTES_OUT: DiagnosticPath = DiagnosticPath::const_new("KB sent per second"); /// How many bytes do we receive per second - pub const PACKETS_IN: DiagnosticId = - DiagnosticId::from_u128(183580771279032958450263611989577449811); + pub const PACKETS_IN: DiagnosticPath = DiagnosticPath::const_new("packets received per second"); /// How many bytes do we send per second - pub const PACKETS_OUT: DiagnosticId = - DiagnosticId::from_u128(314668465487051049643062180884137694217); + pub const PACKETS_OUT: DiagnosticPath = DiagnosticPath::const_new("packets sent per second"); /// Max diagnostic history length. pub const DIAGNOSTIC_HISTORY_LEN: usize = 60; @@ -344,16 +340,16 @@ impl IoDiagnosticsPlugin { if delta_seconds == 0.0 { return; } - diagnostics.add_measurement(Self::BYTES_IN, || { + diagnostics.add_measurement(&Self::BYTES_IN, || { (stats.bytes_received as f64 / 1000.0) / delta_seconds }); - diagnostics.add_measurement(Self::BYTES_OUT, || { + diagnostics.add_measurement(&Self::BYTES_OUT, || { (stats.bytes_sent as f64 / 1000.0) / delta_seconds }); - diagnostics.add_measurement(Self::PACKETS_IN, || { + diagnostics.add_measurement(&Self::PACKETS_IN, || { stats.packets_received as f64 / delta_seconds }); - diagnostics.add_measurement(Self::PACKETS_OUT, || { + diagnostics.add_measurement(&Self::PACKETS_OUT, || { stats.packets_sent as f64 / delta_seconds }); *stats = IoStats::default() @@ -362,26 +358,22 @@ impl IoDiagnosticsPlugin { impl Plugin for IoDiagnosticsPlugin { fn build(&self, app: &mut App) { - app.register_diagnostic(Diagnostic::new( - IoDiagnosticsPlugin::BYTES_IN, - "KB received per second", - IoDiagnosticsPlugin::DIAGNOSTIC_HISTORY_LEN, - )); - app.register_diagnostic(Diagnostic::new( - IoDiagnosticsPlugin::BYTES_OUT, - "KB sent per second", - IoDiagnosticsPlugin::DIAGNOSTIC_HISTORY_LEN, - )); - app.register_diagnostic(Diagnostic::new( - IoDiagnosticsPlugin::PACKETS_IN, - "packets received per second", - IoDiagnosticsPlugin::DIAGNOSTIC_HISTORY_LEN, - )); - app.register_diagnostic(Diagnostic::new( - IoDiagnosticsPlugin::PACKETS_OUT, - "packets sent per second", - IoDiagnosticsPlugin::DIAGNOSTIC_HISTORY_LEN, - )); + app.register_diagnostic( + Diagnostic::new(IoDiagnosticsPlugin::BYTES_IN) + .with_max_history_length(IoDiagnosticsPlugin::DIAGNOSTIC_HISTORY_LEN), + ); + app.register_diagnostic( + Diagnostic::new(IoDiagnosticsPlugin::BYTES_OUT) + .with_max_history_length(IoDiagnosticsPlugin::DIAGNOSTIC_HISTORY_LEN), + ); + app.register_diagnostic( + Diagnostic::new(IoDiagnosticsPlugin::PACKETS_IN) + .with_max_history_length(IoDiagnosticsPlugin::DIAGNOSTIC_HISTORY_LEN), + ); + app.register_diagnostic( + Diagnostic::new(IoDiagnosticsPlugin::PACKETS_OUT) + .with_max_history_length(IoDiagnosticsPlugin::DIAGNOSTIC_HISTORY_LEN), + ); } } diff --git a/lightyear/src/utils/bevy.rs b/lightyear/src/utils/bevy.rs index 27633bb28..12188d97e 100644 --- a/lightyear/src/utils/bevy.rs +++ b/lightyear/src/utils/bevy.rs @@ -1,13 +1,13 @@ //! Implement lightyear traits for some common bevy types use crate::_reexport::LinearInterpolator; use crate::client::components::{ComponentSyncMode, LerpFn, SyncComponent}; +use bevy::ecs::entity::{EntityHashSet, MapEntities}; use bevy::hierarchy::Parent; -use bevy::prelude::{Entity, Transform}; -use bevy::utils::EntityHashSet; +use bevy::prelude::{Children, Entity, EntityMapper, Transform}; use std::ops::Mul; use tracing::{info, trace}; -use crate::prelude::{EntityMapper, MapEntities, Message, Named}; +use crate::prelude::{LightyearMapEntities, Message, Named}; // TODO: add implementations for Parent and Children @@ -38,12 +38,8 @@ impl LerpFn for TransformLinearInterpolation { } } -impl<'a> MapEntities<'a> for Transform { - fn map_entities(&mut self, entity_mapper: Box) {} - - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() - } +impl LightyearMapEntities for Transform { + fn map_entities(&mut self, entity_mapper: &mut M) {} } cfg_if::cfg_if! { @@ -53,26 +49,16 @@ cfg_if::cfg_if! { const NAME: &'static str = "Color"; } - impl<'a> MapEntities<'a> for Color { - fn map_entities(&mut self, entity_mapper: Box) {} - - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() - } + impl LightyearMapEntities for Color { + fn map_entities(&mut self, entity_mapper: &mut M) {} } - impl Named for Visibility { const NAME: &'static str = "Visibility"; } - impl<'a> MapEntities<'a> for Visibility { - fn map_entities(&mut self, entity_mapper: Box) {} - - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() - } + impl LightyearMapEntities for Visibility { + fn map_entities(&mut self, entity_mapper: &mut M) {} } - } } diff --git a/lightyear/src/utils/bevy_xpbd_2d.rs b/lightyear/src/utils/bevy_xpbd_2d.rs index cc9502426..137b85e7a 100644 --- a/lightyear/src/utils/bevy_xpbd_2d.rs +++ b/lightyear/src/utils/bevy_xpbd_2d.rs @@ -1,13 +1,12 @@ //! Implement lightyear traits for some common bevy types use crate::_reexport::LinearInterpolator; use crate::client::components::{ComponentSyncMode, LerpFn, SyncComponent}; -use bevy::prelude::Entity; -use bevy::utils::EntityHashSet; +use bevy::prelude::{Entity, EntityMapper}; use bevy_xpbd_2d::components::*; use std::ops::{Add, Mul}; use tracing::{info, trace}; -use crate::prelude::{EntityMapper, MapEntities, Message, Named}; +use crate::prelude::{LightyearMapEntities, Message, Named}; pub mod position { use super::*; @@ -31,12 +30,8 @@ pub mod position { } } - impl<'a> MapEntities<'a> for Position { - fn map_entities(&mut self, entity_mapper: Box) {} - - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() - } + impl LightyearMapEntities for Position { + fn map_entities(&mut self, entity_mapper: &mut M) {} } } pub use position::*; @@ -71,12 +66,8 @@ pub mod rotation { } } - impl<'a> MapEntities<'a> for Rotation { - fn map_entities(&mut self, entity_mapper: Box) {} - - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() - } + impl LightyearMapEntities for Rotation { + fn map_entities(&mut self, entity_mapper: &mut M) {} } } pub use rotation::*; @@ -102,13 +93,8 @@ pub mod linear_velocity { res } } - - impl<'a> MapEntities<'a> for LinearVelocity { - fn map_entities(&mut self, entity_mapper: Box) {} - - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() - } + impl LightyearMapEntities for LinearVelocity { + fn map_entities(&mut self, entity_mapper: &mut M) {} } } pub use linear_velocity::*; @@ -135,12 +121,8 @@ pub mod angular_velocity { } } - impl<'a> MapEntities<'a> for AngularVelocity { - fn map_entities(&mut self, entity_mapper: Box) {} - - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() - } + impl LightyearMapEntities for AngularVelocity { + fn map_entities(&mut self, entity_mapper: &mut M) {} } } pub use angular_velocity::*; @@ -153,12 +135,8 @@ pub mod mass { impl Named for Mass { const NAME: &'static str = "Mass"; } - impl<'a> MapEntities<'a> for Mass { - fn map_entities(&mut self, entity_mapper: Box) {} - - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() - } + impl LightyearMapEntities for Mass { + fn map_entities(&mut self, entity_mapper: &mut M) {} } } pub use mass::*; diff --git a/macros/Cargo.toml b/macros/Cargo.toml index e7c211156..26972acc1 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lightyear_macros" -version = "0.9.1" +version = "0.10.0" authors = ["Charles Bournhonesque "] edition = "2021" rust-version = "1.65" @@ -31,8 +31,8 @@ uuid = { version = "1.6.1", features = ["v4"] } [dev-dependencies] lightyear = { path = "../lightyear" } -leafwing-input-manager = { version = "0.11.2" } +leafwing-input-manager = { version = "0.13" } enum-as-inner = "0.6.0" -bevy = { version = "0.12", default-features = false } +bevy = { version = "0.13", default-features = false } enum_delegate = "0.2" derive_more = { version = "0.99", features = ["add", "mul"] } diff --git a/macros/src/component.rs b/macros/src/component.rs index 82bba65c6..df310e6ab 100644 --- a/macros/src/component.rs +++ b/macros/src/component.rs @@ -127,6 +127,10 @@ pub fn component_protocol_impl( // #[sync(external)] ShouldBeInterpolated(ShouldBeInterpolated) }); + input.variants.push(parse_quote! { + // #[sync(external)] + ParentSync(ParentSync) + }); #[cfg(feature = "leafwing")] for i in 1..3 { let variant = Ident::new(&format!("ActionState{}", i), Span::call_site()); @@ -199,8 +203,9 @@ pub fn component_protocol_impl( use #shared_crate_name::_reexport::*; use #shared_crate_name::prelude::*; use #shared_crate_name::prelude::client::*; + use bevy::ecs::entity::{EntityHashSet, MapEntities, EntityMapper}; use bevy::prelude::{App, Entity, IntoSystemConfigs, EntityWorldMut, World}; - use bevy::utils::{EntityHashMap, EntityHashSet, HashMap}; + use bevy::utils::HashMap; use std::any::TypeId; use #shared_crate_name::shared::events::{ComponentInsertEvent, ComponentRemoveEvent, ComponentUpdateEvent}; #[cfg(feature = "leafwing")] @@ -590,7 +595,7 @@ fn delegate_method(input: &ItemEnum, enum_kind_name: &Ident) -> TokenStream { let ident = &variant.ident; map_entities_body = quote! { #map_entities_body - Self::#ident(ref mut x) => x.map_entities(entity_mapper), + Self::#ident(ref mut x) => LightyearMapEntities::map_entities(x, entity_mapper), }; entities_body = quote! { #entities_body @@ -600,22 +605,15 @@ fn delegate_method(input: &ItemEnum, enum_kind_name: &Ident) -> TokenStream { // TODO: make it work with generics (generic components) quote! { - impl<'a> MapEntities<'a> for #enum_name { - fn map_entities(&mut self, entity_mapper: Box) { + impl LightyearMapEntities for #enum_name { + fn map_entities(&mut self, entity_mapper: &mut M) { match self { #map_entities_body } } - fn entities(&self) -> EntityHashSet { - match self { - #entities_body - } - } } - impl<'a> MapEntities<'a> for #enum_kind_name { - fn map_entities(&mut self, entity_mapper: Box) {} - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() + impl LightyearMapEntities for #enum_kind_name { + fn map_entities(&mut self, entity_mapper: &mut M) { } } } diff --git a/macros/src/message.rs b/macros/src/message.rs index f567a8fdc..f5fe63216 100644 --- a/macros/src/message.rs +++ b/macros/src/message.rs @@ -41,7 +41,7 @@ pub fn message_impl( pub mod #module_name { use super::*; use bevy::prelude::*; - use bevy::utils::{EntityHashMap, EntityHashSet}; + use bevy::ecs::entity::{MapEntities, EntityMapper}; use #shared_crate_name::prelude::*; #map_entities_trait @@ -59,29 +59,14 @@ pub fn message_impl( // Add the MapEntities trait for the message. // Need to combine the generics from the message with the generics from the trait fn map_entities_trait(input: &DeriveInput, ident_map: bool) -> TokenStream { - // combined generics - let mut gen_clone = input.generics.clone(); - let lt: LifetimeParam = parse_quote_spanned! {Span::mixed_site() => 'a}; - gen_clone.params.push(GenericParam::from(lt)); - let (impl_generics, _, _) = gen_clone.split_for_impl(); - // type generics - let (_, type_generics, where_clause) = input.generics.split_for_impl(); - - // trait generics (MapEntities) - let mut map_entities_generics = Generics::default(); - let lt: LifetimeParam = parse_quote_spanned! {Span::mixed_site() => 'a}; - map_entities_generics.params.push(GenericParam::from(lt)); - let (_, type_generics_map, _) = map_entities_generics.split_for_impl(); + let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); let struct_name = &input.ident; if ident_map { quote! { - impl #impl_generics MapEntities #type_generics_map for #struct_name #type_generics #where_clause { - fn map_entities(&mut self, entity_mapper: Box) {} - fn entities(&self) -> EntityHashSet { - EntityHashSet::default() - } + impl #impl_generics LightyearMapEntities for #struct_name #type_generics #where_clause { + fn map_entities(&mut self, entity_mapper: &mut M) {} } } } else { @@ -164,7 +149,7 @@ pub fn message_protocol_impl( use super::*; use serde::{Serialize, Deserialize}; use bevy::prelude::{App, Entity, World}; - use bevy::utils::{EntityHashMap, EntityHashSet}; + use bevy::ecs::entity::{MapEntities, EntityMapper}; use #shared_crate_name::_reexport::*; use #shared_crate_name::prelude::*; use #shared_crate_name::shared::systems::events::push_message_events; @@ -376,24 +361,15 @@ fn map_entities_impl(input: &ItemEnum) -> TokenStream { #map_entities_body #enum_name::#ident(ref mut x) => x.map_entities(entity_mapper), }; - entities_body = quote! { - #entities_body - #enum_name::#ident(ref x) => x.entities(), - }; } quote! { - impl<'a> MapEntities<'a> for #enum_name { - fn map_entities(&mut self, entity_mapper: Box) { + impl LightyearMapEntities for #enum_name { + fn map_entities(&mut self, entity_mapper: &mut M) { match self { #map_entities_body } } - fn entities(&self) -> EntityHashSet { - match self { - #entities_body - } - } } } }