diff --git a/examples/send_actions_over_network.rs b/examples/send_actions_over_network.rs index 8886946b..1625f8f6 100644 --- a/examples/send_actions_over_network.rs +++ b/examples/send_actions_over_network.rs @@ -1,151 +1,164 @@ -//! [`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::ActionDiff; -use leafwing_input_manager::prelude::*; -use leafwing_input_manager::systems::{generate_action_diffs, process_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); - -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() - }) - // This identifier must match on both the client and server - // and be unique between players - .insert(StableId(76)) - .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_diff_event + .action_diffs + .iter() + .for_each(|diff| action_state.apply_diff(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 97d8ee37..088912ec 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -501,6 +501,34 @@ impl ActionState { pub fn previous_duration(&self, action: A) -> Duration { self.action_data[action.index()].timing.previous_duration } + + /// Applies an [`ActionDiff`] (usually received over the network) to the [`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 } => { + self.press(action.clone()); + self.action_data_mut(action.clone()).value = 1.; + } + 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 } => { + self.press(action.clone()); + self.action_data_mut(action.clone()).value = *value; + } + 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)); + action_data.value = axis_pair.length(); + } + }; + } } impl Default for ActionState { @@ -752,35 +780,41 @@ impl Timing { } } +/// Will store an `ActionDiff` as well as what generated it (either an Entity, or nothing if the +/// input actions are represented by a `Resource`) +/// +/// These are typically accessed using the `Events` resource. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Event)] +pub struct ActionDiffEvent { + /// If some: the entity that has the `ActionState` component + /// If none: `ActionState` is a Resource, not a component + pub owner: Option, + /// The `ActionDiff` that was generated + pub action_diffs: Vec>, +} + /// Stores presses and releases of buttons without timing information /// -/// These are typically accessed using the `Events` resource. +/// These are typically accessed using the `Events` resource. /// Uses a minimal storage format, in order to facilitate transport over the network. /// -/// `ID` should be a component type that stores a unique stable identifier for the entity -/// that stores the corresponding [`ActionState`]. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Event)] -pub enum ActionDiff { +/// An `ActionState` can be fully reconstructed from a stream of `ActionDiff`. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum ActionDiff { /// The action was pressed Pressed { /// The value of the action action: A, - /// The stable identifier of the entity - id: ID, }, /// The action was released Released { /// The value of the action action: A, - /// The stable identifier of the entity - id: ID, }, /// The value of the action changed ValueChanged { /// The value of the action action: A, - /// The stable identifier of the entity - id: ID, /// The new value of the action value: f32, }, @@ -788,8 +822,6 @@ pub enum ActionDiff { AxisPairChanged { /// The value of the action action: A, - /// The stable identifier of the entity - id: ID, /// The new value of the axis axis_pair: Vec2, }, diff --git a/src/systems.rs b/src/systems.rs index a8d91c24..8e2650b2 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -4,7 +4,6 @@ use crate::action_state::ActionStateDriver; use crate::{ action_state::{ActionDiff, ActionState}, - axislike::DualAxisData, clashing_inputs::ClashStrategy, input_map::InputMap, input_streams::InputStreams, @@ -25,8 +24,8 @@ use bevy::{ time::{Real, Time}, utils::{HashMap, Instant}, }; -use core::hash::Hash; +use crate::action_state::ActionDiffEvent; #[cfg(feature = "ui")] use bevy::ui::Interaction; #[cfg(feature = "egui")] @@ -185,23 +184,30 @@ pub fn update_action_state_from_interaction( /// Generates an [`Events`] stream of [`ActionDiff`] from [`ActionState`] /// -/// The `ID` generic type should be a stable entity identifier, -/// suitable to be sent across a network. -/// /// This system is not part of the [`InputManagerPlugin`](crate::plugin::InputManagerPlugin) and must be added manually. -pub fn generate_action_diffs( - action_state_query: Query<(&ActionState, &ID)>, - mut action_diffs: EventWriter>, - mut previous_values: Local>>, - mut previous_axis_pairs: Local>>, +pub fn generate_action_diffs( + action_state: Option>>, + action_state_query: Query<(Entity, &ActionState)>, + mut action_diffs: EventWriter>, + mut previous_values: Local, f32>>>, + mut previous_axis_pairs: Local, Vec2>>>, ) { - for (action_state, id) in action_state_query.iter() { + // 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())), + ); + for (maybe_entity, action_state) in action_state_iter { + let mut diffs = vec![]; for action in action_state.get_just_pressed() { match action_state.action_data(action.clone()).axis_pair { Some(axis_pair) => { - action_diffs.send(ActionDiff::AxisPairChanged { + diffs.push(ActionDiff::AxisPairChanged { action: action.clone(), - id: id.clone(), axis_pair: axis_pair.into(), }); previous_axis_pairs @@ -209,19 +215,17 @@ pub fn generate_action_diffs( .from_key(&action) .or_insert_with(|| (action.clone(), HashMap::default())) .1 - .insert(id.clone(), axis_pair.xy()); + .insert(maybe_entity, axis_pair.xy()); } None => { let value = action_state.value(action.clone()); - action_diffs.send(if value == 1. { + diffs.push(if value == 1. { ActionDiff::Pressed { action: action.clone(), - id: id.clone(), } } else { ActionDiff::ValueChanged { action: action.clone(), - id: id.clone(), value, } }); @@ -230,7 +234,7 @@ pub fn generate_action_diffs( .from_key(&action) .or_insert_with(|| (action.clone(), HashMap::default())) .1 - .insert(id.clone(), value); + .insert(maybe_entity, value); } } } @@ -242,112 +246,50 @@ pub fn generate_action_diffs( Some(axis_pair) => { let previous_axis_pairs = previous_axis_pairs.get_mut(&action).unwrap(); - if let Some(previous_axis_pair) = previous_axis_pairs.get(&id.clone()) { + if let Some(previous_axis_pair) = previous_axis_pairs.get(&maybe_entity) { if *previous_axis_pair == axis_pair.xy() { continue; } } - action_diffs.send(ActionDiff::AxisPairChanged { + diffs.push(ActionDiff::AxisPairChanged { action: action.clone(), - id: id.clone(), axis_pair: axis_pair.into(), }); - previous_axis_pairs.insert(id.clone(), axis_pair.xy()); + previous_axis_pairs.insert(maybe_entity, axis_pair.xy()); } None => { let value = action_state.value(action.clone()); let previous_values = previous_values.get_mut(&action).unwrap(); - if let Some(previous_value) = previous_values.get(&id.clone()) { + if let Some(previous_value) = previous_values.get(&maybe_entity) { if *previous_value == value { continue; } } - action_diffs.send(ActionDiff::ValueChanged { + diffs.push(ActionDiff::ValueChanged { action: action.clone(), - id: id.clone(), value, }); - previous_values.insert(id.clone(), value); + previous_values.insert(maybe_entity, value); } } } for action in action_state.get_just_released() { - action_diffs.send(ActionDiff::Released { + diffs.push(ActionDiff::Released { action: action.clone(), - id: id.clone(), }); if let Some(previous_axes) = previous_axis_pairs.get_mut(&action) { - previous_axes.remove(&id.clone()); + previous_axes.remove(&maybe_entity); } if let Some(previous_values) = previous_values.get_mut(&action) { - previous_values.remove(&id.clone()); + previous_values.remove(&maybe_entity); } } - } -} - -/// Processes an [`Events`] stream of [`ActionDiff`] to update an [`ActionState`] -/// -/// The `ID` generic type should be a stable entity identifier, -/// suitable to be sent across a network. -/// -/// This system is not part of the [`InputManagerPlugin`](crate::plugin::InputManagerPlugin) and must be added manually. -pub fn process_action_diffs( - mut action_state_query: Query<(&mut ActionState, &ID)>, - mut action_diffs: EventReader>, -) { - // PERF: This would probably be faster with an index, but is much more fussy - for action_diff in action_diffs.read() { - for (mut action_state, id) in action_state_query.iter_mut() { - match action_diff { - ActionDiff::Pressed { - action, - id: event_id, - } => { - if event_id == id { - action_state.press(action.clone()); - action_state.action_data_mut(action.clone()).value = 1.; - continue; - } - } - ActionDiff::Released { - action, - id: event_id, - } => { - if event_id == id { - action_state.release(action.clone()); - let action_data = action_state.action_data_mut(action.clone()); - action_data.value = 0.; - action_data.axis_pair = None; - continue; - } - } - ActionDiff::ValueChanged { - action, - id: event_id, - value, - } => { - if event_id == id { - action_state.press(action.clone()); - action_state.action_data_mut(action.clone()).value = *value; - continue; - } - } - ActionDiff::AxisPairChanged { - action, - id: event_id, - axis_pair, - } => { - if event_id == id { - action_state.press(action.clone()); - let action_data = action_state.action_data_mut(action.clone()); - action_data.axis_pair = Some(DualAxisData::from_xy(*axis_pair)); - action_data.value = axis_pair.length(); - continue; - } - } - }; + if !diffs.is_empty() { + action_diffs.send(ActionDiffEvent { + owner: maybe_entity, + action_diffs: diffs, + }); } } } diff --git a/tests/action_diffs.rs b/tests/action_diffs.rs index c65f4565..8e741a6d 100644 --- a/tests/action_diffs.rs +++ b/tests/action_diffs.rs @@ -1,9 +1,7 @@ use bevy::{input::InputPlugin, prelude::*}; +use leafwing_input_manager::action_state::ActionDiffEvent; use leafwing_input_manager::{ - action_state::ActionDiff, - axislike::DualAxisData, - prelude::*, - systems::{generate_action_diffs, process_action_diffs}, + action_state::ActionDiff, axislike::DualAxisData, prelude::*, systems::generate_action_diffs, }; #[derive(Actionlike, Clone, Copy, Debug, Reflect, PartialEq, Eq, Hash)] @@ -11,14 +9,11 @@ enum Action { PayTheBills, } -#[derive(Clone, Copy, Component, Debug, Reflect, PartialEq, Eq, Hash)] -struct BankAccountId(u32); - #[derive(Default)] struct Counter(pub u8); fn spawn_da_bills(mut commands: Commands) { - commands.spawn((BankAccountId(1337), ActionState::::default())); + commands.spawn(ActionState::::default()); } fn pay_da_bills( @@ -37,6 +32,21 @@ fn pay_da_bills( } } +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_diff_event + .action_diffs + .iter() + .for_each(|diff| action_state.apply_diff(diff)); + } + } +} + fn create_app() -> App { let mut app = App::new(); app.add_plugins(( @@ -45,7 +55,8 @@ fn create_app() -> App { InputManagerPlugin::::default(), )) .add_systems(Startup, spawn_da_bills) - .add_event::>(); + .add_event::>(); + app.update(); app } @@ -56,13 +67,13 @@ fn get_events_mut(app: &mut App) -> Mut> { app.world.resource_mut() } -fn send_action_diff(app: &mut App, action_diff: ActionDiff) { - let mut action_diff_events = get_events_mut::>(app); +fn send_action_diff(app: &mut App, action_diff: ActionDiffEvent) { + let mut action_diff_events = get_events_mut::>(app); action_diff_events.send(action_diff); } fn assert_has_no_action_diffs(app: &mut App) { - let action_diff_events = get_events::>(app); + let action_diff_events = get_events::>(app); let action_diff_event_reader = &mut action_diff_events.get_reader(); match action_diff_event_reader.read(action_diff_events).next() { Some(action_diff) => panic!( @@ -73,11 +84,8 @@ fn assert_has_no_action_diffs(app: &mut App) { } } -fn assert_action_diff_created( - app: &mut App, - predicate: impl Fn(&ActionDiff), -) { - let mut action_diff_events = get_events_mut::>(app); +fn assert_action_diff_created(app: &mut App, predicate: impl Fn(&ActionDiffEvent)) { + let mut action_diff_events = get_events_mut::>(app); let action_diff_event_reader = &mut action_diff_events.get_reader(); assert!(action_diff_event_reader.len(action_diff_events.as_ref()) < 2); match action_diff_event_reader @@ -90,32 +98,25 @@ fn assert_action_diff_created( action_diff_events.clear(); } -fn assert_action_diff_received(app: &mut App, action_diff: ActionDiff) { +fn assert_action_diff_received(app: &mut App, action_diff_event: ActionDiffEvent) { let mut action_state_query = app.world.query::<&ActionState>(); let action_state = action_state_query.get_single(&app.world).unwrap(); - match action_diff { - ActionDiff::Pressed { id: _, action } => { + assert_eq!(action_diff_event.action_diffs.len(), 1); + match action_diff_event.action_diffs.first().unwrap().clone() { + ActionDiff::Pressed { action } => { assert!(action_state.pressed(action)); assert!(action_state.value(action) == 1.); } - ActionDiff::Released { id: _, action } => { + ActionDiff::Released { action } => { assert!(action_state.released(action)); assert!(action_state.value(action) == 0.); assert!(action_state.axis_pair(action).is_none()); } - ActionDiff::ValueChanged { - id: _, - action, - value, - } => { + ActionDiff::ValueChanged { action, value } => { assert!(action_state.pressed(action)); assert!(action_state.value(action) == value); } - ActionDiff::AxisPairChanged { - id: _, - action, - axis_pair, - } => { + ActionDiff::AxisPairChanged { action, axis_pair } => { assert!(action_state.pressed(action)); match action_state.axis_pair(action) { Some(axis_pair_data) => { @@ -131,33 +132,36 @@ fn assert_action_diff_received(app: &mut App, action_diff: ActionDiff>>() + .single(&app.world); app.add_systems( Update, pay_da_bills(|mut action_state| { action_state.action_data_mut(Action::PayTheBills).value = 1.; }), ) - .add_systems(PostUpdate, generate_action_diffs::); + .add_systems(PostUpdate, generate_action_diffs::); app.update(); - assert_action_diff_created(&mut app, |action_diff| match action_diff { - ActionDiff::Pressed { id, action } => { - assert!(*id == BankAccountId(1337)); - assert!(*action == Action::PayTheBills); - } - ActionDiff::Released { action: _, id: _ } => { - panic!("Expected a `Pressed` variant got a `Released` variant") + assert_action_diff_created(&mut app, |action_diff_event| { + assert_eq!(action_diff_event.owner, Some(entity)); + assert_eq!(action_diff_event.action_diffs.len(), 1); + match action_diff_event.action_diffs.first().unwrap().clone() { + ActionDiff::Pressed { action } => { + assert_eq!(action, Action::PayTheBills); + } + ActionDiff::Released { .. } => { + panic!("Expected a `Pressed` variant got a `Released` variant") + } + ActionDiff::ValueChanged { .. } => { + panic!("Expected a `Pressed` variant got a `ValueChanged` variant") + } + ActionDiff::AxisPairChanged { .. } => { + panic!("Expected a `Pressed` variant got a `AxisPairChanged` variant") + } } - ActionDiff::ValueChanged { - action: _, - id: _, - value: _, - } => panic!("Expected a `Pressed` variant got a `ValueChanged` variant"), - ActionDiff::AxisPairChanged { - action: _, - id: _, - axis_pair: _, - } => panic!("Expected a `Pressed` variant got a `AxisPairChanged` variant"), }); app.update(); @@ -166,24 +170,23 @@ fn generate_binary_action_diffs() { app.update(); - assert_action_diff_created(&mut app, |action_diff| match action_diff { - ActionDiff::Released { id, action } => { - assert!(*id == BankAccountId(1337)); - assert!(*action == Action::PayTheBills); - } - ActionDiff::Pressed { action: _, id: _ } => { - panic!("Expected a `Released` variant got a `Pressed` variant") + assert_action_diff_created(&mut app, |action_diff_event| { + assert_eq!(action_diff_event.owner, Some(entity)); + assert_eq!(action_diff_event.action_diffs.len(), 1); + match action_diff_event.action_diffs.first().unwrap().clone() { + ActionDiff::Released { action } => { + assert_eq!(action, Action::PayTheBills); + } + ActionDiff::Pressed { .. } => { + panic!("Expected a `Released` variant got a `Pressed` variant") + } + ActionDiff::ValueChanged { .. } => { + panic!("Expected a `Released` variant got a `ValueChanged` variant") + } + ActionDiff::AxisPairChanged { .. } => { + panic!("Expected a `Released` variant got a `AxisPairChanged` variant") + } } - ActionDiff::ValueChanged { - action: _, - id: _, - value: _, - } => panic!("Expected a `Released` variant got a `ValueChanged` variant"), - ActionDiff::AxisPairChanged { - action: _, - id: _, - axis_pair: _, - } => panic!("Expected a `Released` variant got a `AxisPairChanged` variant"), }); } @@ -191,34 +194,39 @@ fn generate_binary_action_diffs() { fn generate_value_action_diffs() { let input_value = 0.5; let mut app = create_app(); + let entity = app + .world + .query_filtered::>>() + .single(&app.world); app.add_systems( Update, pay_da_bills(move |mut action_state| { action_state.action_data_mut(Action::PayTheBills).value = input_value; }), ) - .add_systems(PostUpdate, generate_action_diffs::) - .add_event::>(); + .add_systems(PostUpdate, generate_action_diffs::) + .add_event::>(); app.update(); - assert_action_diff_created(&mut app, |action_diff| match action_diff { - ActionDiff::ValueChanged { id, action, value } => { - assert!(*id == BankAccountId(1337)); - assert!(*action == Action::PayTheBills); - assert!(*value == input_value); - } - ActionDiff::Released { action: _, id: _ } => { - panic!("Expected a `ValueChanged` variant got a `Released` variant") - } - ActionDiff::Pressed { action: _, id: _ } => { - panic!("Expected a `ValueChanged` variant got a `Pressed` variant") + assert_action_diff_created(&mut app, |action_diff_event| { + assert_eq!(action_diff_event.owner, Some(entity)); + assert_eq!(action_diff_event.action_diffs.len(), 1); + match action_diff_event.action_diffs.first().unwrap().clone() { + ActionDiff::ValueChanged { action, value } => { + assert_eq!(action, Action::PayTheBills); + assert_eq!(value, input_value); + } + ActionDiff::Released { .. } => { + panic!("Expected a `ValueChanged` variant got a `Released` variant") + } + ActionDiff::Pressed { .. } => { + panic!("Expected a `ValueChanged` variant got a `Pressed` variant") + } + ActionDiff::AxisPairChanged { .. } => { + panic!("Expected a `ValueChanged` variant got a `AxisPairChanged` variant") + } } - ActionDiff::AxisPairChanged { - action: _, - id: _, - axis_pair: _, - } => panic!("Expected a `ValueChanged` variant got a `AxisPairChanged` variant"), }); app.update(); @@ -227,24 +235,23 @@ fn generate_value_action_diffs() { app.update(); - assert_action_diff_created(&mut app, |action_diff| match action_diff { - ActionDiff::Released { id, action } => { - assert!(*id == BankAccountId(1337)); - assert!(*action == Action::PayTheBills); - } - ActionDiff::Pressed { action: _, id: _ } => { - panic!("Expected a `Released` variant got a `Pressed` variant") + assert_action_diff_created(&mut app, |action_diff_event| { + assert_eq!(action_diff_event.owner, Some(entity)); + assert_eq!(action_diff_event.action_diffs.len(), 1); + match action_diff_event.action_diffs.first().unwrap().clone() { + ActionDiff::Released { action } => { + assert_eq!(action, Action::PayTheBills); + } + ActionDiff::Pressed { .. } => { + panic!("Expected a `Released` variant got a `Pressed` variant") + } + ActionDiff::ValueChanged { .. } => { + panic!("Expected a `Released` variant got a `ValueChanged` variant") + } + ActionDiff::AxisPairChanged { .. } => { + panic!("Expected a `Released` variant got a `AxisPairChanged` variant") + } } - ActionDiff::ValueChanged { - action: _, - id: _, - value: _, - } => panic!("Expected a `Released` variant got a `ValueChanged` variant"), - ActionDiff::AxisPairChanged { - action: _, - id: _, - axis_pair: _, - } => panic!("Expected a `Released` variant got a `AxisPairChanged` variant"), }); } @@ -252,6 +259,10 @@ fn generate_value_action_diffs() { fn generate_axis_action_diffs() { let input_axis_pair = Vec2 { x: 5., y: 8. }; let mut app = create_app(); + let entity = app + .world + .query_filtered::>>() + .single(&app.world); app.add_systems( Update, pay_da_bills(move |mut action_state| { @@ -259,32 +270,29 @@ fn generate_axis_action_diffs() { Some(DualAxisData::from_xy(input_axis_pair)); }), ) - .add_systems(PostUpdate, generate_action_diffs::) - .add_event::>(); + .add_systems(PostUpdate, generate_action_diffs::) + .add_event::>(); app.update(); - assert_action_diff_created(&mut app, |action_diff| match action_diff { - ActionDiff::AxisPairChanged { - id, - action, - axis_pair, - } => { - assert!(*id == BankAccountId(1337)); - assert!(*action == Action::PayTheBills); - assert!(*axis_pair == input_axis_pair); - } - ActionDiff::Released { action: _, id: _ } => { - panic!("Expected a `AxisPairChanged` variant got a `Released` variant") - } - ActionDiff::Pressed { action: _, id: _ } => { - panic!("Expected a `AxisPairChanged` variant got a `Pressed` variant") + assert_action_diff_created(&mut app, |action_diff_event| { + assert_eq!(action_diff_event.owner, Some(entity)); + assert_eq!(action_diff_event.action_diffs.len(), 1); + match action_diff_event.action_diffs.first().unwrap().clone() { + ActionDiff::AxisPairChanged { action, axis_pair } => { + assert_eq!(action, Action::PayTheBills); + assert_eq!(axis_pair, input_axis_pair); + } + ActionDiff::Released { .. } => { + panic!("Expected a `AxisPairChanged` variant got a `Released` variant") + } + ActionDiff::Pressed { .. } => { + panic!("Expected a `AxisPairChanged` variant got a `Pressed` variant") + } + ActionDiff::ValueChanged { .. } => { + panic!("Expected a `AxisPairChanged` variant got a `ValueChanged` variant") + } } - ActionDiff::ValueChanged { - action: _, - id: _, - value: _, - } => panic!("Expected a `AxisPairChanged` variant got a `ValueChanged` variant"), }); app.update(); @@ -293,103 +301,126 @@ fn generate_axis_action_diffs() { app.update(); - assert_action_diff_created(&mut app, |action_diff| match action_diff { - ActionDiff::Released { id, action } => { - assert!(*id == BankAccountId(1337)); - assert!(*action == Action::PayTheBills); - } - ActionDiff::Pressed { action: _, id: _ } => { - panic!("Expected a `Released` variant got a `Pressed` variant") + assert_action_diff_created(&mut app, |action_diff_event| { + assert_eq!(action_diff_event.owner, Some(entity)); + assert_eq!(action_diff_event.action_diffs.len(), 1); + match action_diff_event.action_diffs.first().unwrap().clone() { + ActionDiff::Released { action } => { + assert_eq!(action, Action::PayTheBills); + } + ActionDiff::Pressed { .. } => { + panic!("Expected a `Released` variant got a `Pressed` variant") + } + ActionDiff::ValueChanged { .. } => { + panic!("Expected a `Released` variant got a `ValueChanged` variant") + } + ActionDiff::AxisPairChanged { .. } => { + panic!("Expected a `Released` variant got a `AxisPairChanged` variant") + } } - ActionDiff::ValueChanged { - action: _, - id: _, - value: _, - } => panic!("Expected a `Released` variant got a `ValueChanged` variant"), - ActionDiff::AxisPairChanged { - action: _, - id: _, - axis_pair: _, - } => panic!("Expected a `Released` variant got a `AxisPairChanged` variant"), }); } #[test] fn process_binary_action_diffs() { let mut app = create_app(); - app.add_systems(PreUpdate, process_action_diffs::); - - let action_diff = ActionDiff::Pressed { - id: BankAccountId(1337), - action: Action::PayTheBills, + let entity = app + .world + .query_filtered::>>() + .single(&app.world); + app.add_systems(PreUpdate, process_action_diffs::); + + let action_diff_event = ActionDiffEvent { + owner: Some(entity), + action_diffs: vec![ActionDiff::Pressed { + action: Action::PayTheBills, + }], }; - send_action_diff(&mut app, action_diff.clone()); + send_action_diff(&mut app, action_diff_event.clone()); app.update(); - assert_action_diff_received(&mut app, action_diff); + assert_action_diff_received(&mut app, action_diff_event); - let action_diff = ActionDiff::Released { - id: BankAccountId(1337), - action: Action::PayTheBills, + let action_diff_event = ActionDiffEvent { + owner: Some(entity), + action_diffs: vec![ActionDiff::Released { + action: Action::PayTheBills, + }], }; - send_action_diff(&mut app, action_diff.clone()); + send_action_diff(&mut app, action_diff_event.clone()); app.update(); - assert_action_diff_received(&mut app, action_diff); + assert_action_diff_received(&mut app, action_diff_event); } #[test] fn process_value_action_diff() { let mut app = create_app(); - app.add_systems(PreUpdate, process_action_diffs::); - - let action_diff = ActionDiff::ValueChanged { - id: BankAccountId(1337), - action: Action::PayTheBills, - value: 0.5, + let entity = app + .world + .query_filtered::>>() + .single(&app.world); + app.add_systems(PreUpdate, process_action_diffs::); + + let action_diff_event = ActionDiffEvent { + owner: Some(entity), + action_diffs: vec![ActionDiff::ValueChanged { + action: Action::PayTheBills, + value: 0.5, + }], }; - send_action_diff(&mut app, action_diff.clone()); + send_action_diff(&mut app, action_diff_event.clone()); app.update(); - assert_action_diff_received(&mut app, action_diff); + assert_action_diff_received(&mut app, action_diff_event); - let action_diff = ActionDiff::Released { - id: BankAccountId(1337), - action: Action::PayTheBills, + let action_diff_event = ActionDiffEvent { + owner: Some(entity), + action_diffs: vec![ActionDiff::Released { + action: Action::PayTheBills, + }], }; - send_action_diff(&mut app, action_diff.clone()); + send_action_diff(&mut app, action_diff_event.clone()); app.update(); - assert_action_diff_received(&mut app, action_diff); + assert_action_diff_received(&mut app, action_diff_event); } #[test] fn process_axis_action_diff() { let mut app = create_app(); - app.add_systems(PreUpdate, process_action_diffs::); - - let action_diff = ActionDiff::AxisPairChanged { - id: BankAccountId(1337), - action: Action::PayTheBills, - axis_pair: Vec2 { x: 1., y: 0. }, + let entity = app + .world + .query_filtered::>>() + .single(&app.world); + app.add_systems(PreUpdate, process_action_diffs::); + + let action_diff_event = ActionDiffEvent { + owner: Some(entity), + action_diffs: vec![ActionDiff::AxisPairChanged { + action: Action::PayTheBills, + axis_pair: Vec2 { x: 1., y: 0. }, + }], }; - send_action_diff(&mut app, action_diff.clone()); + send_action_diff(&mut app, action_diff_event.clone()); app.update(); - assert_action_diff_received(&mut app, action_diff); + assert_action_diff_received(&mut app, action_diff_event); - let action_diff = ActionDiff::Released { - id: BankAccountId(1337), - action: Action::PayTheBills, + let action_diff_event = ActionDiffEvent { + owner: Some(entity), + action_diffs: vec![ActionDiff::Released { + action: Action::PayTheBills, + }], }; - send_action_diff(&mut app, action_diff.clone()); + send_action_diff(&mut app, action_diff_event.clone()); app.update(); - assert_action_diff_received(&mut app, action_diff); + assert_action_diff_received(&mut app, action_diff_event); }