From 779ebc1e9be8362945d1c4960958934623c9145b Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 23 Jan 2024 19:37:44 -0500 Subject: [PATCH] Remove methods from `Actionlike` to support more complex Action types (#452) * Remove n_variants * Remove Actionlike::index * Remove Actionlike::variants * Remove get_at method on Actionlike * Fix up tests and examples * Add tests that validate that the trait is more flexible now * Remove fragile test * Clippy * Remove outdated docs --------- Co-authored-by: Alice Cecile --- RELEASES.md | 1 + benches/action_state.rs | 7 +++ examples/arpg_indirection.rs | 12 ++++- examples/clash_handling.rs | 6 +-- examples/default_controls.rs | 19 ++++--- examples/twin_stick_controller.rs | 13 +++-- macros/src/actionlike.rs | 84 +------------------------------ src/action_state.rs | 37 +++++++++++--- src/clashing_inputs.rs | 56 +++++++-------------- src/input_map.rs | 36 +++---------- src/lib.rs | 54 -------------------- tests/actionlike_derive.rs | 29 +++-------- tests/clashes.rs | 21 ++++++-- tests/mouse_motion.rs | 15 +++++- tests/mouse_wheel.rs | 10 +++- 15 files changed, 146 insertions(+), 254 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 53e1906c..b781b916 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -15,6 +15,7 @@ - `InputMap`s are now constructed with `(Action, Input)` pairs, rather than `(Input, Action)` pairs, which directly matches the underlying data model - registered types in the reflection system - added `InputMap::clear` +- added `ActionState::keys` ### Bugs diff --git a/benches/action_state.rs b/benches/action_state.rs index eae6b98f..7bbed104 100644 --- a/benches/action_state.rs +++ b/benches/action_state.rs @@ -21,6 +21,13 @@ enum TestAction { J, } +impl TestAction { + fn variants() -> impl Iterator { + use TestAction::*; + [A, B, C, D, E, F, G, H, I, J].iter().copied() + } +} + fn pressed(action_state: &ActionState) -> bool { action_state.pressed(&TestAction::A) } diff --git a/examples/arpg_indirection.rs b/examples/arpg_indirection.rs index b5ac287d..9af9a61b 100644 --- a/examples/arpg_indirection.rs +++ b/examples/arpg_indirection.rs @@ -38,6 +38,16 @@ enum Slot { Ability4, } +impl Slot { + /// You could use the `strum` crate to derive this automatically! + fn variants() -> impl Iterator { + use Slot::*; + [Primary, Secondary, Ability1, Ability2, Ability3, Ability4] + .iter() + .copied() + } +} + // The list of possible abilities is typically longer than the list of slots #[derive(Actionlike, PartialEq, Eq, Hash, Clone, Debug, Copy, Reflect)] enum Ability { @@ -113,7 +123,7 @@ fn copy_action_state( // 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.clone(), + *matching_ability, slot_state.action_data(&slot).unwrap().clone(), ); } diff --git a/examples/clash_handling.rs b/examples/clash_handling.rs index 7f40ad5f..5f6588ea 100644 --- a/examples/clash_handling.rs +++ b/examples/clash_handling.rs @@ -54,9 +54,5 @@ fn report_pressed_actions( query: Query<&ActionState, Changed>>, ) { let action_state = query.single(); - for action in TestAction::variants() { - if action_state.just_pressed(&action) { - dbg!(action); - } - } + dbg!(action_state.get_just_pressed()); } diff --git a/examples/default_controls.rs b/examples/default_controls.rs index ba878a0a..77fccd56 100644 --- a/examples/default_controls.rs +++ b/examples/default_controls.rs @@ -19,20 +19,27 @@ enum PlayerAction { UseItem, } +impl PlayerAction { + // The `strum` crate provides a deriveable trait for this! + fn variants() -> &'static [PlayerAction] { + &[Self::Run, Self::Jump, Self::UseItem] + } +} + // Exhaustively match `PlayerAction` and define the default binding to the input impl PlayerAction { - fn default_keyboard_mouse_input(action: PlayerAction) -> UserInput { + fn default_keyboard_mouse_input(&self) -> UserInput { // Match against the provided action to get the correct default keyboard-mouse input - match action { + match self { Self::Run => UserInput::VirtualDPad(VirtualDPad::wasd()), Self::Jump => UserInput::Single(InputKind::Keyboard(KeyCode::Space)), Self::UseItem => UserInput::Single(InputKind::Mouse(MouseButton::Left)), } } - fn default_gamepad_input(action: PlayerAction) -> UserInput { + fn default_gamepad_input(&self) -> UserInput { // Match against the provided action to get the correct default gamepad input - match action { + match self { Self::Run => UserInput::Single(InputKind::DualAxis(DualAxis::left_stick())), Self::Jump => UserInput::Single(InputKind::GamepadButton(GamepadButtonType::South)), Self::UseItem => { @@ -52,8 +59,8 @@ fn spawn_player(mut commands: Commands) { // Loop through each action in `PlayerAction` and get the default `UserInput`, // then insert each default input into input_map for action in PlayerAction::variants() { - input_map.insert(action, PlayerAction::default_keyboard_mouse_input(action)); - input_map.insert(action, PlayerAction::default_gamepad_input(action)); + input_map.insert(*action, PlayerAction::default_keyboard_mouse_input(action)); + input_map.insert(*action, PlayerAction::default_gamepad_input(action)); } // Spawn the player with the populated input_map diff --git a/examples/twin_stick_controller.rs b/examples/twin_stick_controller.rs index b692c01d..18c1c6bd 100644 --- a/examples/twin_stick_controller.rs +++ b/examples/twin_stick_controller.rs @@ -38,6 +38,13 @@ pub enum PlayerAction { Shoot, } +impl PlayerAction { + // The `strum` crate provides a deriveable trait for this! + fn variants() -> &'static [PlayerAction] { + &[Self::Move, Self::Look, Self::Shoot] + } +} + // Exhaustively match `PlayerAction` and define the default binding to the input impl PlayerAction { fn default_gamepad_binding(&self) -> UserInput { @@ -51,7 +58,7 @@ impl PlayerAction { } } - fn default_mkb_binding(&self) -> UserInput { + fn default_kbm_binding(&self) -> UserInput { // Match against the provided action to get the correct default gamepad input match self { Self::Move => UserInput::VirtualDPad(VirtualDPad::wasd()), @@ -64,8 +71,8 @@ impl PlayerAction { let mut input_map = InputMap::default(); for variant in PlayerAction::variants() { - input_map.insert(variant, variant.default_mkb_binding()); - input_map.insert(variant, variant.default_gamepad_binding()); + input_map.insert(*variant, variant.default_kbm_binding()); + input_map.insert(*variant, variant.default_gamepad_binding()); } input_map } 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 6650d2d0..1c4a59a7 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -85,8 +85,6 @@ pub struct ActionData { #[derive(Resource, Component, Clone, Debug, PartialEq, Serialize, Deserialize, Reflect)] pub struct ActionState { /// The [`ActionData`] of each action - /// - /// The position in this vector corresponds to [`Actionlike::index`]. action_data: HashMap, } @@ -425,14 +423,14 @@ impl ActionState { /// Consumes all actions #[inline] pub fn consume_all(&mut self) { - for action in A::variants() { + for action in self.keys() { self.consume(&action); } } /// Releases all actions pub fn release_all(&mut self) { - for action in A::variants() { + for action in self.keys() { self.release(&action); } } @@ -492,25 +490,41 @@ impl ActionState { #[must_use] /// Which actions are currently pressed? pub fn get_pressed(&self) -> Vec { - A::variants().filter(|a| self.pressed(a)).collect() + self.action_data + .iter() + .filter(|(_action, data)| data.state.pressed()) + .map(|(action, _data)| action.clone()) + .collect() } #[must_use] /// Which actions were just pressed? pub fn get_just_pressed(&self) -> Vec { - A::variants().filter(|a| self.just_pressed(a)).collect() + self.action_data + .iter() + .filter(|(_action, data)| data.state.just_pressed()) + .map(|(action, _data)| action.clone()) + .collect() } #[must_use] /// Which actions are currently released? pub fn get_released(&self) -> Vec { - A::variants().filter(|a| self.released(a)).collect() + self.action_data + .iter() + .filter(|(_action, data)| data.state.released()) + .map(|(action, _data)| action.clone()) + .collect() } #[must_use] /// Which actions were just released? pub fn get_just_released(&self) -> Vec { - A::variants().filter(|a| self.just_released(a)).collect() + self.action_data + .iter() + .filter(|(_action, data)| data.state.just_released()) + .map(|(action, _data)| action.clone()) + .collect() } /// The [`Instant`] that the action was last pressed or released @@ -582,6 +596,13 @@ impl ActionState { } }; } + + /// Returns an owned list of the [`Actionlike`] keys in this [`ActionState`]. + #[inline] + #[must_use] + pub fn keys(&self) -> Vec { + self.action_data.keys().cloned().collect() + } } /// A component that allows the attached entity to drive the [`ActionState`] of the associated entity diff --git a/src/clashing_inputs.rs b/src/clashing_inputs.rs index 362ea83f..c0b18782 100644 --- a/src/clashing_inputs.rs +++ b/src/clashing_inputs.rs @@ -9,7 +9,6 @@ use crate::Actionlike; use bevy::prelude::Resource; use bevy::utils::HashMap; -use itertools::Itertools; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -104,14 +103,14 @@ impl InputMap { pub(crate) fn possible_clashes(&self) -> Vec> { let mut clashes = Vec::default(); - for action_pair in A::variants().combinations(2) { - let action_a = action_pair.first().unwrap().clone(); - let action_b = action_pair.get(1).unwrap().clone(); - - if let Some(clash) = self.possible_clash(action_a, action_b) { - clashes.push(clash); + for (action_a, _) in self.iter() { + for (action_b, _) in self.iter() { + if let Some(clash) = self.possible_clash(action_a, action_b) { + clashes.push(clash); + } } } + clashes } @@ -151,11 +150,11 @@ impl InputMap { /// If the pair of actions could clash, how? #[must_use] - fn possible_clash(&self, action_a: A, action_b: A) -> Option> { + fn possible_clash(&self, action_a: &A, action_b: &A) -> Option> { let mut clash = Clash::new(action_a.clone(), action_b.clone()); - for input_a in self.get(&action_a)? { - for input_b in self.get(&action_b)? { + for input_a in self.get(action_a)? { + for input_b in self.get(action_b)? { if input_a.clashes(input_b) { clash.inputs_a.push(input_a.clone()); clash.inputs_b.push(input_b.clone()); @@ -175,9 +174,7 @@ impl InputMap { /// as well as the corresponding user inputs #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub(crate) struct Clash { - /// The `Actionlike::index` value corresponding to `action_a` action_a: A, - /// The `Actionlike::index` value corresponding to `action_b` action_b: A, inputs_a: Vec, inputs_b: Vec, @@ -499,7 +496,7 @@ mod tests { fn button_chord_clash_construction() { let input_map = test_input_map(); - let observed_clash = input_map.possible_clash(One, OneAndTwo).unwrap(); + let observed_clash = input_map.possible_clash(&One, &OneAndTwo).unwrap(); let correct_clash = Clash { action_a: One, action_b: OneAndTwo, @@ -515,7 +512,7 @@ mod tests { let input_map = test_input_map(); let observed_clash = input_map - .possible_clash(OneAndTwoAndThree, OneAndTwo) + .possible_clash(&OneAndTwoAndThree, &OneAndTwo) .unwrap(); let correct_clash = Clash { action_a: OneAndTwoAndThree, @@ -531,37 +528,22 @@ mod tests { fn can_clash() { let input_map = test_input_map(); - assert!(input_map.possible_clash(One, Two).is_none()); - assert!(input_map.possible_clash(One, OneAndTwo).is_some()); - assert!(input_map.possible_clash(One, OneAndTwoAndThree).is_some()); - assert!(input_map.possible_clash(One, TwoAndThree).is_none()); + assert!(input_map.possible_clash(&One, &Two).is_none()); + assert!(input_map.possible_clash(&One, &OneAndTwo).is_some()); + assert!(input_map.possible_clash(&One, &OneAndTwoAndThree).is_some()); + assert!(input_map.possible_clash(&One, &TwoAndThree).is_none()); assert!(input_map - .possible_clash(OneAndTwo, OneAndTwoAndThree) + .possible_clash(&OneAndTwo, &OneAndTwoAndThree) .is_some()); } - #[test] - fn clash_caching() { - let mut input_map = test_input_map(); - // Possible clashes are cached upon initialization - assert_eq!(input_map.possible_clashes().len(), 13); - - // Possible clashes are cached upon binding insertion - input_map.insert(Action::Two, UserInput::chord([ControlLeft, AltLeft, Key1])); - assert_eq!(input_map.possible_clashes().len(), 16); - - // Possible clashes are cached upon binding removal - input_map.clear_action(&Action::One); - assert_eq!(input_map.possible_clashes().len(), 10); - } - #[test] fn resolve_prioritize_longest() { let mut app = App::new(); app.add_plugins(InputPlugin); let input_map = test_input_map(); - let simple_clash = input_map.possible_clash(One, OneAndTwo).unwrap(); + let simple_clash = input_map.possible_clash(&One, &OneAndTwo).unwrap(); app.send_input(Key1); app.send_input(Key2); app.update(); @@ -577,7 +559,7 @@ mod tests { Some(One) ); - let reversed_clash = input_map.possible_clash(OneAndTwo, One).unwrap(); + let reversed_clash = input_map.possible_clash(&OneAndTwo, &One).unwrap(); assert_eq!( resolve_clash( &reversed_clash, @@ -588,7 +570,7 @@ mod tests { ); let chord_clash = input_map - .possible_clash(OneAndTwo, OneAndTwoAndThree) + .possible_clash(&OneAndTwo, &OneAndTwoAndThree) .unwrap(); app.send_input(Key3); app.update(); diff --git a/src/input_map.rs b/src/input_map.rs index 7ff662b2..219b06d8 100644 --- a/src/input_map.rs +++ b/src/input_map.rs @@ -245,32 +245,16 @@ impl InputMap { /// /// If the associated gamepads do not match, the resulting associated gamepad will be set to `None`. pub fn merge(&mut self, other: &InputMap) -> &mut Self { - let associated_gamepad = if self.associated_gamepad == other.associated_gamepad { - self.associated_gamepad - } else { - None - }; - - let mut new_map = InputMap { - associated_gamepad, - ..Default::default() - }; - - for action in A::variants() { - if let Some(input_vec) = self.get(&action) { - for input in input_vec { - new_map.insert(action.clone(), input.clone()); - } - } + if self.associated_gamepad != other.associated_gamepad { + self.associated_gamepad = None; + } - if let Some(input_vec) = other.get(&action) { - for input in input_vec { - new_map.insert(action.clone(), input.clone()); - } + for other_action in other.map.iter() { + for input in other_action.1.iter() { + self.insert(other_action.0.clone(), input.clone()); } } - *self = new_map; self } } @@ -329,7 +313,6 @@ impl InputMap { /// Returns the actions that are currently pressed, and the responsible [`UserInput`] for each action /// /// Accounts for clashing inputs according to the [`ClashStrategy`]. - /// The position in each vector corresponds to `Actionlike::index()`. #[must_use] pub fn which_pressed( &self, @@ -389,11 +372,8 @@ impl InputMap { #[must_use] pub fn len(&self) -> usize { let mut i = 0; - for action in A::variants() { - i += match self.get(&action) { - Some(input_vec) => input_vec.len(), - None => 0, - }; + for inputs in self.map.values() { + i += inputs.len(); } i } diff --git a/src/lib.rs b/src/lib.rs index 84da7196..de7d5a9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,6 @@ use crate::input_map::InputMap; use bevy::ecs::prelude::*; use bevy::reflect::{FromReflect, Reflect, TypePath}; use std::hash::Hash; -use std::marker::PhantomData; pub mod action_state; pub mod axislike; @@ -84,59 +83,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 diff --git a/tests/actionlike_derive.rs b/tests/actionlike_derive.rs index b822d561..64938905 100644 --- a/tests/actionlike_derive.rs +++ b/tests/actionlike_derive.rs @@ -31,28 +31,11 @@ enum NamedFieldVariantsAction { Jump, } -#[test] -fn in_order_iteration() { - let constructed_vec = vec![SimpleAction::Zero, SimpleAction::One, SimpleAction::Two]; - let reversed_vec = vec![SimpleAction::Two, SimpleAction::One, SimpleAction::Zero]; - - let iterated_vec: Vec = SimpleAction::variants().collect(); - - assert_eq!(constructed_vec, iterated_vec); - assert!(iterated_vec != reversed_vec); -} - -#[test] -fn get_at() { - assert_eq!(SimpleAction::get_at(0), Some(SimpleAction::Zero)); - assert_eq!(SimpleAction::get_at(1), Some(SimpleAction::One)); - assert_eq!(SimpleAction::get_at(2), Some(SimpleAction::Two)); - assert_eq!(SimpleAction::get_at(3), None); +#[derive(Actionlike, Debug, Hash, PartialEq, Eq, Clone, Copy, Reflect)] +struct StructAction { + x: usize, + y: usize, } -#[test] -fn index() { - assert_eq!(SimpleAction::Zero.index(), 0); - assert_eq!(SimpleAction::One.index(), 1); - assert_eq!(SimpleAction::Two.index(), 2); -} +#[derive(Actionlike, Debug, Hash, PartialEq, Eq, Clone, Copy, Reflect)] +struct TupleAction(usize, usize); diff --git a/tests/clashes.rs b/tests/clashes.rs index dc667f6f..45410d4b 100644 --- a/tests/clashes.rs +++ b/tests/clashes.rs @@ -27,6 +27,21 @@ enum Action { CtrlAltOne, } +impl Action { + fn variants() -> &'static [Action] { + &[ + Self::One, + Self::Two, + Self::OneAndTwo, + Self::TwoAndThree, + Self::OneAndTwoAndThree, + Self::CtrlOne, + Self::AltOne, + Self::CtrlAltOne, + ] + } +} + fn spawn_input_map(mut commands: Commands) { use Action::*; use KeyCode::*; @@ -73,14 +88,14 @@ impl ClashTestExt for App { let keyboard_input = self.world.resource::>(); for action in Action::variants() { - if pressed_actions.contains(&action) { + if pressed_actions.contains(action) { assert!( - input_map.pressed(&action, &InputStreams::from_world(&self.world, None), clash_strategy), + input_map.pressed(action, &InputStreams::from_world(&self.world, None), clash_strategy), "{action:?} was incorrectly not pressed for {clash_strategy:?} when `Input` was \n {keyboard_input:?}." ); } else { assert!( - !input_map.pressed(&action, &InputStreams::from_world(&self.world, None), clash_strategy), + !input_map.pressed(action, &InputStreams::from_world(&self.world, None), clash_strategy), "{action:?} was incorrectly pressed for {clash_strategy:?} when `Input` was \n {keyboard_input:?}" ); } diff --git a/tests/mouse_motion.rs b/tests/mouse_motion.rs index bc88680f..fc37a9e3 100644 --- a/tests/mouse_motion.rs +++ b/tests/mouse_motion.rs @@ -14,6 +14,17 @@ enum ButtonlikeTestAction { Right, } +impl ButtonlikeTestAction { + fn variants() -> &'static [ButtonlikeTestAction] { + &[ + ButtonlikeTestAction::Up, + ButtonlikeTestAction::Down, + ButtonlikeTestAction::Left, + ButtonlikeTestAction::Right, + ] + } +} + #[derive(Actionlike, Clone, Copy, Debug, Reflect, PartialEq, Eq, Hash)] enum AxislikeTestAction { X, @@ -127,13 +138,13 @@ fn mouse_motion_buttonlike() { for action in ButtonlikeTestAction::variants() { let input_map = app.world.resource::>(); // Get the first associated input - let input = input_map.get(&action).unwrap().first().unwrap().clone(); + let input = input_map.get(action).unwrap().first().unwrap().clone(); app.send_input(input.clone()); app.update(); let action_state = app.world.resource::>(); - assert!(action_state.pressed(&action), "failed for {input:?}"); + assert!(action_state.pressed(action), "failed for {input:?}"); } } diff --git a/tests/mouse_wheel.rs b/tests/mouse_wheel.rs index 58460408..b8466cd7 100644 --- a/tests/mouse_wheel.rs +++ b/tests/mouse_wheel.rs @@ -12,6 +12,12 @@ enum ButtonlikeTestAction { Right, } +impl ButtonlikeTestAction { + fn variants() -> &'static [ButtonlikeTestAction] { + &[Self::Up, Self::Down, Self::Left, Self::Right] + } +} + #[derive(Actionlike, Clone, Copy, Debug, Reflect, PartialEq, Eq, Hash)] enum AxislikeTestAction { X, @@ -128,13 +134,13 @@ fn mouse_wheel_buttonlike() { for action in ButtonlikeTestAction::variants() { let input_map = app.world.resource::>(); // Get the first associated input - let input = input_map.get(&action).unwrap().first().unwrap().clone(); + let input = input_map.get(action).unwrap().first().unwrap().clone(); app.send_input(input.clone()); app.update(); let action_state = app.world.resource::>(); - assert!(action_state.pressed(&action), "failed for {input:?}"); + assert!(action_state.pressed(action), "failed for {input:?}"); } }