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) -> bool {
112        let Some(updated_values) = self.updated_values.get(&TypeId::of::<B>()) else {
113            return false;
114        };
115
116        let UpdatedValues::Buttonlike(buttonlikes) = updated_values else {
117            panic!("Expected Buttonlike, found {updated_values:?}");
118        };
119
120        // PERF: surely there's a way to avoid cloning here
121        let boxed_buttonlike: Box<dyn Buttonlike> = Box::new(buttonlike.clone());
122
123        buttonlikes
124            .get(&boxed_buttonlike)
125            .copied()
126            .map(|button| button.pressed)
127            .unwrap_or(false)
128    }
129
130    /// Fetches the value of a [`Buttonlike`] input.
131    ///
132    /// This should be between 0.0 and 1.0, where 0.0 is not pressed and 1.0 is fully pressed.
133    pub fn button_value<B: Buttonlike + Hash + Eq + Clone>(&self, buttonlike: &B) -> f32 {
134        let Some(updated_values) = self.updated_values.get(&TypeId::of::<B>()) else {
135            return 0.0;
136        };
137
138        let UpdatedValues::Buttonlike(buttonlikes) = updated_values else {
139            panic!("Expected Buttonlike, found {updated_values:?}");
140        };
141
142        // PERF: surely there's a way to avoid cloning here
143        let boxed_buttonlike: Box<dyn Buttonlike> = Box::new(buttonlike.clone());
144
145        buttonlikes
146            .get(&boxed_buttonlike)
147            .copied()
148            .map(|button| button.value)
149            .unwrap_or(0.0)
150    }
151
152    /// Fetches the value of an [`Axislike`] input.
153    ///
154    /// 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.
155    pub fn value<A: Axislike + Hash + Eq + Clone>(&self, axislike: &A) -> f32 {
156        let Some(updated_values) = self.updated_values.get(&TypeId::of::<A>()) else {
157            return 0.0;
158        };
159
160        let UpdatedValues::Axislike(axislikes) = updated_values else {
161            panic!("Expected Axislike, found {updated_values:?}");
162        };
163
164        // PERF: surely there's a way to avoid cloning here
165        let boxed_axislike: Box<dyn Axislike> = Box::new(axislike.clone());
166
167        axislikes.get(&boxed_axislike).copied().unwrap_or(0.0)
168    }
169
170    /// Fetches the value of a [`DualAxislike`] input.
171    pub fn pair<D: DualAxislike + Hash + Eq + Clone>(&self, dualaxislike: &D) -> Vec2 {
172        let Some(updated_values) = self.updated_values.get(&TypeId::of::<D>()) else {
173            return Vec2::ZERO;
174        };
175
176        let UpdatedValues::Dualaxislike(dualaxislikes) = updated_values else {
177            panic!("Expected DualAxislike, found {updated_values:?}");
178        };
179
180        // PERF: surely there's a way to avoid cloning here
181        let boxed_dualaxislike: Box<dyn DualAxislike> = Box::new(dualaxislike.clone());
182
183        dualaxislikes
184            .get(&boxed_dualaxislike)
185            .copied()
186            .unwrap_or(Vec2::ZERO)
187    }
188
189    /// Fetches the value of a [`TripleAxislike`] input.
190    pub fn triple<T: TripleAxislike + Hash + Eq + Clone>(&self, tripleaxislike: &T) -> Vec3 {
191        let Some(updated_values) = self.updated_values.get(&TypeId::of::<T>()) else {
192            return Vec3::ZERO;
193        };
194
195        let UpdatedValues::Tripleaxislike(tripleaxislikes) = updated_values else {
196            panic!("Expected TripleAxislike, found {updated_values:?}");
197        };
198
199        // PERF: surely there's a way to avoid cloning here
200        let boxed_tripleaxislike: Box<dyn TripleAxislike> = Box::new(tripleaxislike.clone());
201
202        tripleaxislikes
203            .get(&boxed_tripleaxislike)
204            .copied()
205            .unwrap_or(Vec3::ZERO)
206    }
207}
208
209#[derive(Resource)]
210/// This resource exists for each input type that implements [`UpdatableInput`] (e.g. [`bevy::input::mouse::MouseButton`], [`bevy::input::keyboard::KeyCode`]).
211/// Set [`EnabledInput::is_enabled`] to `false` to disable corresponding input handling.
212pub struct EnabledInput<T> {
213    /// Set this flag to `false` to disable input handling of corresponding events.
214    pub is_enabled: bool,
215    _p: PhantomData<T>,
216}
217
218// SAFETY: The `Resource` derive requires `T` to implement `Send + Sync` as well, but since it's
219// used only as `PhantomData`, it's safe to say that `EnabledInput` is `Send + Sync` regardless of `T`.
220unsafe impl<T> Send for EnabledInput<T> {}
221unsafe impl<T> Sync for EnabledInput<T> {}
222
223impl<T: UpdatableInput> Default for EnabledInput<T> {
224    fn default() -> Self {
225        Self {
226            is_enabled: true,
227            _p: PhantomData,
228        }
229    }
230}
231
232/// Trait for registering updatable inputs with the central input store
233pub trait InputRegistration {
234    /// Registers a new source of raw input data of a matching `kind`.
235    ///
236    /// This will allow the input to be updated based on the state of the world,
237    /// by adding the [`UpdatableInput::compute`] system to [`InputManagerSystem::Unify`] during [`PreUpdate`].
238    ///
239    /// To improve clarity and data consistency, only one kind of input should be registered for each new data stream:
240    /// compute the values of all related inputs from the data stored the [`CentralInputStore`].
241    ///
242    /// This method has no effect if the input kind has already been registered.
243    fn register_input_kind<I: UpdatableInput>(&mut self, kind: InputControlKind);
244}
245
246impl InputRegistration for App {
247    fn register_input_kind<I: UpdatableInput>(&mut self, kind: InputControlKind) {
248        let mut central_input_store = self.world_mut().resource_mut::<CentralInputStore>();
249
250        // Ensure this method is idempotent.
251        if central_input_store
252            .registered_input_kinds
253            .contains(&TypeId::of::<I>())
254        {
255            return;
256        }
257
258        central_input_store.updated_values.insert(
259            TypeId::of::<I>(),
260            UpdatedValues::from_input_control_kind(kind),
261        );
262        central_input_store
263            .registered_input_kinds
264            .insert(TypeId::of::<I>());
265        self.insert_resource(EnabledInput::<I>::default());
266        self.add_systems(
267            PreUpdate,
268            I::compute
269                .in_set(InputManagerSystem::Unify)
270                .run_if(input_is_enabled::<I>),
271        );
272    }
273}
274
275pub(crate) fn input_is_enabled<T: UpdatableInput>(enabled_input: Res<EnabledInput<T>>) -> bool {
276    enabled_input.is_enabled
277}
278
279/// Registers the standard input types defined by [`bevy`] and [`leafwing_input_manager`](crate).
280///
281/// The set of input kinds registered by this method is controlled by the features enabled:
282/// turn off default features to avoid registering input kinds that are not needed.
283#[allow(unused_variables)]
284pub(crate) fn register_standard_input_kinds(app: &mut App) {
285    // Buttonlike
286    #[cfg(feature = "keyboard")]
287    app.register_input_kind::<bevy::input::keyboard::KeyCode>(InputControlKind::Button);
288    #[cfg(feature = "mouse")]
289    app.register_input_kind::<bevy::input::mouse::MouseButton>(InputControlKind::Button);
290    #[cfg(feature = "gamepad")]
291    app.register_input_kind::<bevy::input::gamepad::GamepadButton>(InputControlKind::Button);
292
293    // Axislike
294    #[cfg(feature = "gamepad")]
295    app.register_input_kind::<bevy::input::gamepad::GamepadAxis>(InputControlKind::Axis);
296
297    // Dualaxislike
298    #[cfg(feature = "mouse")]
299    app.register_input_kind::<crate::prelude::MouseMove>(InputControlKind::DualAxis);
300    #[cfg(feature = "mouse")]
301    app.register_input_kind::<crate::prelude::MouseScroll>(InputControlKind::DualAxis);
302}
303
304/// A map of values that have been updated during the current frame.
305///
306/// The key should be the default form of the input if there is no need to differentiate between possible inputs of the same type,
307/// and the value should be the updated value fetched from [`UpdatableInput::SourceData`].
308#[derive(Debug, Reflect)]
309enum UpdatedValues {
310    Buttonlike(HashMap<Box<dyn Buttonlike>, ButtonValue>),
311    Axislike(HashMap<Box<dyn Axislike>, f32>),
312    Dualaxislike(HashMap<Box<dyn DualAxislike>, Vec2>),
313    Tripleaxislike(HashMap<Box<dyn TripleAxislike>, Vec3>),
314}
315
316impl UpdatedValues {
317    fn from_input_control_kind(kind: InputControlKind) -> Self {
318        match kind {
319            InputControlKind::Button => Self::Buttonlike(HashMap::default()),
320            InputControlKind::Axis => Self::Axislike(HashMap::default()),
321            InputControlKind::DualAxis => Self::Dualaxislike(HashMap::default()),
322            InputControlKind::TripleAxis => Self::Tripleaxislike(HashMap::default()),
323        }
324    }
325}
326
327/// A trait that enables user input to be updated based on the state of the world.
328///
329/// This trait is intended to be used for the values stored inside of [`CentralInputStore`].
330/// For the actual user inputs that you might bind actions to, use [`UserInput`](crate::user_input::UserInput) instead.
331///
332/// The values of each [`UserInput`](crate::user_input::UserInput) type will be computed by calling the methods on [`CentralInputStore`],
333/// and so the [`UpdatableInput`] trait is only needed when defining new kinds of input that we can
334/// derive user-facing inputs from.
335///
336/// In simple cases, a type will be both [`UserInput`](crate::user_input::UserInput) and [`UpdatableInput`],
337/// however when configuration is needed (such as for processed axes or virtual d-pads),
338/// two distinct types must be used.
339///
340/// To add a new kind of input, call [`InputRegistration::register_input_kind`] during [`App`] setup.
341pub trait UpdatableInput: 'static {
342    /// The [`SystemParam`] that must be fetched from the world in order to update the user input.
343    ///
344    /// # Panics
345    ///
346    /// This type cannot be [`CentralInputStore`], as that would cause mutable aliasing and panic at runtime.
347    type SourceData: SystemParam;
348
349    /// A system that updates the central store of user input based on the state of the world.
350    ///
351    /// When defining these systems, use the `update` methods on [`CentralInputStore`] to store the new values.
352    ///
353    /// # Warning
354    ///
355    /// This system should not be added manually: instead, call [`InputRegistration::register_input_kind`].
356    fn compute(
357        central_input_store: ResMut<CentralInputStore>,
358        source_data: StaticSystemParam<Self::SourceData>,
359    );
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365    use leafwing_input_manager_macros::Actionlike;
366
367    use crate as leafwing_input_manager;
368    use crate::plugin::{CentralInputStorePlugin, InputManagerPlugin};
369
370    #[derive(Actionlike, Debug, PartialEq, Eq, Hash, Clone, Reflect)]
371    enum TestAction {
372        Run,
373        Jump,
374    }
375
376    #[test]
377    fn central_input_store_is_added_by_plugins() {
378        let mut app = App::new();
379        app.add_plugins(CentralInputStorePlugin);
380        assert!(app.world().contains_resource::<CentralInputStore>());
381
382        let mut app = App::new();
383        app.add_plugins(InputManagerPlugin::<TestAction>::default());
384        assert!(app.world().contains_resource::<CentralInputStore>());
385    }
386
387    #[test]
388    fn number_of_maps_matches_number_of_registered_input_kinds() {
389        let mut app = App::new();
390        app.add_plugins(CentralInputStorePlugin);
391        let central_input_store = app.world().resource::<CentralInputStore>();
392
393        assert_eq!(
394            central_input_store.updated_values.len(),
395            central_input_store.registered_input_kinds.len()
396        );
397    }
398
399    #[cfg(feature = "mouse")]
400    #[test]
401    fn compute_call_updates_central_store() {
402        use bevy::ecs::system::RunSystemOnce;
403        use bevy::prelude::*;
404
405        let mut world = World::new();
406        world.init_resource::<CentralInputStore>();
407
408        // MouseButton has a very straightforward implementation, so we can use it for testing.
409        let mut mouse_button_input = ButtonInput::<MouseButton>::default();
410        mouse_button_input.press(MouseButton::Left);
411        assert!(mouse_button_input.pressed(MouseButton::Left));
412        dbg!(&mouse_button_input);
413        world.insert_resource(mouse_button_input);
414
415        world.run_system_once(MouseButton::compute).unwrap();
416        let central_input_store = world.resource::<CentralInputStore>();
417        dbg!(central_input_store);
418        assert!(central_input_store.pressed(&MouseButton::Left));
419    }
420}