diff --git a/examples/send_actions_over_network.rs b/examples/send_actions_over_network.rs index e6db2f61..b76194cd 100644 --- a/examples/send_actions_over_network.rs +++ b/examples/send_actions_over_network.rs @@ -1,164 +1,161 @@ -//! [`ActionDiff`] event streams are minimalistic representations -//! of the action state, intended for serialization and networking -//! While they are less convenient to work with than the complete [`ActionState`], -//! they are much smaller, and can be created from and reconstructed into [`ActionState`] -//! -//! Note that [`ActionState`] can also be serialized and sent directly. -//! This approach will be less bandwidth efficient, but involve less complexity and CPU work. - -use bevy::ecs::event::{Events, ManualEventReader}; -use bevy::input::InputPlugin; -use bevy::prelude::*; -use leafwing_input_manager::action_state::{ActionDiffEvent}; -use leafwing_input_manager::prelude::*; -use leafwing_input_manager::systems::{generate_action_diffs}; - -use std::fmt::Debug; - -#[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)] -enum FpsAction { - MoveLeft, - MoveRight, - Jump, - Shoot, -} - -/// This identifier uniquely identifies entities across the network -#[derive(Component, Clone, PartialEq, Eq, Hash, Debug)] -struct StableId(u64); - -/// Processes an [`Events`] stream of [`ActionDiff`] to update an [`ActionState`] -/// -/// In a real scenario, you would have to map the entities between the server and client world. -/// In this case, we will just use the fact that there is only a single entity. -fn process_action_diffs( - mut action_state_query: Query<&mut ActionState>, - mut action_diff_events: EventReader>, -) { - for action_diff_event in action_diff_events.read() { - if action_diff_event.owner.is_some() { - let mut action_state = action_state_query.get_single_mut().unwrap(); - action_state.apply_diff(&action_diff_event.action_diff); - } - } -} - -fn main() { - // In a real use case, these apps would be running on separate devices. - let mut client_app = App::new(); - - client_app - .add_plugins(MinimalPlugins) - .add_plugins(InputPlugin) - .add_plugins(InputManagerPlugin::::default()) - // Creates an event stream of `ActionDiffs` to send to the server - .add_systems(PostUpdate, generate_action_diffs::) - .add_event::>() - .add_systems(Startup, spawn_player); - - let mut server_app = App::new(); - server_app - .add_plugins(MinimalPlugins) - .add_plugins(InputManagerPlugin::::server()) - .add_event::>() - // Reads in the event stream of `ActionDiffs` to update the `ActionState` - .add_systems(PreUpdate, process_action_diffs::) - // Typically, the rest of this information would synchronized as well - .add_systems(Startup, spawn_player); - - // Starting up the game - client_app.update(); - - // Sending inputs to the client - client_app.send_input(KeyCode::Space); - client_app.send_input(MouseButton::Left); - - // These are converted into actions when the client_app's `Schedule` runs - client_app.update(); - - let mut player_state_query = client_app.world.query::<&ActionState>(); - let player_state = player_state_query.iter(&client_app.world).next().unwrap(); - assert!(player_state.pressed(FpsAction::Jump)); - assert!(player_state.pressed(FpsAction::Shoot)); - - // These events are transferred to the server - let event_reader = - send_events::>(&client_app, &mut server_app, None); - - // The server processes the event stream - server_app.update(); - - // And the actions are pressed on the server! - let mut player_state_query = server_app.world.query::<&ActionState>(); - let player_state = player_state_query.iter(&server_app.world).next().unwrap(); - assert!(player_state.pressed(FpsAction::Jump)); - assert!(player_state.pressed(FpsAction::Shoot)); - - // If we wait a tick, the buttons will be released - client_app.reset_inputs(); - client_app.update(); - let mut player_state_query = client_app.world.query::<&ActionState>(); - let player_state = player_state_query.iter(&client_app.world).next().unwrap(); - assert!(player_state.released(FpsAction::Jump)); - assert!(player_state.released(FpsAction::Shoot)); - - // Sending over the new `ActionDiff` event stream, - // we can see that the actions are now released on the server too - let _event_reader = send_events::>( - &client_app, - &mut server_app, - Some(event_reader), - ); - - server_app.update(); - - let mut player_state_query = server_app.world.query::<&ActionState>(); - let player_state = player_state_query.iter(&server_app.world).next().unwrap(); - assert!(player_state.released(FpsAction::Jump)); - assert!(player_state.released(FpsAction::Shoot)); -} - -#[derive(Component)] -struct Player; - -fn spawn_player(mut commands: Commands) { - use FpsAction::*; - use KeyCode::*; - - commands - .spawn(InputManagerBundle { - input_map: InputMap::new([(W, MoveLeft), (D, MoveRight), (Space, Jump)]) - .insert(MouseButton::Left, Shoot) - .build(), - ..default() - }) - .insert(Player); -} - -/// A simple mock network interface that copies a set of events from the client to the server -/// -/// The events are sent directly; -/// in real applications they would be serialized to a networking protocol instead. -/// -/// The [`ManualEventReader`] returned must be reused in order to avoid double-sending events -#[must_use] -fn send_events( - client_app: &App, - server_app: &mut App, - reader: Option>, -) -> ManualEventReader { - let client_events: &Events = client_app.world.resource(); - let mut server_events: Mut> = server_app.world.resource_mut(); - - // Get an event reader, one way or another - let mut reader = reader.unwrap_or_else(|| client_events.get_reader()); - - // Push the clients' events to the server - for client_event in reader.read(client_events) { - dbg!(client_event.clone()); - server_events.send(client_event.clone()); - } - - // Return the event reader for reuse - reader -} +//! [`ActionDiff`] event streams are minimalistic representations +//! of the action state, intended for serialization and networking +//! While they are less convenient to work with than the complete [`ActionState`], +//! they are much smaller, and can be created from and reconstructed into [`ActionState`] +//! +//! Note that [`ActionState`] can also be serialized and sent directly. +//! This approach will be less bandwidth efficient, but involve less complexity and CPU work. + +use bevy::ecs::event::{Events, ManualEventReader}; +use bevy::input::InputPlugin; +use bevy::prelude::*; +use leafwing_input_manager::action_state::ActionDiffEvent; +use leafwing_input_manager::prelude::*; +use leafwing_input_manager::systems::generate_action_diffs; + +use std::fmt::Debug; + +#[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)] +enum FpsAction { + MoveLeft, + MoveRight, + Jump, + Shoot, +} + +/// This identifier uniquely identifies entities across the network +#[derive(Component, Clone, PartialEq, Eq, Hash, Debug)] +struct StableId(u64); + +/// Processes an [`Events`] stream of [`ActionDiff`] to update an [`ActionState`] +/// +/// In a real scenario, you would have to map the entities between the server and client world. +/// In this case, we will just use the fact that there is only a single entity. +fn process_action_diffs( + mut action_state_query: Query<&mut ActionState>, + mut action_diff_events: EventReader>, +) { + for action_diff_event in action_diff_events.read() { + if action_diff_event.owner.is_some() { + let mut action_state = action_state_query.get_single_mut().unwrap(); + action_state.apply_diff(&action_diff_event.action_diff); + } + } +} + +fn main() { + // In a real use case, these apps would be running on separate devices. + let mut client_app = App::new(); + + client_app + .add_plugins(MinimalPlugins) + .add_plugins(InputPlugin) + .add_plugins(InputManagerPlugin::::default()) + // Creates an event stream of `ActionDiffs` to send to the server + .add_systems(PostUpdate, generate_action_diffs::) + .add_event::>() + .add_systems(Startup, spawn_player); + + let mut server_app = App::new(); + server_app + .add_plugins(MinimalPlugins) + .add_plugins(InputManagerPlugin::::server()) + .add_event::>() + // Reads in the event stream of `ActionDiffs` to update the `ActionState` + .add_systems(PreUpdate, process_action_diffs::) + // Typically, the rest of this information would synchronized as well + .add_systems(Startup, spawn_player); + + // Starting up the game + client_app.update(); + + // Sending inputs to the client + client_app.send_input(KeyCode::Space); + client_app.send_input(MouseButton::Left); + + // These are converted into actions when the client_app's `Schedule` runs + client_app.update(); + + let mut player_state_query = client_app.world.query::<&ActionState>(); + let player_state = player_state_query.iter(&client_app.world).next().unwrap(); + assert!(player_state.pressed(FpsAction::Jump)); + assert!(player_state.pressed(FpsAction::Shoot)); + + // These events are transferred to the server + let event_reader = + send_events::>(&client_app, &mut server_app, None); + + // The server processes the event stream + server_app.update(); + + // And the actions are pressed on the server! + let mut player_state_query = server_app.world.query::<&ActionState>(); + let player_state = player_state_query.iter(&server_app.world).next().unwrap(); + assert!(player_state.pressed(FpsAction::Jump)); + assert!(player_state.pressed(FpsAction::Shoot)); + + // If we wait a tick, the buttons will be released + client_app.reset_inputs(); + client_app.update(); + let mut player_state_query = client_app.world.query::<&ActionState>(); + let player_state = player_state_query.iter(&client_app.world).next().unwrap(); + assert!(player_state.released(FpsAction::Jump)); + assert!(player_state.released(FpsAction::Shoot)); + + // Sending over the new `ActionDiff` event stream, + // we can see that the actions are now released on the server too + let _event_reader = + send_events::>(&client_app, &mut server_app, Some(event_reader)); + + server_app.update(); + + let mut player_state_query = server_app.world.query::<&ActionState>(); + let player_state = player_state_query.iter(&server_app.world).next().unwrap(); + assert!(player_state.released(FpsAction::Jump)); + assert!(player_state.released(FpsAction::Shoot)); +} + +#[derive(Component)] +struct Player; + +fn spawn_player(mut commands: Commands) { + use FpsAction::*; + use KeyCode::*; + + commands + .spawn(InputManagerBundle { + input_map: InputMap::new([(W, MoveLeft), (D, MoveRight), (Space, Jump)]) + .insert(MouseButton::Left, Shoot) + .build(), + ..default() + }) + .insert(Player); +} + +/// A simple mock network interface that copies a set of events from the client to the server +/// +/// The events are sent directly; +/// in real applications they would be serialized to a networking protocol instead. +/// +/// The [`ManualEventReader`] returned must be reused in order to avoid double-sending events +#[must_use] +fn send_events( + client_app: &App, + server_app: &mut App, + reader: Option>, +) -> ManualEventReader { + let client_events: &Events = client_app.world.resource(); + let mut server_events: Mut> = server_app.world.resource_mut(); + + // Get an event reader, one way or another + let mut reader = reader.unwrap_or_else(|| client_events.get_reader()); + + // Push the clients' events to the server + for client_event in reader.read(client_events) { + dbg!(client_event.clone()); + server_events.send(client_event.clone()); + } + + // Return the event reader for reuse + reader +} diff --git a/src/action_state.rs b/src/action_state.rs index 00951f8e..2fa19415 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -507,31 +507,21 @@ impl ActionState { /// This lets you reconstruct an [`ActionState`] from a stream of [`ActionDiff`]s pub fn apply_diff(&mut self, action_diff: &ActionDiff) { match action_diff { - ActionDiff::Pressed { - action, - } => { + ActionDiff::Pressed { action } => { self.press(action.clone()); self.action_data_mut(action.clone()).value = 1.; } - ActionDiff::Released { - action, - } => { + ActionDiff::Released { action } => { self.release(action.clone()); let action_data = self.action_data_mut(action.clone()); action_data.value = 0.; action_data.axis_pair = None; } - ActionDiff::ValueChanged { - action, - value, - } => { + ActionDiff::ValueChanged { action, value } => { self.press(action.clone()); self.action_data_mut(action.clone()).value = *value; } - ActionDiff::AxisPairChanged { - action, - axis_pair, - } => { + ActionDiff::AxisPairChanged { action, axis_pair } => { self.press(action.clone()); let action_data = self.action_data_mut(action.clone()); action_data.axis_pair = Some(DualAxisData::from_xy(*axis_pair)); @@ -803,7 +793,6 @@ pub struct ActionDiffEvent { pub action_diff: ActionDiff, } - /// Stores presses and releases of buttons without timing information /// /// These are typically accessed using the `Events` resource. diff --git a/src/systems.rs b/src/systems.rs index 2d363846..4c88eeda 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -25,11 +25,11 @@ use bevy::{ utils::{HashMap, Instant}, }; +use crate::action_state::ActionDiffEvent; #[cfg(feature = "ui")] use bevy::ui::Interaction; #[cfg(feature = "egui")] use bevy_egui::EguiContexts; -use crate::action_state::ActionDiffEvent; /// Advances actions timer. /// @@ -193,11 +193,14 @@ pub fn generate_action_diffs( mut previous_axis_pairs: Local, Vec2>>>, ) { // we use None to represent the global ActionState - let action_state_iter = action_state_query.iter().map(|(entity, action_state)| { - (Some(entity), action_state) - }).chain(action_state.as_ref().map(|action_state| { - (None, action_state.as_ref()) - })); + let action_state_iter = action_state_query + .iter() + .map(|(entity, action_state)| (Some(entity), action_state)) + .chain( + action_state + .as_ref() + .map(|action_state| (None, action_state.as_ref())), + ); for (maybe_entity, action_state) in action_state_iter { for action in action_state.get_just_pressed() { match action_state.action_data(action.clone()).axis_pair { @@ -207,7 +210,7 @@ pub fn generate_action_diffs( action_diff: ActionDiff::AxisPairChanged { action: action.clone(), axis_pair: axis_pair.into(), - } + }, }); previous_axis_pairs .raw_entry_mut() @@ -218,8 +221,6 @@ pub fn generate_action_diffs( } None => { let value = action_state.value(action.clone()); - // NOTE: this seems strange; we detect if the button is Pressed/ValueChanged just based on the value? - // what if we have a 'Value' type button that has a value of 1.0? action_diffs.send(ActionDiffEvent { owner: maybe_entity, action_diff: if value == 1. { @@ -231,7 +232,7 @@ pub fn generate_action_diffs( action: action.clone(), value, } - } + }, }); previous_values .raw_entry_mut() @@ -257,10 +258,11 @@ pub fn generate_action_diffs( } action_diffs.send(ActionDiffEvent { owner: maybe_entity, - action_diff: ActionDiff::AxisPairChanged { + action_diff: ActionDiff::AxisPairChanged { action: action.clone(), axis_pair: axis_pair.into(), - }}); + }, + }); previous_axis_pairs.insert(maybe_entity, axis_pair.xy()); } None => { @@ -277,7 +279,7 @@ pub fn generate_action_diffs( action_diff: ActionDiff::ValueChanged { action: action.clone(), value, - } + }, }); previous_values.insert(maybe_entity, value); } @@ -288,7 +290,7 @@ pub fn generate_action_diffs( owner: maybe_entity, action_diff: ActionDiff::Released { action: action.clone(), - } + }, }); if let Some(previous_axes) = previous_axis_pairs.get_mut(&action) { previous_axes.remove(&maybe_entity); @@ -300,7 +302,6 @@ pub fn generate_action_diffs( } } - /// Release all inputs if the [`ToggleActions`] resource exists and its `enabled` field is false. pub fn release_on_disable( mut query: Query<&mut ActionState>,