Repository: Leafwing-Studios/leafwing-input-manager (rust)

Issue:
Remove `variants` and related methods from `Actionlike`
              There are still quite a bit of `A::variants()` usage left. I'm kinda surprised that it has not broken anything else like it did. Is it within the scope of this pr to remove usage of `A::variants()` as you mentioned on Discord? The removal of `A::variants()` will not be trivial, I think. For `InputMap` it can use `iter()`, but `ActionState` doesn't have access to the map so it will require some thought.

_Originally posted by @100-TomatoJuice in https://github.com/Leafwing-Studios/leafwing-input-manager/issues/400#issuecomment-1806846091_
            

Pull request description:
Remove methods from `Actionlike` to support more complex Action types
Fixes #412. Previously attempted in #415.

Turns out, doing one thing at once and using CI is helpful.

Patch:
diff --git a/RELEASES.md b/RELEASES.md
index 53e1906..b781b91 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 eae6b98..7bbed10 100644
--- a/benches/action_state.rs
+++ b/benches/action_state.rs
@@ -21,6 +21,13 @@ enum TestAction {
     J,
 }
 
+impl TestAction {
+    fn variants() -> impl Iterator<Item = TestAction> {
+        use TestAction::*;
+        [A, B, C, D, E, F, G, H, I, J].iter().copied()
+    }
+}
+
 fn pressed(action_state: &ActionState<TestAction>) -> bool {
     action_state.pressed(&TestAction::A)
 }
diff --git a/examples/arpg_indirection.rs b/examples/arpg_indirection.rs
index b5ac287..9af9a61 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<Item = Slot> {
+        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 7f40ad5..5f6588e 100644
--- a/examples/clash_handling.rs
+++ b/examples/clash_handling.rs
@@ -54,9 +54,5 @@ fn report_pressed_actions(
     query: Query<&ActionState<TestAction>, Changed<ActionState<TestAction>>>,
 ) {
     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 ba878a0..77fccd5 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 b692c01..18c1c6b 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 e715217..6adcc79 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<Self> {
-                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 6650d2d..1c4a59a 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<A: Actionlike> {
     /// The [`ActionData`] of each action
-    ///
-    /// The position in this vector corresponds to [`Actionlike::index`].
     action_data: HashMap<A, ActionData>,
 }
 
@@ -425,14 +423,14 @@ impl<A: Actionlike> ActionState<A> {
     /// 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<A: Actionlike> ActionState<A> {
     #[must_use]
     /// Which actions are currently pressed?
     pub fn get_pressed(&self) -> Vec<A> {
-        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> {
-        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> {
-        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> {
-        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<A: Actionlike> ActionState<A> {
             }
         };
     }
+
+    /// Returns an owned list of the [`Actionlike`] keys in this [`ActionState`].
+    #[inline]
+    #[must_use]
+    pub fn keys(&self) -> Vec<A> {
+        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 362ea83..c0b1878 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<A: Actionlike> InputMap<A> {
     pub(crate) fn possible_clashes(&self) -> Vec<Clash<A>> {
         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<A: Actionlike> InputMap<A> {
 
     /// If the pair of actions could clash, how?
     #[must_use]
-    fn possible_clash(&self, action_a: A, action_b: A) -> Option<Clash<A>> {
+    fn possible_clash(&self, action_a: &A, action_b: &A) -> Option<Clash<A>> {
         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<A: Actionlike> InputMap<A> {
 /// as well as the corresponding user inputs
 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
 pub(crate) struct Clash<A: Actionlike> {
-    /// 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<UserInput>,
     inputs_b: Vec<UserInput>,
@@ -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 7ff662b..219b06d 100644
--- a/src/input_map.rs
+++ b/src/input_map.rs
@@ -245,32 +245,16 @@ impl<A: Actionlike> InputMap<A> {
     ///
     /// If the associated gamepads do not match, the resulting associated gamepad will be set to `None`.
     pub fn merge(&mut self, other: &InputMap<A>) -> &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<A: Actionlike> InputMap<A> {
     /// 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<A: Actionlike> InputMap<A> {
     #[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 84da719..de7d5a9 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<Self> {
-        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<Self>;
-
-    /// 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<A: Actionlike> {
-    index: usize,
-    _phantom: PhantomData<A>,
-}
-
-impl<A: Actionlike> Iterator for ActionIter<A> {
-    type Item = A;
-
-    fn next(&mut self) -> Option<A> {
-        let item = A::get_at(self.index);
-        if item.is_some() {
-            self.index += 1;
-        }
-
-        item
-    }
-}
-
-impl<A: Actionlike> ExactSizeIterator for ActionIter<A> {
-    fn len(&self) -> usize {
-        A::n_variants()
-    }
-}
-
-// We can't derive this, because otherwise it won't work when A is not default
-impl<A: Actionlike> Default for ActionIter<A> {
-    fn default() -> Self {
-        ActionIter {
-            index: 0,
-            _phantom: PhantomData,
-        }
-    }
 }
 
 /// This [`Bundle`] allows entities to collect and interpret inputs from across input sources


Test patch:
diff --git a/tests/actionlike_derive.rs b/tests/actionlike_derive.rs
index b822d56..6493890 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> = 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 dc667f6..45410d4 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::<Input<KeyCode>>();
 
         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<KeyCode>` 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<KeyCode>` was \n {keyboard_input:?}"
                 );
             }
diff --git a/tests/mouse_motion.rs b/tests/mouse_motion.rs
index bc88680..fc37a9e 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::<InputMap<ButtonlikeTestAction>>();
         // 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::<ActionState<ButtonlikeTestAction>>();
-        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 5846040..b8466cd 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::<InputMap<ButtonlikeTestAction>>();
         // 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::<ActionState<ButtonlikeTestAction>>();
-        assert!(action_state.pressed(&action), "failed for {input:?}");
+        assert!(action_state.pressed(action), "failed for {input:?}");
     }
 }