leafwing_input_manager/user_input/
updating.rs

1//! Logic for updating user input based on the state of the world.
2
3use std::any::TypeId;
4use std::hash::Hash;
5use std::marker::PhantomData;
6
7use bevy::{
8    app::{App, PreUpdate},
9    ecs::{
10        schedule::IntoScheduleConfigs,
11        system::{StaticSystemParam, SystemParam},
12    },
13    math::{Vec2, Vec3},
14    platform::collections::{HashMap, HashSet},
15    prelude::{Res, ResMut, Resource},
16    reflect::Reflect,
17};
18
19use super::{Axislike, Buttonlike, DualAxislike, TripleAxislike};
20use crate::buttonlike::ButtonValue;
21use crate::{plugin::InputManagerSystem, InputControlKind};
22
23/// An overarching store for all user inputs.
24///
25/// This resource allows values to be updated and fetched in a single location,
26/// and ensures that their values are only recomputed once per frame.
27///
28/// To add a new kind of input, call [`InputRegistration::register_input_kind`] during [`App`] setup.
29#[derive(Resource, Default, Debug, Reflect)]
30pub struct CentralInputStore {
31    /// Stores the updated values of each kind of input.
32    updated_values: HashMap<TypeId, UpdatedValues>,
33    /// Tracks the input kinds that have been registered, to avoid redundant system additions.
34    registered_input_kinds: HashSet<TypeId>,
35}
36
37impl CentralInputStore {
38    /// Clears all existing values.
39    ///
40    /// This should be called once at the start of each frame, before polling for new input.
41    pub fn clear(&mut self) {
42        // Clear the values inside of each map:
43        // the base maps can be reused, but the values inside them need to be replaced each frame.
44        for map in self.updated_values.values_mut() {
45            match map {
46                UpdatedValues::Buttonlike(buttonlikes) => buttonlikes.clear(),
47                UpdatedValues::Axislike(axislikes) => axislikes.clear(),
48                UpdatedValues::Dualaxislike(dualaxislikes) => dualaxislikes.clear(),
49                UpdatedValues::Tripleaxislike(tripleaxislikes) => tripleaxislikes.clear(),
50            }
51        }
52    }
53
54    /// Updates the value of a [`Buttonlike`] input.
55    pub fn update_buttonlike<B: Buttonlike>(&mut self, buttonlike: B, value: ButtonValue) {
56        let updated_values = self
57            .updated_values
58            .entry(TypeId::of::<B>())
59            .or_insert_with(|| UpdatedValues::Buttonlike(HashMap::default()));
60
61        let UpdatedValues::Buttonlike(buttonlikes) = updated_values else {
62            panic!("Expected Buttonlike, found {updated_values:?}");
63        };
64
65        buttonlikes.insert(Box::new(buttonlike), value);
66    }
67
68    /// Updates the value of an [`Axislike`] input.
69    pub fn update_axislike<A: Axislike>(&mut self, axislike: A, value: f32) {
70        let updated_values = self
71            .updated_values
72            .entry(TypeId::of::<A>())
73            .or_insert_with(|| UpdatedValues::Axislike(HashMap::default()));
74
75        let UpdatedValues::Axislike(axislikes) = updated_values else {
76            panic!("Expected Axislike, found {updated_values:?}");
77        };
78
79        axislikes.insert(Box::new(axislike), value);
80    }
81
82    /// Updates the value of a [`DualAxislike`] input.
83    pub fn update_dualaxislike<D: DualAxislike>(&mut self, dualaxislike: D, value: Vec2) {
84        let updated_values = self
85            .updated_values
86            .entry(TypeId::of::<D>())
87            .or_insert_with(|| UpdatedValues::Dualaxislike(HashMap::default()));
88
89        let UpdatedValues::Dualaxislike(dualaxislikes) = updated_values else {
90            panic!("Expected DualAxislike, found {updated_values:?}");
91        };
92
93        dualaxislikes.insert(Box::new(dualaxislike), value);
94    }
95
96    /// Updates the value of a [`TripleAxislike`] input.
97    pub fn update_tripleaxislike<T: TripleAxislike>(&mut self, tripleaxislike: T, value: Vec3) {
98        let updated_values = self
99            .updated_values
100            .entry(TypeId::of::<T>())
101            .or_insert_with(|| UpdatedValues::Tripleaxislike(HashMap::default()));
102
103        let UpdatedValues::Tripleaxislike(tripleaxislikes) = updated_values else {
104            panic!("Expected TripleAxislike, found {updated_values:?}");
105        };
106
107        tripleaxislikes.insert(Box::new(tripleaxislike), value);
108    }
109
110    /// Check if a [`Buttonlike`] input is currently pressing.
111    pub fn pressed<B: Buttonlike + Hash + Eq + Clone>(&self, buttonlike: &B) -> Option<bool> {
112        let updated_values = self.updated_values.get(&TypeId::of::<B>())?;
113
114        let UpdatedValues::Buttonlike(buttonlikes) = updated_values else {
115            panic!("Expected Buttonlike, found {updated_values:?}");
116        };
117
118        // PERF: surely there's a way to avoid cloning here
119        let boxed_buttonlike: Box<dyn Buttonlike> = Box::new(buttonlike.clone());
120
121        buttonlikes
122            .get(&boxed_buttonlike)
123            .copied()
124            .map(|button| button.pressed)
125    }
126
127    /// Fetches the value of a [`Buttonlike`] input.
128    ///
129    /// This should be between 0.0 and 1.0, where 0.0 is not pressed and 1.0 is fully pressed.
130    pub fn button_value<B: Buttonlike + Hash + Eq + Clone>(&self, buttonlike: &B) -> f32 {
131        let Some(updated_values) = self.updated_values.get(&TypeId::of::<B>()) else {
132            return 0.0;
133        };
134
135        let UpdatedValues::Buttonlike(buttonlikes) = updated_values else {
136            panic!("Expected Buttonlike, found {updated_values:?}");
137        };
138
139        // PERF: surely there's a way to avoid cloning here
140        let boxed_buttonlike: Box<dyn Buttonlike> = Box::new(buttonlike.clone());
141
142        buttonlikes
143            .get(&boxed_buttonlike)
144            .copied()
145            .map(|button| button.value)
146            .unwrap_or(0.0)
147    }
148
149    /// Fetches the value of an [`Axislike`] input.
150    ///
151    /// This should be between -1.0 and 1.0, where -1.0 is fully left or down and 1.0 is fully right or up.
152    pub fn value<A: Axislike + Hash + Eq + Clone>(&self, axislike: &A) -> Option<f32> {
153        let updated_values = self.updated_values.get(&TypeId::of::<A>())?;
154
155        let UpdatedValues::Axislike(axislikes) = updated_values else {
156            panic!("Expected Axislike, found {updated_values:?}");
157        };
158
159        // PERF: surely there's a way to avoid cloning here
160        let boxed_axislike: Box<dyn Axislike> = Box::new(axislike.clone());
161
162        axislikes.get(&boxed_axislike).copied()
163    }
164
165    /// Fetches the value of a [`DualAxislike`] input.
166    pub fn pair<D: DualAxislike + Hash + Eq + Clone>(&self, dualaxislike: &D) -> Option<Vec2> {
167        let updated_values = self.updated_values.get(&TypeId::of::<D>())?;
168
169        let UpdatedValues::Dualaxislike(dualaxislikes) = updated_values else {
170            panic!("Expected DualAxislike, found {updated_values:?}");
171        };
172
173        // PERF: surely there's a way to avoid cloning here
174        let boxed_dualaxislike: Box<dyn DualAxislike> = Box::new(dualaxislike.clone());
175
176        dualaxislikes.get(&boxed_dualaxislike).copied()
177    }
178
179    /// Fetches the value of a [`TripleAxislike`] input.
180    pub fn triple<T: TripleAxislike + Hash + Eq + Clone>(&self, tripleaxislike: &T) -> Vec3 {
181        let Some(updated_values) = self.updated_values.get(&TypeId::of::<T>()) else {
182            return Vec3::ZERO;
183        };
184
185        let UpdatedValues::Tripleaxislike(tripleaxislikes) = updated_values else {
186            panic!("Expected TripleAxislike, found {updated_values:?}");
187        };
188
189        // PERF: surely there's a way to avoid cloning here
190        let boxed_tripleaxislike: Box<dyn TripleAxislike> = Box::new(tripleaxislike.clone());
191
192        tripleaxislikes
193            .get(&boxed_tripleaxislike)
194            .copied()
195            .unwrap_or(Vec3::ZERO)
196    }
197}
198
199#[derive(Resource)]
200/// This resource exists for each input type that implements [`UpdatableInput`] (e.g. [`bevy::input::mouse::MouseButton`], [`bevy::input::keyboard::KeyCode`]).
201/// Set [`EnabledInput::is_enabled`] to `false` to disable corresponding input handling.
202pub struct EnabledInput<T> {
203    /// Set this flag to `false` to disable input handling of corresponding messages.
204    pub is_enabled: bool,
205    _p: PhantomData<T>,
206}
207
208// SAFETY: The `Resource` derive requires `T` to implement `Send + Sync` as well, but since it's
209// used only as `PhantomData`, it's safe to say that `EnabledInput` is `Send + Sync` regardless of `T`.
210unsafe impl<T> Send for EnabledInput<T> {}
211unsafe impl<T> Sync for EnabledInput<T> {}
212
213impl<T: UpdatableInput> Default for EnabledInput<T> {
214    fn default() -> Self {
215        Self {
216            is_enabled: true,
217            _p: PhantomData,
218        }
219    }
220}
221
222/// Trait for registering updatable inputs with the central input store
223pub trait InputRegistration {
224    /// Registers a new source of raw input data of a matching `kind`.
225    ///
226    /// This will allow the input to be updated based on the state of the world,
227    /// by adding the [`UpdatableInput::compute`] system to [`InputManagerSystem::Unify`] during [`PreUpdate`].
228    ///
229    /// To improve clarity and data consistency, only one kind of input should be registered for each new data stream:
230    /// compute the values of all related inputs from the data stored the [`CentralInputStore`].
231    ///
232    /// This method has no effect if the input kind has already been registered.
233    fn register_input_kind<I: UpdatableInput>(&mut self, kind: InputControlKind) -> &mut App;
234}
235
236impl InputRegistration for App {
237    fn register_input_kind<I: UpdatableInput>(&mut self, kind: InputControlKind) -> &mut App {
238        let mut central_input_store = self.world_mut().resource_mut::<CentralInputStore>();
239
240        // Ensure this method is idempotent.
241        if central_input_store
242            .registered_input_kinds
243            .contains(&TypeId::of::<I>())
244        {
245            return self;
246        }
247
248        central_input_store.updated_values.insert(
249            TypeId::of::<I>(),
250            UpdatedValues::from_input_control_kind(kind),
251        );
252        central_input_store
253            .registered_input_kinds
254            .insert(TypeId::of::<I>());
255        self.insert_resource(EnabledInput::<I>::default());
256        self.add_systems(
257            PreUpdate,
258            I::compute
259                .in_set(InputManagerSystem::Unify)
260                .run_if(input_is_enabled::<I>),
261        )
262    }
263}
264
265pub(crate) fn input_is_enabled<T: UpdatableInput>(enabled_input: Res<EnabledInput<T>>) -> bool {
266    enabled_input.is_enabled
267}
268
269/// Registers the standard input types defined by [`bevy`] and [`leafwing_input_manager`](crate).
270///
271/// The set of input kinds registered by this method is controlled by the features enabled:
272/// turn off default features to avoid registering input kinds that are not needed.
273#[allow(unused_variables)]
274pub(crate) fn register_standard_input_kinds(app: &mut App) {
275    // Buttonlike
276    #[cfg(feature = "keyboard")]
277    app.register_input_kind::<bevy::input::keyboard::KeyCode>(InputControlKind::Button);
278    #[cfg(feature = "mouse")]
279    app.register_input_kind::<bevy::input::mouse::MouseButton>(InputControlKind::Button);
280    #[cfg(feature = "gamepad")]
281    app.register_input_kind::<bevy::input::gamepad::GamepadButton>(InputControlKind::Button);
282
283    // Axislike
284    #[cfg(feature = "gamepad")]
285    app.register_input_kind::<bevy::input::gamepad::GamepadAxis>(InputControlKind::Axis);
286
287    // Dualaxislike
288    #[cfg(feature = "mouse")]
289    app.register_input_kind::<crate::prelude::MouseMove>(InputControlKind::DualAxis);
290    #[cfg(feature = "mouse")]
291    app.register_input_kind::<crate::prelude::MouseScroll>(InputControlKind::DualAxis);
292}
293
294/// A map of values that have been updated during the current frame.
295///
296/// The key should be the default form of the input if there is no need to differentiate between possible inputs of the same type,
297/// and the value should be the updated value fetched from [`UpdatableInput::SourceData`].
298#[derive(Debug, Reflect)]
299enum UpdatedValues {
300    Buttonlike(HashMap<Box<dyn Buttonlike>, ButtonValue>),
301    Axislike(HashMap<Box<dyn Axislike>, f32>),
302    Dualaxislike(HashMap<Box<dyn DualAxislike>, Vec2>),
303    Tripleaxislike(HashMap<Box<dyn TripleAxislike>, Vec3>),
304}
305
306impl UpdatedValues {
307    fn from_input_control_kind(kind: InputControlKind) -> Self {
308        match kind {
309            InputControlKind::Button => Self::Buttonlike(HashMap::default()),
310            InputControlKind::Axis => Self::Axislike(HashMap::default()),
311            InputControlKind::DualAxis => Self::Dualaxislike(HashMap::default()),
312            InputControlKind::TripleAxis => Self::Tripleaxislike(HashMap::default()),
313        }
314    }
315}
316
317/// A trait that enables user input to be updated based on the state of the world.
318///
319/// This trait is intended to be used for the values stored inside of [`CentralInputStore`].
320/// For the actual user inputs that you might bind actions to, use [`UserInput`](crate::user_input::UserInput) instead.
321///
322/// The values of each [`UserInput`](crate::user_input::UserInput) type will be computed by calling the methods on [`CentralInputStore`],
323/// and so the [`UpdatableInput`] trait is only needed when defining new kinds of input that we can
324/// derive user-facing inputs from.
325///
326/// In simple cases, a type will be both [`UserInput`](crate::user_input::UserInput) and [`UpdatableInput`],
327/// however when configuration is needed (such as for processed axes or virtual d-pads),
328/// two distinct types must be used.
329///
330/// To add a new kind of input, call [`InputRegistration::register_input_kind`] during [`App`] setup.
331pub trait UpdatableInput: 'static {
332    /// The [`SystemParam`] that must be fetched from the world in order to update the user input.
333    ///
334    /// # Panics
335    ///
336    /// This type cannot be [`CentralInputStore`], as that would cause mutable aliasing and panic at runtime.
337    type SourceData: SystemParam;
338
339    /// A system that updates the central store of user input based on the state of the world.
340    ///
341    /// When defining these systems, use the `update` methods on [`CentralInputStore`] to store the new values.
342    ///
343    /// # Warning
344    ///
345    /// This system should not be added manually: instead, call [`InputRegistration::register_input_kind`].
346    fn compute(
347        central_input_store: ResMut<CentralInputStore>,
348        source_data: StaticSystemParam<Self::SourceData>,
349    );
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355    use leafwing_input_manager_macros::Actionlike;
356
357    use crate as leafwing_input_manager;
358    use crate::plugin::{CentralInputStorePlugin, InputManagerPlugin};
359
360    #[derive(Actionlike, Debug, PartialEq, Eq, Hash, Clone, Reflect)]
361    enum TestAction {
362        Run,
363        Jump,
364    }
365
366    #[test]
367    fn central_input_store_is_added_by_plugins() {
368        let mut app = App::new();
369        app.add_plugins(CentralInputStorePlugin);
370        assert!(app.world().contains_resource::<CentralInputStore>());
371
372        let mut app = App::new();
373        app.add_plugins(InputManagerPlugin::<TestAction>::default());
374        assert!(app.world().contains_resource::<CentralInputStore>());
375    }
376
377    #[test]
378    fn number_of_maps_matches_number_of_registered_input_kinds() {
379        let mut app = App::new();
380        app.add_plugins(CentralInputStorePlugin);
381        let central_input_store = app.world().resource::<CentralInputStore>();
382
383        assert_eq!(
384            central_input_store.updated_values.len(),
385            central_input_store.registered_input_kinds.len()
386        );
387    }
388
389    #[cfg(feature = "mouse")]
390    #[test]
391    fn compute_call_updates_central_store() {
392        use bevy::ecs::system::RunSystemOnce;
393        use bevy::prelude::*;
394
395        let mut world = World::new();
396        world.init_resource::<CentralInputStore>();
397
398        // MouseButton has a very straightforward implementation, so we can use it for testing.
399        let mut mouse_button_input = ButtonInput::<MouseButton>::default();
400        mouse_button_input.press(MouseButton::Left);
401        assert!(mouse_button_input.pressed(MouseButton::Left));
402        dbg!(&mouse_button_input);
403        world.insert_resource(mouse_button_input);
404
405        world.run_system_once(MouseButton::compute).unwrap();
406        let central_input_store = world.resource::<CentralInputStore>();
407        dbg!(central_input_store);
408        assert_eq!(central_input_store.pressed(&MouseButton::Left), Some(true));
409    }
410}