leafwing_input_manager/user_input/
updating.rs1use 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#[derive(Resource, Default, Debug, Reflect)]
30pub struct CentralInputStore {
31 updated_values: HashMap<TypeId, UpdatedValues>,
33 registered_input_kinds: HashSet<TypeId>,
35}
36
37impl CentralInputStore {
38 pub fn clear(&mut self) {
42 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 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 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 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 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 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 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 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 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 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 let boxed_axislike: Box<dyn Axislike> = Box::new(axislike.clone());
161
162 axislikes.get(&boxed_axislike).copied()
163 }
164
165 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 let boxed_dualaxislike: Box<dyn DualAxislike> = Box::new(dualaxislike.clone());
175
176 dualaxislikes.get(&boxed_dualaxislike).copied()
177 }
178
179 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 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)]
200pub struct EnabledInput<T> {
203 pub is_enabled: bool,
205 _p: PhantomData<T>,
206}
207
208unsafe 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
222pub trait InputRegistration {
224 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 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#[allow(unused_variables)]
274pub(crate) fn register_standard_input_kinds(app: &mut App) {
275 #[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 #[cfg(feature = "gamepad")]
285 app.register_input_kind::<bevy::input::gamepad::GamepadAxis>(InputControlKind::Axis);
286
287 #[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#[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
317pub trait UpdatableInput: 'static {
332 type SourceData: SystemParam;
338
339 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 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}