From b808d684359bded68716cf804be854b4e23121bf Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 6 Aug 2024 13:06:23 -0400 Subject: [PATCH] Store only a single Hashmap in ActionState --- benches/action_state.rs | 15 +- src/action_state/action_data.rs | 59 +++++++ src/action_state/mod.rs | 279 +++++++++++++------------------- src/clashing_inputs.rs | 62 ++++--- src/input_map.rs | 73 +++++---- src/systems.rs | 45 +++--- 6 files changed, 279 insertions(+), 254 deletions(-) diff --git a/benches/action_state.rs b/benches/action_state.rs index 9f0ebde5..f4c00636 100644 --- a/benches/action_state.rs +++ b/benches/action_state.rs @@ -1,6 +1,10 @@ use bevy::{prelude::Reflect, utils::HashMap}; use criterion::{criterion_group, criterion_main, Criterion}; -use leafwing_input_manager::{input_map::UpdatedActions, prelude::ActionState, Actionlike}; +use leafwing_input_manager::{ + input_map::{UpdatedActions, UpdatedValue}, + prelude::ActionState, + Actionlike, +}; #[derive(Actionlike, Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)] enum TestAction { @@ -54,14 +58,11 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function("released", |b| b.iter(|| released(&action_state))); c.bench_function("just_released", |b| b.iter(|| just_released(&action_state))); - let button_actions: HashMap = TestAction::variants() - .map(|action| (action, true)) + let button_actions: HashMap = TestAction::variants() + .map(|action| (action, UpdatedValue::Button(true))) .collect(); - let updated_actions = UpdatedActions { - button_actions, - ..Default::default() - }; + let updated_actions = UpdatedActions(button_actions); c.bench_function("update", |b| { b.iter(|| update(action_state.clone(), updated_actions.clone())) diff --git a/src/action_state/action_data.rs b/src/action_state/action_data.rs index b435249c..eefb43ed 100644 --- a/src/action_state/action_data.rs +++ b/src/action_state/action_data.rs @@ -1,5 +1,7 @@ //! Contains types used to store the state of the actions held in an [`ActionState`](super::ActionState). +use std::time::Instant; + use bevy::{math::Vec2, reflect::Reflect}; use serde::{Deserialize, Serialize}; @@ -33,6 +35,23 @@ impl ActionData { }, } } + + /// Ticks the action data, updating the state of the action. + pub fn tick(&mut self, _current_instant: Instant, _previous_instant: Instant) { + match self.kind_data { + ActionKindData::Button(ref mut data) => { + data.state.tick(); + + #[cfg(feature = "timing")] + // Durations should not advance while actions are consumed + if !data.consumed { + data.timing.tick(_current_instant, _previous_instant); + } + } + ActionKindData::Axis(ref mut _data) => {} + ActionKindData::DualAxis(ref mut _data) => {} + } + } } /// A wrapper over the various forms of data that an action can take. @@ -46,6 +65,46 @@ pub enum ActionKindData { DualAxis(DualAxisData), } +impl ActionKindData { + pub(super) fn swap_to_update_state(&mut self) { + // save the changes applied to `state` into `fixed_update_state` + // switch to loading the `update_state` into `state` + match self { + Self::Button(data) => { + data.fixed_update_state = data.state; + data.state = data.update_state; + } + Self::Axis(data) => { + data.fixed_update_value = data.value; + data.value = data.update_value; + } + Self::DualAxis(data) => { + data.fixed_update_pair = data.pair; + data.pair = data.update_pair; + } + } + } + + pub(super) fn swap_to_fixed_update_state(&mut self) { + // save the changes applied to `state` into `update_state` + // switch to loading the `fixed_update_state` into `state` + match self { + Self::Button(data) => { + data.update_state = data.state; + data.state = data.fixed_update_state; + } + Self::Axis(data) => { + data.update_value = data.value; + data.value = data.fixed_update_value; + } + Self::DualAxis(data) => { + data.update_pair = data.pair; + data.pair = data.fixed_update_pair; + } + } + } +} + /// Metadata about an [`Buttonlike`](crate::user_input::Buttonlike) action /// /// If a button is released, its `reasons_pressed` should be empty. diff --git a/src/action_state/mod.rs b/src/action_state/mod.rs index e0bb795e..b4d16217 100644 --- a/src/action_state/mod.rs +++ b/src/action_state/mod.rs @@ -1,5 +1,6 @@ //! This module contains [`ActionState`] and its supporting methods and impls. +use crate::input_map::UpdatedValue; use crate::{action_diff::ActionDiff, input_map::UpdatedActions}; use crate::{Actionlike, InputControlKind}; @@ -64,12 +65,6 @@ pub use action_data::*; pub struct ActionState { /// The shared action data for each action action_data: HashMap, - /// The [`ButtonData`] of each action - button_data: HashMap, - /// The [`AxisData`] of each action - axis_data: HashMap, - /// The [`Vec2`] of each action - dual_axis_data: HashMap, } // The derive does not work unless A: Default, @@ -78,58 +73,24 @@ impl Default for ActionState { fn default() -> Self { Self { action_data: HashMap::default(), - button_data: HashMap::default(), - axis_data: HashMap::default(), - dual_axis_data: HashMap::default(), } } } impl ActionState { - /// Returns a reference to the complete [`ButtonData`] for all actions. + /// Returns a reference to the complete [`ActionData`] for all actions. #[inline] #[must_use] - pub fn all_button_data(&self) -> &HashMap { - &self.button_data - } - - /// Returns a reference to the complete [`AxisData`] for all actions. - #[inline] - #[must_use] - pub fn all_axis_data(&self) -> &HashMap { - &self.axis_data - } - - /// Returns a reference to the complete [`DualAxisData`] for all actions. - #[inline] - #[must_use] - pub fn all_dual_axis_data(&self) -> &HashMap { - &self.dual_axis_data + pub fn all_action_data(&self) -> &HashMap { + &self.action_data } /// We are about to enter the `Main` schedule, so we: /// - save all the changes applied to `state` into the `fixed_update_state` /// - switch to loading the `update_state` pub(crate) fn swap_to_update_state(&mut self) { - for (_action, action_datum) in self.button_data.iter_mut() { - // save the changes applied to `state` into `fixed_update_state` - action_datum.fixed_update_state = action_datum.state; - // switch to loading the `update_state` into `state` - action_datum.state = action_datum.update_state; - } - - for (_action, action_datum) in self.axis_data.iter_mut() { - // save the changes applied to `state` into `fixed_update_state` - action_datum.fixed_update_value = action_datum.value; - // switch to loading the `update_state` into `state` - action_datum.value = action_datum.update_value; - } - - for (_action, action_datum) in self.dual_axis_data.iter_mut() { - // save the changes applied to `state` into `fixed_update_state` - action_datum.fixed_update_pair = action_datum.pair; - // switch to loading the `update_state` into `state` - action_datum.pair = action_datum.update_pair; + for action_datum in self.action_data.values_mut() { + action_datum.kind_data.swap_to_update_state(); } } @@ -137,25 +98,8 @@ impl ActionState { /// - save all the changes applied to `state` into the `update_state` /// - switch to loading the `fixed_update_state` pub(crate) fn swap_to_fixed_update_state(&mut self) { - for (_action, action_datum) in self.button_data.iter_mut() { - // save the changes applied to `state` into `update_state` - action_datum.update_state = action_datum.state; - // switch to loading the `fixed_update_state` into `state` - action_datum.state = action_datum.fixed_update_state; - } - - for (_action, action_datum) in self.axis_data.iter_mut() { - // save the changes applied to `state` into `update_state` - action_datum.update_value = action_datum.value; - // switch to loading the `fixed_update_state` into `state` - action_datum.value = action_datum.fixed_update_value; - } - - for (_action, action_datum) in self.dual_axis_data.iter_mut() { - // save the changes applied to `state` into `update_state` - action_datum.update_pair = action_datum.pair; - // switch to loading the `fixed_update_state` into `state` - action_datum.pair = action_datum.fixed_update_pair; + for action_datum in self.action_data.values_mut() { + action_datum.kind_data.swap_to_fixed_update_state(); } } @@ -164,49 +108,21 @@ impl ActionState { /// The `action_data` is typically constructed from [`InputMap::process_actions`](crate::input_map::InputMap::process_actions), /// which reads from the assorted [`ButtonInput`](bevy::input::ButtonInput) resources. pub fn update(&mut self, updated_actions: UpdatedActions) { - for (action, button_datum) in updated_actions.button_actions { - if self.button_data.contains_key(&action) { - match button_datum { - true => self.press(&action), - false => self.release(&action), + for (action, updated_value) in updated_actions.iter() { + match updated_value { + UpdatedValue::Button(pressed) => { + if *pressed { + self.press(action); + } else { + self.release(action); + } + } + UpdatedValue::Axis(value) => { + self.axis_data_mut_or_default(action).value = *value; + } + UpdatedValue::DualAxis(pair) => { + self.dual_axis_data_mut_or_default(action).pair = *pair; } - } else { - match button_datum { - true => self.button_data.insert(action, ButtonData::JUST_PRESSED), - // Buttons should start in a released state, - // and should not be just pressed or just released. - // This behavior helps avoid unexpected behavior with on-key-release actions - // at the start of the game. - false => self.button_data.insert(action, ButtonData::RELEASED), - }; - } - } - - for (action, axis_datum) in updated_actions.axis_actions.into_iter() { - if self.axis_data.contains_key(&action) { - self.axis_data.get_mut(&action).unwrap().value = axis_datum; - } else { - self.axis_data.insert( - action, - AxisData { - value: axis_datum, - ..Default::default() - }, - ); - } - } - - for (action, dual_axis_datum) in updated_actions.dual_axis_actions.into_iter() { - if self.dual_axis_data.contains_key(&action) { - self.dual_axis_data.get_mut(&action).unwrap().pair = dual_axis_datum; - } else { - self.dual_axis_data.insert( - action, - DualAxisData { - pair: dual_axis_datum, - ..Default::default() - }, - ); } } } @@ -257,17 +173,10 @@ impl ActionState { /// assert!(!action_state.just_pressed(&Action::Jump)); /// ``` pub fn tick(&mut self, _current_instant: Instant, _previous_instant: Instant) { - // Advanced the ButtonState - self.button_data.values_mut().for_each(|ad| ad.state.tick()); - - // Advance the Timings if the feature is enabled - #[cfg(feature = "timing")] - self.button_data.values_mut().for_each(|ad| { - // Durations should not advance while actions are consumed - if !ad.consumed { - ad.timing.tick(_current_instant, _previous_instant); - } - }); + // Advanced the action states + self.action_data + .values_mut() + .for_each(|action_datum| action_datum.tick(_current_instant, _previous_instant)); } /// A reference to the [`ActionData`] corresponding to the `action`. @@ -323,7 +232,13 @@ impl ActionState { #[inline] #[must_use] pub fn button_data(&self, action: &A) -> Option<&ButtonData> { - self.button_data.get(action) + match self.action_data(action) { + Some(action_data) => match action_data.kind_data { + ActionKindData::Button(ref button_data) => Some(button_data), + _ => None, + }, + None => None, + } } /// A mutable reference of the [`ButtonData`] corresponding to the `action`. @@ -346,7 +261,13 @@ impl ActionState { #[inline] #[must_use] pub fn button_data_mut(&mut self, action: &A) -> Option<&mut ButtonData> { - self.button_data.get_mut(action) + match self.action_data_mut(action) { + Some(action_data) => match &mut action_data.kind_data { + ActionKindData::Button(ref mut button_data) => Some(button_data), + _ => None, + }, + None => None, + } } /// A mutable reference of the [`ButtonData`] corresponding to the `action`, initializing it if needed. @@ -360,11 +281,13 @@ impl ActionState { #[inline] #[must_use] pub fn button_data_mut_or_default(&mut self, action: &A) -> &mut ButtonData { - self.button_data - .raw_entry_mut() - .from_key(action) - .or_insert_with(|| (action.clone(), ButtonData::default())) - .1 + debug_assert_eq!(action.input_control_kind(), InputControlKind::Button); + + let action_data = self.action_data_mut_or_default(action); + let ActionKindData::Button(ref mut button_data) = action_data.kind_data else { + panic!("{action:?} is not a Button"); + }; + button_data } /// A reference of the [`AxisData`] corresponding to the `action`. @@ -383,7 +306,13 @@ impl ActionState { pub fn axis_data(&self, action: &A) -> Option<&AxisData> { debug_assert_eq!(action.input_control_kind(), InputControlKind::Axis); - self.axis_data.get(action) + match self.action_data(action) { + Some(action_data) => match action_data.kind_data { + ActionKindData::Axis(ref axis_data) => Some(axis_data), + _ => None, + }, + None => None, + } } /// A mutable reference of the [`AxisData`] corresponding to the `action`. @@ -400,9 +329,13 @@ impl ActionState { #[inline] #[must_use] pub fn axis_data_mut(&mut self, action: &A) -> Option<&mut AxisData> { - debug_assert_eq!(action.input_control_kind(), InputControlKind::Axis); - - self.axis_data.get_mut(action) + match self.action_data_mut(action) { + Some(action_data) => match &mut action_data.kind_data { + ActionKindData::Axis(ref mut axis_data) => Some(axis_data), + _ => None, + }, + None => None, + } } /// A mutable reference of the [`AxisData`] corresponding to the `action`, initializing it if needed.. @@ -418,11 +351,11 @@ impl ActionState { pub fn axis_data_mut_or_default(&mut self, action: &A) -> &mut AxisData { debug_assert_eq!(action.input_control_kind(), InputControlKind::Axis); - self.axis_data - .raw_entry_mut() - .from_key(action) - .or_insert_with(|| (action.clone(), AxisData::default())) - .1 + let action_data = self.action_data_mut_or_default(action); + let ActionKindData::Axis(ref mut axis_data) = action_data.kind_data else { + panic!("{action:?} is not an Axis"); + }; + axis_data } /// A reference of the [`DualAxisData`] corresponding to the `action`. @@ -441,7 +374,13 @@ impl ActionState { pub fn dual_axis_data(&self, action: &A) -> Option<&DualAxisData> { debug_assert_eq!(action.input_control_kind(), InputControlKind::DualAxis); - self.dual_axis_data.get(action) + match self.action_data(action) { + Some(action_data) => match action_data.kind_data { + ActionKindData::DualAxis(ref axis_data) => Some(axis_data), + _ => None, + }, + None => None, + } } /// A mutable reference of the [`DualAxisData`] corresponding to the `action`. @@ -460,7 +399,13 @@ impl ActionState { pub fn dual_axis_data_mut(&mut self, action: &A) -> Option<&mut DualAxisData> { debug_assert_eq!(action.input_control_kind(), InputControlKind::DualAxis); - self.dual_axis_data.get_mut(action) + match self.action_data_mut(action) { + Some(action_data) => match &mut action_data.kind_data { + ActionKindData::DualAxis(ref mut dual_axis_data) => Some(dual_axis_data), + _ => None, + }, + None => None, + } } /// A mutable reference of the [`ButtonData`] corresponding to the `action` initializing it if needed. @@ -476,11 +421,11 @@ impl ActionState { pub fn dual_axis_data_mut_or_default(&mut self, action: &A) -> &mut DualAxisData { debug_assert_eq!(action.input_control_kind(), InputControlKind::DualAxis); - self.dual_axis_data - .raw_entry_mut() - .from_key(action) - .or_insert_with(|| (action.clone(), DualAxisData::default())) - .1 + let action_data = self.action_data_mut_or_default(action); + let ActionKindData::DualAxis(ref mut dual_axis_data) = action_data.kind_data else { + panic!("{action:?} is not a Dual Axis"); + }; + dual_axis_data } /// Get the value associated with the corresponding `action` if present. @@ -597,12 +542,13 @@ impl ActionState { pub fn set_button_data(&mut self, action: A, data: ButtonData) { debug_assert_eq!(action.input_control_kind(), InputControlKind::Button); - self.button_data.insert(action, data); + let button_data = self.button_data_mut_or_default(&action); + *button_data = data; } /// Press the `action` /// - /// No initial instant or reasons why the button was pressed will be recorded + /// No initial instant or reasons why the button was pressed will be recorded. /// Instead, this is set through [`ActionState::tick()`] #[inline] pub fn press(&mut self, action: &A) { @@ -625,7 +571,7 @@ impl ActionState { /// Release the `action` /// - /// No initial instant will be recorded + /// No initial instant will be recorded. /// Instead, this is set through [`ActionState::tick()`] #[inline] pub fn release(&mut self, action: &A) { @@ -644,11 +590,14 @@ impl ActionState { action_data.state.release(); } - /// Releases all [`Buttonlike`](crate::user_input::Buttonlike) actions + /// Releases all [`Buttonlike`](crate::user_input::Buttonlike) actions. pub fn release_all(&mut self) { // Collect out to avoid angering the borrow checker - let buttonlike_actions = self.button_data.keys().cloned().collect::>(); - for action in buttonlike_actions { + let all_actions = self.action_data.keys().cloned().collect::>(); + for action in all_actions + .into_iter() + .filter(|action| action.input_control_kind() == InputControlKind::Button) + { self.release(&action); } } @@ -861,40 +810,44 @@ impl ActionState { #[must_use] /// Which actions are currently pressed? pub fn get_pressed(&self) -> Vec { - self.button_data - .iter() - .filter(|(_action, data)| data.pressed()) - .map(|(action, _data)| action.clone()) + let all_actions = self.action_data.keys().cloned(); + + all_actions + .into_iter() + .filter(|action| self.pressed(action)) .collect() } #[must_use] /// Which actions were just pressed? pub fn get_just_pressed(&self) -> Vec { - self.button_data - .iter() - .filter(|(_action, data)| data.just_pressed()) - .map(|(action, _data)| action.clone()) + let all_actions = self.action_data.keys().cloned(); + + all_actions + .into_iter() + .filter(|action| self.just_pressed(action)) .collect() } #[must_use] /// Which actions are currently released? pub fn get_released(&self) -> Vec { - self.button_data - .iter() - .filter(|(_action, data)| data.released()) - .map(|(action, _data)| action.clone()) + let all_actions = self.action_data.keys().cloned(); + + all_actions + .into_iter() + .filter(|action| self.released(action)) .collect() } #[must_use] /// Which actions were just released? pub fn get_just_released(&self) -> Vec { - self.button_data - .iter() - .filter(|(_action, data)| data.just_released()) - .map(|(action, _data)| action.clone()) + let all_actions = self.action_data.keys().cloned(); + + all_actions + .into_iter() + .filter(|action| self.just_released(action)) .collect() } @@ -971,7 +924,7 @@ impl ActionState { #[inline] #[must_use] pub fn keys(&self) -> Vec { - self.button_data.keys().cloned().collect() + self.action_data.keys().cloned().collect() } } diff --git a/src/clashing_inputs.rs b/src/clashing_inputs.rs index 3696ed2b..c53d9153 100644 --- a/src/clashing_inputs.rs +++ b/src/clashing_inputs.rs @@ -7,10 +7,9 @@ use std::cmp::Ordering; use bevy::prelude::Resource; -use bevy::utils::HashMap; use serde::{Deserialize, Serialize}; -use crate::input_map::InputMap; +use crate::input_map::{InputMap, UpdatedActions}; use crate::input_streams::InputStreams; use crate::user_input::Buttonlike; use crate::{Actionlike, InputControlKind}; @@ -173,14 +172,14 @@ impl InputMap { /// The `usize` stored in `pressed_actions` corresponds to `Actionlike::index` pub fn handle_clashes( &self, - button_data: &mut HashMap, + updated_actions: &mut UpdatedActions, input_streams: &InputStreams, clash_strategy: ClashStrategy, ) { - for clash in self.get_clashes(button_data, input_streams) { + for clash in self.get_clashes(updated_actions, input_streams) { // Remove the action in the pair that was overruled, if any if let Some(culled_action) = resolve_clash(&clash, clash_strategy, input_streams) { - button_data.remove(&culled_action); + updated_actions.remove(&culled_action); } } } @@ -206,21 +205,15 @@ impl InputMap { #[must_use] fn get_clashes( &self, - action_data: &HashMap, + updated_actions: &UpdatedActions, input_streams: &InputStreams, ) -> Vec> { let mut clashes = Vec::default(); // We can limit our search to the cached set of possibly clashing actions for clash in self.possible_clashes() { - let pressed_a = action_data - .get(&clash.action_a) - .copied() - .unwrap_or_default(); - let pressed_b = action_data - .get(&clash.action_b) - .copied() - .unwrap_or_default(); + let pressed_a = updated_actions.pressed(&clash.action_a); + let pressed_b = updated_actions.pressed(&clash.action_b); // Clashes can only occur if both actions were triggered // This is not strictly necessary, but saves work @@ -468,7 +461,10 @@ mod tests { } mod basic_functionality { - use crate::prelude::{AccumulatedMouseMovement, AccumulatedMouseScroll, ModifierKey}; + use crate::{ + input_map::UpdatedValue, + prelude::{AccumulatedMouseMovement, AccumulatedMouseScroll, ModifierKey}, + }; use bevy::input::InputPlugin; use Action::*; @@ -630,22 +626,22 @@ mod tests { Digit2.press(app.world_mut()); app.update(); - let mut button_data = HashMap::new(); + let mut updated_actions = UpdatedActions::default(); - button_data.insert(One, true); - button_data.insert(Two, true); - button_data.insert(OneAndTwo, true); + updated_actions.insert(One, UpdatedValue::Button(true)); + updated_actions.insert(Two, UpdatedValue::Button(true)); + updated_actions.insert(OneAndTwo, UpdatedValue::Button(true)); input_map.handle_clashes( - &mut button_data, + &mut updated_actions, &InputStreams::from_world(app.world(), None), ClashStrategy::PrioritizeLongest, ); - let mut expected = HashMap::new(); - expected.insert(OneAndTwo, true); + let mut expected = UpdatedActions::default(); + expected.insert(OneAndTwo, UpdatedValue::Button(true)); - assert_eq!(button_data, expected); + assert_eq!(updated_actions, expected); } // Checks that a clash between a VirtualDPad and a chord chooses the chord @@ -664,9 +660,9 @@ mod tests { // Both the DPad and the chord are pressed, // because we've sent the inputs for both - let mut button_data = HashMap::new(); - button_data.insert(CtrlUp, true); - button_data.insert(MoveDPad, true); + let mut updated_actions = UpdatedActions::default(); + updated_actions.insert(CtrlUp, UpdatedValue::Button(true)); + updated_actions.insert(MoveDPad, UpdatedValue::Button(true)); // Double-check that the two input bindings clash let chord_input = input_map.get_buttonlike(&CtrlUp).unwrap().first().unwrap(); @@ -689,17 +685,17 @@ mod tests { assert!(chord_input.decompose().len() > dpad_input.decompose().len()); input_map.handle_clashes( - &mut button_data, + &mut updated_actions, &InputStreams::from_world(app.world(), None), ClashStrategy::PrioritizeLongest, ); // Only the chord should be pressed, // because it is longer than the DPad - let mut expected = HashMap::new(); - expected.insert(CtrlUp, true); + let mut expected = UpdatedActions::default(); + expected.insert(CtrlUp, UpdatedValue::Button(true)); - assert_eq!(button_data, expected); + assert_eq!(updated_actions, expected); } #[test] @@ -720,11 +716,11 @@ mod tests { ClashStrategy::PrioritizeLongest, ); - for (action, button_pressed) in action_data.button_actions.iter() { + for (action, &updated_value) in action_data.iter() { if *action == CtrlOne || *action == OneAndTwo { - assert!(button_pressed); + assert_eq!(updated_value, UpdatedValue::Button(true)); } else { - assert!(!button_pressed); + assert_eq!(updated_value, UpdatedValue::Button(false)); } } } diff --git a/src/input_map.rs b/src/input_map.rs index 27ad4f31..7246e88c 100644 --- a/src/input_map.rs +++ b/src/input_map.rs @@ -6,7 +6,7 @@ use std::fmt::Debug; use bevy::asset::Asset; use bevy::log::error; use bevy::math::Vec2; -use bevy::prelude::{Component, Gamepad, Reflect, Resource}; +use bevy::prelude::{Component, Deref, DerefMut, Gamepad, Reflect, Resource}; use bevy::utils::HashMap; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -453,11 +453,16 @@ impl InputMap { input_streams: &InputStreams, clash_strategy: ClashStrategy, ) -> bool { - self.process_actions(input_streams, clash_strategy) - .button_actions - .get(action) - .copied() - .unwrap_or_default() + let processed_actions = self.process_actions(input_streams, clash_strategy); + + let Some(updated_value) = processed_actions.get(action) else { + return false; + }; + + match updated_value { + UpdatedValue::Button(state) => *state, + _ => false, + } } /// Determines the correct state for each action according to provided [`InputStreams`]. @@ -476,9 +481,7 @@ impl InputMap { input_streams: &InputStreams, clash_strategy: ClashStrategy, ) -> UpdatedActions { - let mut button_actions = HashMap::new(); - let mut axis_actions = HashMap::new(); - let mut dual_axis_actions = HashMap::new(); + let mut updated_actions = UpdatedActions::default(); // Generate the base action data for each action for (action, _input_bindings) in self.iter_buttonlike() { @@ -490,7 +493,7 @@ impl InputMap { } } - button_actions.insert(action.clone(), final_state); + updated_actions.insert(action.clone(), UpdatedValue::Button(final_state)); } for (action, _input_bindings) in self.iter_axislike() { @@ -499,7 +502,7 @@ impl InputMap { final_value += binding.value(input_streams); } - axis_actions.insert(action.clone(), final_value); + updated_actions.insert(action.clone(), UpdatedValue::Axis(final_value)); } for (action, _input_bindings) in self.iter_dual_axislike() { @@ -508,39 +511,47 @@ impl InputMap { final_value += binding.axis_pair(input_streams); } - dual_axis_actions.insert(action.clone(), final_value); + updated_actions.insert(action.clone(), UpdatedValue::DualAxis(final_value)); } // Handle clashing inputs, possibly removing some pressed actions from the list - self.handle_clashes(&mut button_actions, input_streams, clash_strategy); + self.handle_clashes(&mut updated_actions, input_streams, clash_strategy); - UpdatedActions { - button_actions, - axis_actions, - dual_axis_actions, - } + updated_actions } } /// The output returned by [`InputMap::process_actions`], /// used by [`ActionState::update`](crate::action_state::ActionState) to update the state of each action. -#[derive(Debug, Clone, PartialEq)] -pub struct UpdatedActions { - /// The updated state of each buttonlike action. - pub button_actions: HashMap, - /// The updated state of each axislike action. - pub axis_actions: HashMap, - /// The updated state of each dual-axislike action. - pub dual_axis_actions: HashMap, +#[derive(Debug, Clone, PartialEq, Deref, DerefMut)] +pub struct UpdatedActions(pub HashMap); + +impl UpdatedActions { + /// Returns `true` if the action is both buttonlike and pressed. + pub fn pressed(&self, action: &A) -> bool { + match self.0.get(action) { + Some(UpdatedValue::Button(state)) => *state, + _ => false, + } + } +} + +/// An enum representing the updated value of an action. +/// +/// Used in [`UpdatedActions`] to store the updated state of each action. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum UpdatedValue { + /// A buttonlike action that was pressed or released. + Button(bool), + /// An axislike action that was updated. + Axis(f32), + /// A dual-axislike action that was updated. + DualAxis(Vec2), } impl Default for UpdatedActions { fn default() -> Self { - Self { - button_actions: Default::default(), - axis_actions: Default::default(), - dual_axis_actions: Default::default(), - } + Self(HashMap::default()) } } diff --git a/src/systems.rs b/src/systems.rs index a85727e9..aba8f473 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -1,5 +1,6 @@ //! The systems that power each [`InputManagerPlugin`](crate::plugin::InputManagerPlugin). +use crate::action_state::ActionKindData; use crate::user_input::{AccumulatedMouseMovement, AccumulatedMouseScroll}; use crate::{ action_state::ActionState, clashing_inputs::ClashStrategy, input_map::InputMap, @@ -250,16 +251,18 @@ impl SummarizedActionState { let mut per_entity_axis_state = HashMap::default(); let mut per_entity_dual_axis_state = HashMap::default(); - for (action, button_data) in global_action_state.all_button_data() { - per_entity_button_state.insert(action.clone(), button_data.pressed()); - } - - for (action, axis_data) in global_action_state.all_axis_data() { - per_entity_axis_state.insert(action.clone(), axis_data.value); - } - - for (action, dual_axis_data) in global_action_state.all_dual_axis_data() { - per_entity_dual_axis_state.insert(action.clone(), dual_axis_data.pair); + for (action, action_data) in global_action_state.all_action_data() { + match &action_data.kind_data { + ActionKindData::Button(button_data) => { + per_entity_button_state.insert(action.clone(), button_data.pressed()); + } + ActionKindData::Axis(axis_data) => { + per_entity_axis_state.insert(action.clone(), axis_data.value); + } + ActionKindData::DualAxis(dual_axis_data) => { + per_entity_dual_axis_state.insert(action.clone(), dual_axis_data.pair); + } + } } button_state_map.insert(Entity::PLACEHOLDER, per_entity_button_state); @@ -272,16 +275,18 @@ impl SummarizedActionState { let mut per_entity_axis_state = HashMap::default(); let mut per_entity_dual_axis_state = HashMap::default(); - for (action, button_data) in action_state.all_button_data() { - per_entity_button_state.insert(action.clone(), button_data.pressed()); - } - - for (action, axis_data) in action_state.all_axis_data() { - per_entity_axis_state.insert(action.clone(), axis_data.value); - } - - for (action, dual_axis_data) in action_state.all_dual_axis_data() { - per_entity_dual_axis_state.insert(action.clone(), dual_axis_data.pair); + for (action, action_data) in action_state.all_action_data() { + match &action_data.kind_data { + ActionKindData::Button(button_data) => { + per_entity_button_state.insert(action.clone(), button_data.pressed()); + } + ActionKindData::Axis(axis_data) => { + per_entity_axis_state.insert(action.clone(), axis_data.value); + } + ActionKindData::DualAxis(dual_axis_data) => { + per_entity_dual_axis_state.insert(action.clone(), dual_axis_data.pair); + } + } } button_state_map.insert(entity, per_entity_button_state);