From dd9394a4726401e5a1ec6f59d4e2f464910a652b Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 23 Jan 2024 14:22:47 -0500 Subject: [PATCH 1/9] Remove methods on Actionlike --- macros/src/actionlike.rs | 84 +--------------------------------------- src/action_state.rs | 2 - src/lib.rs | 53 ------------------------- 3 files changed, 2 insertions(+), 137 deletions(-) diff --git a/macros/src/actionlike.rs b/macros/src/actionlike.rs index e715217c..6adcc79a 100644 --- a/macros/src/actionlike.rs +++ b/macros/src/actionlike.rs @@ -2,7 +2,7 @@ use proc_macro2::Span; use proc_macro2::TokenStream; use proc_macro_crate::{crate_name, FoundCrate}; use quote::quote; -use syn::{Data, DeriveInput, Ident}; +use syn::{DeriveInput, Ident}; /// This approach and implementation is inspired by the `strum` crate, /// Copyright (c) 2019 Peter Glotfelty @@ -34,87 +34,7 @@ pub(crate) fn actionlike_inner(ast: &DeriveInput) -> TokenStream { quote!(leafwing_input_manager) }; - let variants = match &ast.data { - Data::Enum(v) => &v.variants, - _ => panic!("`Actionlike` cannot be derived for non-enum types. Manually implement the trait instead."), - }; - - // Populate the array - let mut get_at_match_items = Vec::new(); - let mut index_match_items = Vec::new(); - - for (index, variant) in variants.iter().enumerate() { - // The name of the enum variant - let variant_identifier = variant.ident.clone(); - - let get_at_params = match &variant.fields { - // Unit fields have no parameters - syn::Fields::Unit => quote! {}, - // Use the default values for tuple-like fields - syn::Fields::Unnamed(fields) => { - let defaults = ::std::iter::repeat(quote!(::core::default::Default::default())) - .take(fields.unnamed.len()); - quote! { (#(#defaults),*) } - } - // Use the default values for tuple-like fields - syn::Fields::Named(fields) => { - let fields = fields - .named - .iter() - .map(|field| field.ident.as_ref().unwrap()); - quote! { {#(#fields: ::core::default::Default::default()),*} } - } - }; - - let index_params = match &variant.fields { - // Unit fields have no parameters - syn::Fields::Unit => quote! {}, - // Use the default values for tuple-like fields - syn::Fields::Unnamed(fields) => { - let underscores = ::std::iter::repeat(quote!(_)).take(fields.unnamed.len()); - quote! { (#(#underscores),*) } - } - // Use the default values for tuple-like fields - syn::Fields::Named(fields) => { - let fields = fields - .named - .iter() - .map(|field| field.ident.as_ref().unwrap()); - quote! { {#(#fields: _),*} } - } - }; - - // Match items - get_at_match_items.push(quote! { - #index => Some(#enum_name::#variant_identifier #get_at_params), - }); - - index_match_items.push(quote! { - #enum_name::#variant_identifier #index_params => #index, - }); - } - - let n_variants = variants.iter().len(); - quote! { - impl #impl_generics #crate_path::Actionlike for #enum_name #type_generics #where_clause { - fn n_variants() -> usize { - #n_variants - } - - fn get_at(index: usize) -> Option { - match index { - #(#get_at_match_items)* - _ => None, - } - } - - fn index(&self) -> usize { - match self { - #(#index_match_items)* - _ => unreachable!() - } - } - } + impl #impl_generics #crate_path::Actionlike for #enum_name #type_generics #where_clause {} } } diff --git a/src/action_state.rs b/src/action_state.rs index 2cded708..443b15ae 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -99,8 +99,6 @@ impl ActionState { /// The `action_data` is typically constructed from [`InputMap::which_pressed`](crate::input_map::InputMap), /// which reads from the assorted [`Input`](bevy::input::Input) resources. pub fn update(&mut self, action_data: Vec) { - assert_eq!(action_data.len(), A::n_variants()); - for (i, action) in A::variants().enumerate() { match action_data[i].state { ButtonState::JustPressed => self.press(&action), diff --git a/src/lib.rs b/src/lib.rs index 64a791d6..ed12771c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,59 +85,6 @@ pub mod prelude { pub trait Actionlike: Eq + Hash + Send + Sync + Clone + Hash + Reflect + TypePath + FromReflect + 'static { - /// The number of variants of this action type - fn n_variants() -> usize; - - /// Iterates over the possible actions in the order they were defined - fn variants() -> ActionIter { - ActionIter::default() - } - - /// Returns the default value for the action stored at the provided index if it exists - /// - /// This is mostly used internally, to enable space-efficient iteration. - fn get_at(index: usize) -> Option; - - /// Returns the position in the defining enum of the given action - fn index(&self) -> usize; -} - -/// An iterator of [`Actionlike`] actions -/// -/// Created by calling [`Actionlike::variants()`]. -#[derive(Debug, Clone)] -pub struct ActionIter { - index: usize, - _phantom: PhantomData, -} - -impl Iterator for ActionIter { - type Item = A; - - fn next(&mut self) -> Option { - let item = A::get_at(self.index); - if item.is_some() { - self.index += 1; - } - - item - } -} - -impl ExactSizeIterator for ActionIter { - fn len(&self) -> usize { - A::n_variants() - } -} - -// We can't derive this, because otherwise it won't work when A is not default -impl Default for ActionIter { - fn default() -> Self { - ActionIter { - index: 0, - _phantom: PhantomData, - } - } } /// This [`Bundle`] allows entities to collect and interpret inputs from across input sources From 8e08e034f938039a008d7b3243be4b8411aacf8d Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 23 Jan 2024 14:42:15 -0500 Subject: [PATCH 2/9] Revert "Remove methods on Actionlike" This reverts commit dd9394a4726401e5a1ec6f59d4e2f464910a652b. --- macros/src/actionlike.rs | 84 +++++++++++++++++++++++++++++++++++++++- src/action_state.rs | 2 + src/lib.rs | 53 +++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 2 deletions(-) diff --git a/macros/src/actionlike.rs b/macros/src/actionlike.rs index 6adcc79a..e715217c 100644 --- a/macros/src/actionlike.rs +++ b/macros/src/actionlike.rs @@ -2,7 +2,7 @@ use proc_macro2::Span; use proc_macro2::TokenStream; use proc_macro_crate::{crate_name, FoundCrate}; use quote::quote; -use syn::{DeriveInput, Ident}; +use syn::{Data, DeriveInput, Ident}; /// This approach and implementation is inspired by the `strum` crate, /// Copyright (c) 2019 Peter Glotfelty @@ -34,7 +34,87 @@ pub(crate) fn actionlike_inner(ast: &DeriveInput) -> TokenStream { quote!(leafwing_input_manager) }; + let variants = match &ast.data { + Data::Enum(v) => &v.variants, + _ => panic!("`Actionlike` cannot be derived for non-enum types. Manually implement the trait instead."), + }; + + // Populate the array + let mut get_at_match_items = Vec::new(); + let mut index_match_items = Vec::new(); + + for (index, variant) in variants.iter().enumerate() { + // The name of the enum variant + let variant_identifier = variant.ident.clone(); + + let get_at_params = match &variant.fields { + // Unit fields have no parameters + syn::Fields::Unit => quote! {}, + // Use the default values for tuple-like fields + syn::Fields::Unnamed(fields) => { + let defaults = ::std::iter::repeat(quote!(::core::default::Default::default())) + .take(fields.unnamed.len()); + quote! { (#(#defaults),*) } + } + // Use the default values for tuple-like fields + syn::Fields::Named(fields) => { + let fields = fields + .named + .iter() + .map(|field| field.ident.as_ref().unwrap()); + quote! { {#(#fields: ::core::default::Default::default()),*} } + } + }; + + let index_params = match &variant.fields { + // Unit fields have no parameters + syn::Fields::Unit => quote! {}, + // Use the default values for tuple-like fields + syn::Fields::Unnamed(fields) => { + let underscores = ::std::iter::repeat(quote!(_)).take(fields.unnamed.len()); + quote! { (#(#underscores),*) } + } + // Use the default values for tuple-like fields + syn::Fields::Named(fields) => { + let fields = fields + .named + .iter() + .map(|field| field.ident.as_ref().unwrap()); + quote! { {#(#fields: _),*} } + } + }; + + // Match items + get_at_match_items.push(quote! { + #index => Some(#enum_name::#variant_identifier #get_at_params), + }); + + index_match_items.push(quote! { + #enum_name::#variant_identifier #index_params => #index, + }); + } + + let n_variants = variants.iter().len(); + quote! { - impl #impl_generics #crate_path::Actionlike for #enum_name #type_generics #where_clause {} + impl #impl_generics #crate_path::Actionlike for #enum_name #type_generics #where_clause { + fn n_variants() -> usize { + #n_variants + } + + fn get_at(index: usize) -> Option { + match index { + #(#get_at_match_items)* + _ => None, + } + } + + fn index(&self) -> usize { + match self { + #(#index_match_items)* + _ => unreachable!() + } + } + } } } diff --git a/src/action_state.rs b/src/action_state.rs index 443b15ae..2cded708 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -99,6 +99,8 @@ impl ActionState { /// The `action_data` is typically constructed from [`InputMap::which_pressed`](crate::input_map::InputMap), /// which reads from the assorted [`Input`](bevy::input::Input) resources. pub fn update(&mut self, action_data: Vec) { + assert_eq!(action_data.len(), A::n_variants()); + for (i, action) in A::variants().enumerate() { match action_data[i].state { ButtonState::JustPressed => self.press(&action), diff --git a/src/lib.rs b/src/lib.rs index ed12771c..64a791d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,59 @@ pub mod prelude { pub trait Actionlike: Eq + Hash + Send + Sync + Clone + Hash + Reflect + TypePath + FromReflect + 'static { + /// The number of variants of this action type + fn n_variants() -> usize; + + /// Iterates over the possible actions in the order they were defined + fn variants() -> ActionIter { + ActionIter::default() + } + + /// Returns the default value for the action stored at the provided index if it exists + /// + /// This is mostly used internally, to enable space-efficient iteration. + fn get_at(index: usize) -> Option; + + /// Returns the position in the defining enum of the given action + fn index(&self) -> usize; +} + +/// An iterator of [`Actionlike`] actions +/// +/// Created by calling [`Actionlike::variants()`]. +#[derive(Debug, Clone)] +pub struct ActionIter { + index: usize, + _phantom: PhantomData, +} + +impl Iterator for ActionIter { + type Item = A; + + fn next(&mut self) -> Option { + let item = A::get_at(self.index); + if item.is_some() { + self.index += 1; + } + + item + } +} + +impl ExactSizeIterator for ActionIter { + fn len(&self) -> usize { + A::n_variants() + } +} + +// We can't derive this, because otherwise it won't work when A is not default +impl Default for ActionIter { + fn default() -> Self { + ActionIter { + index: 0, + _phantom: PhantomData, + } + } } /// This [`Bundle`] allows entities to collect and interpret inputs from across input sources From ff6ecd9738ff700051ba738afb7290aa5b5277b1 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 23 Jan 2024 15:26:17 -0500 Subject: [PATCH 3/9] Use a HashMap in ActionState --- examples/arpg_indirection.rs | 6 +- examples/mouse_position.rs | 6 +- examples/twin_stick_controller.rs | 4 +- src/action_state.rs | 206 ++++++++++++++++++++---------- src/systems.rs | 21 ++- tests/action_diffs.rs | 16 ++- 6 files changed, 180 insertions(+), 79 deletions(-) diff --git a/examples/arpg_indirection.rs b/examples/arpg_indirection.rs index 6fde8893..b5ac287d 100644 --- a/examples/arpg_indirection.rs +++ b/examples/arpg_indirection.rs @@ -112,8 +112,10 @@ fn copy_action_state( if let Some(matching_ability) = ability_slot_map.get(&slot) { // This copies the `ActionData` between the ActionStates, // including information about how long the buttons have been pressed or released - ability_state - .set_action_data(matching_ability, slot_state.action_data(&slot).clone()); + ability_state.set_action_data( + matching_ability.clone(), + slot_state.action_data(&slot).unwrap().clone(), + ); } } } diff --git a/examples/mouse_position.rs b/examples/mouse_position.rs index fafeedbb..3853f69b 100644 --- a/examples/mouse_position.rs +++ b/examples/mouse_position.rs @@ -59,8 +59,10 @@ fn update_cursor_state_from_window( .expect("Entity does not exist, or does not have an `ActionState` component"); if let Some(val) = window.cursor_position() { - action_state.action_data_mut(&driver.action).axis_pair = - Some(DualAxisData::from_xy(val)); + action_state + .action_data_mut(&driver.action) + .unwrap() + .axis_pair = Some(DualAxisData::from_xy(val)); } } } diff --git a/examples/twin_stick_controller.rs b/examples/twin_stick_controller.rs index 7e862228..b692c01d 100644 --- a/examples/twin_stick_controller.rs +++ b/examples/twin_stick_controller.rs @@ -157,7 +157,9 @@ fn player_mouse_look( // Press the look action, so we can check that it is active action_state.press(&PlayerAction::Look); // Modify the action data to set the axis - let action_data = action_state.action_data_mut(&PlayerAction::Look); + let Some(action_data) = action_state.action_data_mut(&PlayerAction::Look) else { + return; + }; // Flipping y sign here to be consistent with gamepad input. We could also invert the gamepad y axis action_data.axis_pair = Some(DualAxisData::from_xy(Vec2::new(diff.x, -diff.y))); } diff --git a/src/action_state.rs b/src/action_state.rs index 2cded708..9964a63e 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -8,10 +8,9 @@ use bevy::math::Vec2; use bevy::prelude::{Event, Resource}; use bevy::reflect::Reflect; use bevy::utils::hashbrown::hash_set::Iter; -use bevy::utils::{Duration, HashSet, Instant}; +use bevy::utils::{Duration, Entry, HashMap, HashSet, Instant}; use serde::{Deserialize, Serialize}; use std::iter::Once; -use std::marker::PhantomData; /// Metadata about an [`Actionlike`] action /// @@ -88,9 +87,17 @@ pub struct ActionState { /// The [`ActionData`] of each action /// /// The position in this vector corresponds to [`Actionlike::index`]. - action_data: Vec, - #[reflect(ignore)] - _phantom: PhantomData, + action_data: HashMap, +} + +// The derive does not work unless A: Default, +// so we have to implement it manually +impl Default for ActionState { + fn default() -> Self { + Self { + action_data: HashMap::default(), + } + } } impl ActionState { @@ -102,15 +109,24 @@ impl ActionState { assert_eq!(action_data.len(), A::n_variants()); for (i, action) in A::variants().enumerate() { - match action_data[i].state { - ButtonState::JustPressed => self.press(&action), - ButtonState::Pressed => self.press(&action), - ButtonState::JustReleased => self.release(&action), - ButtonState::Released => self.release(&action), + match self.action_data.entry(action) { + Entry::Occupied(occupied_entry) => { + let entry = occupied_entry.into_mut(); + + match action_data[i].state { + ButtonState::JustPressed => entry.state.press(), + ButtonState::Pressed => entry.state.press(), + ButtonState::JustReleased => entry.state.release(), + ButtonState::Released => entry.state.release(), + } + + entry.axis_pair = action_data[i].axis_pair; + entry.value = action_data[i].value; + } + Entry::Vacant(empty_entry) => { + empty_entry.insert(action_data[i].clone()); + } } - - self.action_data[i].axis_pair = action_data[i].axis_pair; - self.action_data[i].value = action_data[i].value; } } @@ -160,10 +176,12 @@ impl ActionState { /// ``` pub fn tick(&mut self, current_instant: Instant, previous_instant: Instant) { // Advanced the ButtonState - self.action_data.iter_mut().for_each(|ad| ad.state.tick()); + self.action_data + .iter_mut() + .for_each(|(_, ad)| ad.state.tick()); // Advance the Timings - self.action_data.iter_mut().for_each(|ad| { + self.action_data.iter_mut().for_each(|(_, ad)| { // Durations should not advance while actions are consumed if !ad.consumed { ad.timing.tick(current_instant, previous_instant); @@ -171,7 +189,7 @@ impl ActionState { }); } - /// A reference to the [`ActionData`] of the corresponding `action` + /// A reference to the [`ActionData`] of the corresponding `action` if populated. /// /// Generally, it'll be clearer to call `pressed` or so on directly on the [`ActionState`]. /// However, accessing the raw data directly allows you to examine detailed metadata holistically. @@ -193,11 +211,11 @@ impl ActionState { /// ``` #[inline] #[must_use] - pub fn action_data(&self, action: &A) -> &ActionData { - &self.action_data[action.index()] + pub fn action_data(&self, action: &A) -> Option<&ActionData> { + self.action_data.get(action) } - /// A mutable reference of the [`ActionData`] of the corresponding `action` + /// A mutable reference of the [`ActionData`] of the corresponding `action` if populated. /// /// Generally, it'll be clearer to call `pressed` or so on directly on the [`ActionState`]. /// However, accessing the raw data directly allows you to examine detailed metadata holistically. @@ -220,11 +238,11 @@ impl ActionState { /// ``` #[inline] #[must_use] - pub fn action_data_mut(&mut self, action: &A) -> &mut ActionData { - &mut self.action_data[action.index()] + pub fn action_data_mut(&mut self, action: &A) -> Option<&mut ActionData> { + self.action_data.get_mut(action) } - /// Get the value associated with the corresponding `action` + /// Get the value associated with the corresponding `action` if present. /// /// Different kinds of bindings have different ways of calculating the value: /// @@ -243,16 +261,25 @@ impl ActionState { /// If multiple inputs trigger the same game action at the same time, the value of each /// triggering input will be added together. /// - /// # Warning + /// # Warnings + /// + /// This value will be 0. if the action has never been pressed or released. /// /// This value may not be bounded as you might expect. /// Consider clamping this to account for multiple triggering inputs, /// typically using the [`clamped_value`](Self::clamped_value) method instead. pub fn value(&self, action: &A) -> f32 { - self.action_data(action).value + match self.action_data(action) { + Some(action_data) => action_data.value, + None => 0.0, + } } /// Get the value associated with the corresponding `action`, clamped to `[-1.0, 1.0]`. + /// + /// # Warning + /// + /// This value will be 0. if the action has never been pressed or released. pub fn clamped_value(&self, action: &A) -> f32 { self.value(action).clamp(-1., 1.) } @@ -274,7 +301,8 @@ impl ActionState { /// Consider clamping this to account for multiple triggering inputs, /// typically using the [`clamped_axis_pair`](Self::clamped_axis_pair) method instead. pub fn axis_pair(&self, action: &A) -> Option { - self.action_data(action).axis_pair + let action_data = self.action_data(action)?; + action_data.axis_pair } /// Get the [`DualAxisData`] associated with the corresponding `action`, clamped to `[-1.0, 1.0]`. @@ -318,8 +346,8 @@ impl ActionState { /// action_state.set_action_data(&Action::Run, slot_1_state.clone()); /// ``` #[inline] - pub fn set_action_data(&mut self, action: &A, data: ActionData) { - self.action_data[action.index()] = data; + pub fn set_action_data(&mut self, action: A, data: ActionData) { + self.action_data.insert(action, data); } /// Press the `action` @@ -328,17 +356,24 @@ impl ActionState { /// Instead, this is set through [`ActionState::tick()`] #[inline] pub fn press(&mut self, action: &A) { - let index = action.index(); + let action_data = match self.action_data_mut(action) { + Some(action_data) => action_data, + None => { + self.set_action_data(action.clone(), ActionData::default()); + self.action_data_mut(action).unwrap() + } + }; + // Consumed actions cannot be pressed until they are released - if self.action_data[index].consumed { + if action_data.consumed { return; } - if self.released(action) { - self.action_data[index].timing.flip(); + if action_data.state.released() { + action_data.timing.flip(); } - self.action_data[index].state.press(); + action_data.state.press(); } /// Release the `action` @@ -347,15 +382,22 @@ impl ActionState { /// Instead, this is set through [`ActionState::tick()`] #[inline] pub fn release(&mut self, action: &A) { - let index = action.index(); + let action_data = match self.action_data_mut(action) { + Some(action_data) => action_data, + None => { + self.set_action_data(action.clone(), ActionData::default()); + self.action_data_mut(action).unwrap() + } + }; + // Once released, consumed actions can be pressed again - self.action_data[index].consumed = false; + action_data.consumed = false; - if self.pressed(action) { - self.action_data[index].timing.flip(); + if action_data.state.pressed() { + action_data.timing.flip(); } - self.action_data[index].state.release(); + action_data.state.release(); } /// Consumes the `action` @@ -399,11 +441,18 @@ impl ActionState { /// ``` #[inline] pub fn consume(&mut self, action: &A) { - let index = action.index(); + let action_data = match self.action_data_mut(action) { + Some(action_data) => action_data, + None => { + self.set_action_data(action.clone(), ActionData::default()); + self.action_data_mut(action).unwrap() + } + }; + // This is the only difference from action_state.release(&action) - self.action_data[index].consumed = true; - self.action_data[index].state.release(); - self.action_data[index].timing.flip(); + action_data.consumed = true; + action_data.state.release(); + action_data.timing.flip(); } /// Consumes all actions @@ -425,21 +474,30 @@ impl ActionState { #[inline] #[must_use] pub fn consumed(&self, action: &A) -> bool { - self.action_data[action.index()].consumed + match self.action_data(action) { + Some(action_data) => action_data.consumed, + None => false, + } } /// Is this `action` currently pressed? #[inline] #[must_use] pub fn pressed(&self, action: &A) -> bool { - self.action_data[action.index()].state.pressed() + match self.action_data(action) { + Some(action_data) => action_data.state.pressed(), + None => false, + } } /// Was this `action` pressed since the last time [tick](ActionState::tick) was called? #[inline] #[must_use] pub fn just_pressed(&self, action: &A) -> bool { - self.action_data[action.index()].state.just_pressed() + match self.action_data(action) { + Some(action_data) => action_data.state.just_pressed(), + None => false, + } } /// Is this `action` currently released? @@ -448,14 +506,20 @@ impl ActionState { #[inline] #[must_use] pub fn released(&self, action: &A) -> bool { - self.action_data[action.index()].state.released() + match self.action_data(action) { + Some(action_data) => action_data.state.released(), + None => true, + } } /// Was this `action` released since the last time [tick](ActionState::tick) was called? #[inline] #[must_use] pub fn just_released(&self, action: &A) -> bool { - self.action_data[action.index()].state.just_released() + match self.action_data(action) { + Some(action_data) => action_data.state.just_released(), + None => false, + } } #[must_use] @@ -484,25 +548,40 @@ impl ActionState { /// The [`Instant`] that the action was last pressed or released /// + /// + /// /// If the action was pressed or released since the last time [`ActionState::tick`] was called /// the value will be [`None`]. /// This ensures that all of our actions are assigned a timing and duration /// that corresponds exactly to the start of a frame, rather than relying on idiosyncratic timing. + /// + /// This will also be [`None`] if the action was never pressed or released. pub fn instant_started(&self, action: &A) -> Option { - self.action_data[action.index()].timing.instant_started + let action_data = self.action_data(action)?; + action_data.timing.instant_started } /// The [`Duration`] for which the action has been held or released + /// + /// This will be [`Duration::ZERO`] if the action was never pressed or released. pub fn current_duration(&self, action: &A) -> Duration { - self.action_data[action.index()].timing.current_duration + let Some(action_data) = self.action_data(action) else { + return Duration::ZERO; + }; + action_data.timing.current_duration } /// The [`Duration`] for which the action was last held or released /// /// This is a snapshot of the [`ActionState::current_duration`] state at the time /// the action was last pressed or released. + /// + /// This will be [`Duration::ZERO`] if the action was never pressed or released. pub fn previous_duration(&self, action: &A) -> Duration { - self.action_data[action.index()].timing.previous_duration + let Some(action_data) = self.action_data(action) else { + return Duration::ZERO; + }; + action_data.timing.previous_duration } /// Applies an [`ActionDiff`] (usually received over the network) to the [`ActionState`]. @@ -511,22 +590,26 @@ impl ActionState { pub fn apply_diff(&mut self, action_diff: &ActionDiff) { match action_diff { ActionDiff::Pressed { action } => { - self.press(&action.clone()); - self.action_data_mut(action).value = 1.; + self.press(action); + // Pressing will initialize the ActionData if it doesn't exist + self.action_data_mut(action).unwrap().value = 1.; } ActionDiff::Released { action } => { - self.release(&action.clone()); - let action_data = self.action_data_mut(action); + self.release(action); + // Releasing will initialize the ActionData if it doesn't exist + let action_data = self.action_data_mut(action).unwrap(); action_data.value = 0.; action_data.axis_pair = None; } ActionDiff::ValueChanged { action, value } => { - self.press(&action.clone()); - self.action_data_mut(action).value = *value; + self.press(action); + // Pressing will initialize the ActionData if it doesn't exist + self.action_data_mut(action).unwrap().value = *value; } ActionDiff::AxisPairChanged { action, axis_pair } => { - self.press(&action.clone()); - let action_data = self.action_data_mut(action); + self.press(action); + let action_data = self.action_data_mut(action).unwrap(); + // Pressing will initialize the ActionData if it doesn't exist action_data.axis_pair = Some(DualAxisData::from_xy(*axis_pair)); action_data.value = axis_pair.length(); } @@ -534,15 +617,6 @@ impl ActionState { } } -impl Default for ActionState { - fn default() -> ActionState { - ActionState { - action_data: A::variants().map(|_| ActionData::default()).collect(), - _phantom: PhantomData, - } - } -} - /// A component that allows the attached entity to drive the [`ActionState`] of the associated entity /// /// # Examples diff --git a/src/systems.rs b/src/systems.rs index 562448bf..03c7b66d 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -20,6 +20,7 @@ use bevy::{ mouse::{MouseButton, MouseMotion, MouseWheel}, Axis, Input, }, + log::warn, math::Vec2, time::{Real, Time}, utils::{HashMap, Instant}, @@ -206,7 +207,12 @@ pub fn generate_action_diffs( 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).axis_pair { + 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(), @@ -220,7 +226,8 @@ pub fn generate_action_diffs( .insert(maybe_entity, axis_pair.xy()); } None => { - let value = action_state.value(&action); + let value = action_data.value; + diffs.push(if value == 1. { ActionDiff::Pressed { action: action.clone(), @@ -244,7 +251,13 @@ pub fn generate_action_diffs( if action_state.just_pressed(&action) { continue; } - match action_state.action_data(&action).axis_pair { + + 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) => { let previous_axis_pairs = previous_axis_pairs.get_mut(&action).unwrap(); @@ -260,7 +273,7 @@ pub fn generate_action_diffs( previous_axis_pairs.insert(maybe_entity, axis_pair.xy()); } None => { - let value = action_state.value(&action); + let value = action_data.value; let previous_values = previous_values.get_mut(&action).unwrap(); if let Some(previous_value) = previous_values.get(&maybe_entity) { diff --git a/tests/action_diffs.rs b/tests/action_diffs.rs index d22b3dc0..3c323747 100644 --- a/tests/action_diffs.rs +++ b/tests/action_diffs.rs @@ -139,7 +139,10 @@ fn generate_binary_action_diffs() { app.add_systems( Update, pay_da_bills(|mut action_state| { - action_state.action_data_mut(&Action::PayTheBills).value = 1.; + action_state + .action_data_mut(&Action::PayTheBills) + .unwrap() + .value = 1.; }), ) .add_systems(PostUpdate, generate_action_diffs::); @@ -201,7 +204,10 @@ fn generate_value_action_diffs() { app.add_systems( Update, pay_da_bills(move |mut action_state| { - action_state.action_data_mut(&Action::PayTheBills).value = input_value; + action_state + .action_data_mut(&Action::PayTheBills) + .unwrap() + .value = input_value; }), ) .add_systems(PostUpdate, generate_action_diffs::) @@ -266,8 +272,10 @@ fn generate_axis_action_diffs() { app.add_systems( Update, pay_da_bills(move |mut action_state| { - action_state.action_data_mut(&Action::PayTheBills).axis_pair = - Some(DualAxisData::from_xy(input_axis_pair)); + action_state + .action_data_mut(&Action::PayTheBills) + .unwrap() + .axis_pair = Some(DualAxisData::from_xy(input_axis_pair)); }), ) .add_systems(PostUpdate, generate_action_diffs::) From 1d2b05f2f24ea7429df5985eca88e77bb0d9f074 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 23 Jan 2024 15:29:14 -0500 Subject: [PATCH 4/9] Update release notes --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index 6cee57cd..f3ab5163 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -27,6 +27,7 @@ - all non-insertion methods now take `&A: Actionlike` rather than `A: Actionlike` to avoid pointless cloning - removed `multimap` dependency in favor of regular `HashMap` which allowed to derive `Reflect` for `InputMap` - removed widely unused and untested dynamic actions functionality: this should be more feasible to implement directly with the changed architecture +- `ActionState` now stores a `HashMap` internally ## Version 0.11.2 From 6ccd762d3fdbf455ab4ca454122743fd5e3c84e8 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 23 Jan 2024 15:50:28 -0500 Subject: [PATCH 5/9] Remove unhelpful test --- tests/integration.rs | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 18305302..70d3be8e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -50,48 +50,6 @@ fn spawn_player(mut commands: Commands) { .insert(Player); } -#[test] -fn do_nothing() { - use bevy::input::InputPlugin; - use bevy::utils::Duration; - - let mut app = App::new(); - - app.add_plugins(MinimalPlugins) - .add_plugins(InputPlugin) - .add_plugins(InputManagerPlugin::::default()) - .add_systems(Startup, spawn_player) - .init_resource::>() - .insert_resource(InputMap::::new([(Action::PayRespects, KeyCode::F)])); - - app.update(); - let action_state = app.world.resource::>(); - let t0 = action_state.instant_started(&Action::PayRespects); - assert!(t0.is_some()); - let mut duration_last_update = Duration::ZERO; - - for _ in 0..3 { - app.update(); - let action_state = app.world.resource::>(); - - // Sanity checking state to catch wonkiness - assert!(!action_state.pressed(&Action::PayRespects)); - assert!(!action_state.just_pressed(&Action::PayRespects)); - assert!(action_state.released(&Action::PayRespects)); - assert!(!action_state.just_released(&Action::PayRespects)); - - assert_eq!(action_state.instant_started(&Action::PayRespects), t0); - assert_eq!( - action_state.previous_duration(&Action::PayRespects), - Duration::ZERO - ); - assert!(action_state.current_duration(&Action::PayRespects) > duration_last_update); - - duration_last_update = action_state.current_duration(&Action::PayRespects); - dbg!(duration_last_update); - } -} - #[test] fn disable_input() { use bevy::input::InputPlugin; From 9968dcbf75cee5890f1b77c9c0208fa37e50753d Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 23 Jan 2024 16:50:03 -0500 Subject: [PATCH 6/9] Pass around a Hashmap when dealing with which_pressed --- RELEASES.md | 4 ++ benches/action_state.rs | 23 ++++--- benches/input_map.rs | 6 +- src/action_state.rs | 14 ++--- src/clashing_inputs.rs | 133 +++++++++++++--------------------------- src/input_map.rs | 29 ++++----- tests/clashes.rs | 5 -- 7 files changed, 86 insertions(+), 128 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index f3ab5163..344947e4 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -28,6 +28,10 @@ - removed `multimap` dependency in favor of regular `HashMap` which allowed to derive `Reflect` for `InputMap` - removed widely unused and untested dynamic actions functionality: this should be more feasible to implement directly with the changed architecture - `ActionState` now stores a `HashMap` internally + - `ActionState::update` now takes a `HashMap` rather than relying on ordering + - `InputMap::which_pressed` now returns a `HashMap` + - `handle_clashes` now takes a `HashMap` + - `ClashStrategy::UseActionOrder` has been removed ## Version 0.11.2 diff --git a/benches/action_state.rs b/benches/action_state.rs index 779a99b3..eae6b98f 100644 --- a/benches/action_state.rs +++ b/benches/action_state.rs @@ -1,4 +1,4 @@ -use bevy::prelude::Reflect; +use bevy::{prelude::Reflect, utils::HashMap}; use criterion::{criterion_group, criterion_main, Criterion}; use leafwing_input_manager::{ action_state::{ActionData, Timing}, @@ -37,7 +37,7 @@ fn just_released(action_state: &ActionState) -> bool { action_state.just_released(&TestAction::A) } -fn update(mut action_state: ActionState, action_data: Vec) { +fn update(mut action_state: ActionState, action_data: HashMap) { action_state.update(action_data); } @@ -52,13 +52,18 @@ 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 action_data: Vec = TestAction::variants() - .map(|_action| ActionData { - state: ButtonState::JustPressed, - value: 0.0, - axis_pair: None, - timing: Timing::default(), - consumed: false, + let action_data: HashMap = TestAction::variants() + .map(|action| { + ( + action, + ActionData { + state: ButtonState::JustPressed, + value: 0.0, + axis_pair: None, + timing: Timing::default(), + consumed: false, + }, + ) }) .collect(); diff --git a/benches/input_map.rs b/benches/input_map.rs index 555ef4be..3d4ad05a 100644 --- a/benches/input_map.rs +++ b/benches/input_map.rs @@ -1,4 +1,5 @@ use bevy::prelude::Reflect; +use bevy::utils::HashMap; use bevy::{ input::InputPlugin, prelude::{App, KeyCode}, @@ -57,7 +58,10 @@ fn construct_input_map_from_chained_calls() -> InputMap { ) } -fn which_pressed(input_streams: &InputStreams, clash_strategy: ClashStrategy) -> Vec { +fn which_pressed( + input_streams: &InputStreams, + clash_strategy: ClashStrategy, +) -> HashMap { let input_map = construct_input_map_from_iter(); input_map.which_pressed(input_streams, clash_strategy) } diff --git a/src/action_state.rs b/src/action_state.rs index 9964a63e..946ae4fc 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -105,26 +105,24 @@ impl ActionState { /// /// The `action_data` is typically constructed from [`InputMap::which_pressed`](crate::input_map::InputMap), /// which reads from the assorted [`Input`](bevy::input::Input) resources. - pub fn update(&mut self, action_data: Vec) { - assert_eq!(action_data.len(), A::n_variants()); - - for (i, action) in A::variants().enumerate() { + pub fn update(&mut self, action_data: HashMap) { + for (action, action_datum) in action_data { match self.action_data.entry(action) { Entry::Occupied(occupied_entry) => { let entry = occupied_entry.into_mut(); - match action_data[i].state { + match action_datum.state { ButtonState::JustPressed => entry.state.press(), ButtonState::Pressed => entry.state.press(), ButtonState::JustReleased => entry.state.release(), ButtonState::Released => entry.state.release(), } - entry.axis_pair = action_data[i].axis_pair; - entry.value = action_data[i].value; + entry.axis_pair = action_datum.axis_pair; + entry.value = action_datum.value; } Entry::Vacant(empty_entry) => { - empty_entry.insert(action_data[i].clone()); + empty_entry.insert(action_datum.clone()); } } } diff --git a/src/clashing_inputs.rs b/src/clashing_inputs.rs index fb1feaaf..6b3dae4c 100644 --- a/src/clashing_inputs.rs +++ b/src/clashing_inputs.rs @@ -8,10 +8,10 @@ use crate::user_input::{InputKind, UserInput}; use crate::Actionlike; use bevy::prelude::Resource; +use bevy::utils::HashMap; use itertools::Itertools; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; -use std::marker::PhantomData; /// How should clashing inputs by handled by an [`InputMap`]? /// @@ -36,11 +36,6 @@ pub enum ClashStrategy { /// This is the default strategy. #[default] PrioritizeLongest, - /// Use the order in which actions are defined in the enum to resolve clashing inputs - /// - /// Uses the iteration order returned by [`Actionlike::variants()`], - /// which is generated in order of the enum items by the `#[derive(Actionlike)]` macro. - UseActionOrder, } impl ClashStrategy { @@ -48,7 +43,7 @@ impl ClashStrategy { pub fn variants() -> &'static [ClashStrategy] { use ClashStrategy::*; - &[PressAll, PrioritizeLongest, UseActionOrder] + &[PressAll, PrioritizeLongest] } } @@ -93,14 +88,14 @@ impl InputMap { /// The `usize` stored in `pressed_actions` corresponds to `Actionlike::index` pub fn handle_clashes( &self, - action_data: &mut [ActionData], + action_data: &mut HashMap, input_streams: &InputStreams, clash_strategy: ClashStrategy, ) { for clash in self.get_clashes(action_data, 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) { - action_data[culled_action.index()] = ActionData::default(); + action_data.remove(&culled_action); } } } @@ -126,18 +121,24 @@ impl InputMap { #[must_use] fn get_clashes( &self, - action_data: &[ActionData], + action_data: &HashMap, 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 Some(data_a) = action_data.get(&clash.action_a) else { + continue; + }; + + let Some(data_b) = action_data.get(&clash.action_b) else { + continue; + }; + // Clashes can only occur if both actions were triggered // This is not strictly necessary, but saves work - if action_data[clash.index_a].state.pressed() - && action_data[clash.index_b].state.pressed() - { + if data_a.state.pressed() && data_b.state.pressed() { // Check if the potential clash occurred based on the pressed inputs if let Some(clash) = check_clash(&clash, input_streams) { clashes.push(clash) @@ -175,12 +176,11 @@ impl InputMap { #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub(crate) struct Clash { /// The `Actionlike::index` value corresponding to `action_a` - index_a: usize, + action_a: A, /// The `Actionlike::index` value corresponding to `action_b` - index_b: usize, + action_b: A, inputs_a: Vec, inputs_b: Vec, - _phantom: PhantomData, } impl Clash { @@ -188,23 +188,10 @@ impl Clash { #[must_use] fn new(action_a: A, action_b: A) -> Self { Self { - index_a: action_a.index(), - index_b: action_b.index(), - inputs_a: Vec::default(), - inputs_b: Vec::default(), - _phantom: PhantomData, - } - } - - /// Creates a new clash between the two actions based on their `Actionlike::index` indexes - #[must_use] - fn from_indexes(index_a: usize, index_b: usize) -> Self { - Self { - index_a, - index_b, + action_a: action_a, + action_b: action_b, inputs_a: Vec::default(), inputs_b: Vec::default(), - _phantom: PhantomData, } } } @@ -319,7 +306,7 @@ fn is_subset(slice_a: &[InputKind], slice_b: &[InputKind]) -> bool { /// Returns `Some(clash)` if they are clashing, and `None` if they are not. #[must_use] fn check_clash(clash: &Clash, input_streams: &InputStreams) -> Option> { - let mut actual_clash: Clash = Clash::from_indexes(clash.index_a, clash.index_b); + let mut actual_clash: Clash = clash.clone(); // For all inputs that were actually pressed that match action A for input_a in clash @@ -398,16 +385,11 @@ fn resolve_clash( .unwrap_or_default(); match longest_a.cmp(&longest_b) { - Ordering::Greater => Some(A::get_at(clash.index_b).unwrap()), - Ordering::Less => Some(A::get_at(clash.index_a).unwrap()), + Ordering::Greater => Some(clash.action_b.clone()), + Ordering::Less => Some(clash.action_a.clone()), Ordering::Equal => None, } - } // Remove the clashing action that comes later in the action enum - ClashStrategy::UseActionOrder => match clash.index_a.cmp(&clash.index_b) { - Ordering::Greater => Some(A::get_at(clash.index_a).unwrap()), - Ordering::Less => Some(A::get_at(clash.index_b).unwrap()), - Ordering::Equal => None, - }, + } } } @@ -463,7 +445,6 @@ mod tests { mod basic_functionality { use crate::axislike::VirtualDPad; - use crate::buttonlike::ButtonState; use crate::input_mocking::MockInput; use bevy::input::InputPlugin; use Action::*; @@ -520,11 +501,10 @@ mod tests { let observed_clash = input_map.possible_clash(One, OneAndTwo).unwrap(); let correct_clash = Clash { - index_a: One.index(), - index_b: OneAndTwo.index(), + action_a: One, + action_b: OneAndTwo, inputs_a: vec![Key1.into()], inputs_b: vec![UserInput::chord([Key1, Key2])], - _phantom: PhantomData, }; assert_eq!(observed_clash, correct_clash); @@ -538,11 +518,10 @@ mod tests { .possible_clash(OneAndTwoAndThree, OneAndTwo) .unwrap(); let correct_clash = Clash { - index_a: OneAndTwoAndThree.index(), - index_b: OneAndTwo.index(), + action_a: OneAndTwoAndThree, + action_b: OneAndTwo, inputs_a: vec![UserInput::chord([Key1, Key2, Key3])], inputs_b: vec![UserInput::chord([Key1, Key2])], - _phantom: PhantomData, }; assert_eq!(observed_clash, correct_clash); @@ -626,35 +605,6 @@ mod tests { ); } - #[test] - fn resolve_use_action_order() { - let mut app = App::new(); - app.add_plugins(InputPlugin); - - let input_map = test_input_map(); - let simple_clash = input_map.possible_clash(One, CtrlOne).unwrap(); - let reversed_clash = input_map.possible_clash(CtrlOne, One).unwrap(); - app.send_input(Key1); - app.send_input(ControlLeft); - app.update(); - - let input_streams = InputStreams::from_world(&app.world, None); - - assert_eq!( - resolve_clash(&simple_clash, ClashStrategy::UseActionOrder, &input_streams,), - Some(CtrlOne) - ); - - assert_eq!( - resolve_clash( - &reversed_clash, - ClashStrategy::UseActionOrder, - &input_streams, - ), - Some(CtrlOne) - ); - } - #[test] fn handle_clashes() { let mut app = App::new(); @@ -665,10 +615,13 @@ mod tests { app.send_input(Key2); app.update(); - let mut action_data = vec![ActionData::default(); Action::n_variants()]; - action_data[One.index()].state = ButtonState::JustPressed; - action_data[Two.index()].state = ButtonState::JustPressed; - action_data[OneAndTwo.index()].state = ButtonState::JustPressed; + let mut action_data = HashMap::new(); + let mut action_datum = ActionData::default(); + action_datum.state.press(); + + action_data.insert(One, action_datum.clone()); + action_data.insert(Two, action_datum.clone()); + action_data.insert(OneAndTwo, action_datum.clone()); input_map.handle_clashes( &mut action_data, @@ -676,8 +629,8 @@ mod tests { ClashStrategy::PrioritizeLongest, ); - let mut expected = vec![ActionData::default(); Action::n_variants()]; - expected[OneAndTwo.index()].state = ButtonState::JustPressed; + let mut expected = HashMap::new(); + expected.insert(OneAndTwo, action_datum.clone()); assert_eq!(action_data, expected); } @@ -693,9 +646,11 @@ mod tests { app.send_input(Up); app.update(); - let mut action_data = vec![ActionData::default(); Action::n_variants()]; - action_data[MoveDPad.index()].state = ButtonState::JustPressed; - action_data[CtrlUp.index()].state = ButtonState::JustPressed; + let mut action_data = HashMap::new(); + let mut action_datum = ActionData::default(); + action_datum.state.press(); + action_data.insert(CtrlUp, action_datum.clone()); + action_data.insert(MoveDPad, action_datum.clone()); input_map.handle_clashes( &mut action_data, @@ -703,8 +658,8 @@ mod tests { ClashStrategy::PrioritizeLongest, ); - let mut expected = vec![ActionData::default(); Action::n_variants()]; - expected[CtrlUp.index()].state = ButtonState::JustPressed; + let mut expected = HashMap::new(); + expected.insert(CtrlUp, action_datum); assert_eq!(action_data, expected); } @@ -725,8 +680,8 @@ mod tests { ClashStrategy::PrioritizeLongest, ); - for (i, action_data) in action_data.iter().enumerate() { - if i == CtrlOne.index() || i == OneAndTwo.index() { + for (action, action_data) in action_data.iter() { + if *action == CtrlOne || *action == OneAndTwo { assert!(action_data.state.pressed()); } else { assert!(action_data.state.released()); diff --git a/src/input_map.rs b/src/input_map.rs index 63b74d4d..7ff662b2 100644 --- a/src/input_map.rs +++ b/src/input_map.rs @@ -319,7 +319,11 @@ impl InputMap { clash_strategy: ClashStrategy, ) -> bool { let action_data = self.which_pressed(input_streams, clash_strategy); - action_data[action.index()].state.pressed() + let Some(action_datum) = action_data.get(action) else { + return false; + }; + + action_datum.state.pressed() } /// Returns the actions that are currently pressed, and the responsible [`UserInput`] for each action @@ -331,36 +335,29 @@ impl InputMap { &self, input_streams: &InputStreams, clash_strategy: ClashStrategy, - ) -> Vec { - let mut action_data = vec![ActionData::default(); A::n_variants()]; + ) -> HashMap { + let mut action_data = HashMap::new(); // Generate the raw action presses for (action, input_vec) in self.iter() { let mut inputs = Vec::new(); + let mut action_datum = ActionData::default(); for input in input_vec { - let action = &mut action_data[action.index()]; - - // Merge axis pair into action data - let axis_pair = input_streams.input_axis_pair(input); - if let Some(axis_pair) = axis_pair { - if let Some(current_axis_pair) = &mut action.axis_pair { - *current_axis_pair = current_axis_pair.merged_with(axis_pair); - } else { - action.axis_pair = Some(axis_pair); - } - } + action_datum.axis_pair = input_streams.input_axis_pair(input); if input_streams.input_pressed(input) { inputs.push(input.clone()); - action.value += input_streams.input_value(input, true); + action_datum.value += input_streams.input_value(input, true); } } if !inputs.is_empty() { - action_data[action.index()].state = ButtonState::JustPressed; + action_datum.state = ButtonState::JustPressed; } + + action_data.insert(action.clone(), action_datum); } // Handle clashing inputs, possibly removing some pressed actions from the list diff --git a/tests/clashes.rs b/tests/clashes.rs index b87999bf..dc667f6f 100644 --- a/tests/clashes.rs +++ b/tests/clashes.rs @@ -102,7 +102,6 @@ fn two_inputs_clash_handling() { app.assert_input_map_actions_eq(ClashStrategy::PressAll, [One, Two, OneAndTwo]); app.assert_input_map_actions_eq(ClashStrategy::PrioritizeLongest, [OneAndTwo]); - app.assert_input_map_actions_eq(ClashStrategy::UseActionOrder, [One, Two]); } #[test] @@ -124,7 +123,6 @@ fn three_inputs_clash_handling() { [One, Two, OneAndTwo, TwoAndThree, OneAndTwoAndThree], ); app.assert_input_map_actions_eq(ClashStrategy::PrioritizeLongest, [OneAndTwoAndThree]); - app.assert_input_map_actions_eq(ClashStrategy::UseActionOrder, [One, Two]); } #[test] @@ -150,7 +148,6 @@ fn modifier_clash_handling() { ClashStrategy::PrioritizeLongest, [CtrlOne, OneAndTwoAndThree], ); - app.assert_input_map_actions_eq(ClashStrategy::UseActionOrder, [One, Two]); } #[test] @@ -169,7 +166,6 @@ fn multiple_modifiers_clash_handling() { app.assert_input_map_actions_eq(ClashStrategy::PressAll, [One, CtrlOne, AltOne, CtrlAltOne]); app.assert_input_map_actions_eq(ClashStrategy::PrioritizeLongest, [CtrlAltOne]); - app.assert_input_map_actions_eq(ClashStrategy::UseActionOrder, [One]); } #[test] @@ -187,5 +183,4 @@ fn action_order_clash_handling() { app.assert_input_map_actions_eq(ClashStrategy::PressAll, [Two, TwoAndThree]); app.assert_input_map_actions_eq(ClashStrategy::PrioritizeLongest, [TwoAndThree]); - app.assert_input_map_actions_eq(ClashStrategy::UseActionOrder, [Two]); } From 3b1aadc2f4c94a504ed28b826a3046db7e9fe34f Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 23 Jan 2024 17:20:34 -0500 Subject: [PATCH 7/9] Remove unhelpful doc tests --- src/action_state.rs | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/src/action_state.rs b/src/action_state.rs index 946ae4fc..8fdfcabc 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -191,22 +191,6 @@ impl ActionState { /// /// Generally, it'll be clearer to call `pressed` or so on directly on the [`ActionState`]. /// However, accessing the raw data directly allows you to examine detailed metadata holistically. - /// - /// # Example - /// ```rust - /// use bevy::prelude::Reflect; - /// use leafwing_input_manager::prelude::*; - /// - /// #[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)] - /// enum Action { - /// Run, - /// Jump, - /// } - /// let mut action_state = ActionState::::default(); - /// let run_data = action_state.action_data(&Action::Run); - /// - /// dbg!(run_data); - /// ``` #[inline] #[must_use] pub fn action_data(&self, action: &A) -> Option<&ActionData> { @@ -217,23 +201,6 @@ impl ActionState { /// /// Generally, it'll be clearer to call `pressed` or so on directly on the [`ActionState`]. /// However, accessing the raw data directly allows you to examine detailed metadata holistically. - /// - /// # Example - /// ```rust - /// use bevy::prelude::Reflect; - /// use leafwing_input_manager::prelude::*; - /// - /// #[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)] - /// enum Action { - /// Run, - /// Jump, - /// } - /// let mut action_state = ActionState::::default(); - /// let mut run_data = action_state.action_data_mut(&Action::Run); - /// run_data.axis_pair = None; - /// - /// dbg!(run_data); - /// ``` #[inline] #[must_use] pub fn action_data_mut(&mut self, action: &A) -> Option<&mut ActionData> { From b9bbed522793e794d4b650f6c785dcd826d764a1 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 23 Jan 2024 17:43:29 -0500 Subject: [PATCH 8/9] Fix doc test --- src/action_state.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/action_state.rs b/src/action_state.rs index 8fdfcabc..6650d2d0 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -308,7 +308,9 @@ impl ActionState { /// /// // And transfer it to the actual ability that we care about /// // without losing timing information - /// action_state.set_action_data(&Action::Run, slot_1_state.clone()); + /// if let Some(state) = slot_1_state { + /// action_state.set_action_data(Action::Run, state.clone()); + /// } /// ``` #[inline] pub fn set_action_data(&mut self, action: A, data: ActionData) { From 7b75cd6337b1c948799e61c3772b13873194dbda Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 23 Jan 2024 18:18:26 -0500 Subject: [PATCH 9/9] Clippy --- src/clashing_inputs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clashing_inputs.rs b/src/clashing_inputs.rs index 6b3dae4c..362ea83f 100644 --- a/src/clashing_inputs.rs +++ b/src/clashing_inputs.rs @@ -188,8 +188,8 @@ impl Clash { #[must_use] fn new(action_a: A, action_b: A) -> Self { Self { - action_a: action_a, - action_b: action_b, + action_a, + action_b, inputs_a: Vec::default(), inputs_b: Vec::default(), }