leafwing_input_manager/user_input/
updating.rsuse std::any::TypeId;
use std::hash::Hash;
use bevy::{
app::{App, PreUpdate},
math::{Vec2, Vec3},
prelude::{IntoSystemConfigs, Res, ResMut, Resource},
reflect::Reflect,
utils::{HashMap, HashSet},
};
use crate::{plugin::InputManagerSystem, InputControlKind};
use super::{Axislike, Buttonlike, DualAxislike, TripleAxislike};
#[derive(Resource, Default, Debug, Reflect)]
pub struct CentralInputStore {
updated_values: HashMap<TypeId, UpdatedValues>,
registered_input_kinds: HashSet<TypeId>,
}
impl CentralInputStore {
pub fn register_input_kind<I: UpdatableInput>(
&mut self,
kind: InputControlKind,
app: &mut App,
) {
if self.registered_input_kinds.contains(&TypeId::of::<I>()) {
return;
}
self.updated_values.insert(
TypeId::of::<I>(),
UpdatedValues::from_input_control_kind(kind),
);
self.registered_input_kinds.insert(TypeId::of::<I>());
app.add_systems(PreUpdate, I::compute.in_set(InputManagerSystem::Unify));
}
#[allow(unused_variables)]
pub fn register_standard_input_kinds(&mut self, app: &mut App) {
#[cfg(feature = "keyboard")]
self.register_input_kind::<bevy::input::keyboard::KeyCode>(InputControlKind::Button, app);
#[cfg(feature = "mouse")]
self.register_input_kind::<bevy::input::mouse::MouseButton>(InputControlKind::Button, app);
#[cfg(feature = "gamepad")]
self.register_input_kind::<bevy::input::gamepad::GamepadButton>(
InputControlKind::Button,
app,
);
#[cfg(feature = "gamepad")]
self.register_input_kind::<bevy::input::gamepad::GamepadAxis>(InputControlKind::Axis, app);
#[cfg(feature = "mouse")]
self.register_input_kind::<crate::prelude::MouseMove>(InputControlKind::DualAxis, app);
#[cfg(feature = "mouse")]
self.register_input_kind::<crate::prelude::MouseScroll>(InputControlKind::DualAxis, app);
}
pub fn clear(&mut self) {
for map in self.updated_values.values_mut() {
match map {
UpdatedValues::Buttonlike(buttonlikes) => buttonlikes.clear(),
UpdatedValues::Axislike(axislikes) => axislikes.clear(),
UpdatedValues::Dualaxislike(dualaxislikes) => dualaxislikes.clear(),
UpdatedValues::Tripleaxislike(tripleaxislikes) => tripleaxislikes.clear(),
}
}
}
pub fn update_buttonlike<B: Buttonlike>(&mut self, buttonlike: B, pressed: bool) {
let updated_values = self
.updated_values
.entry(TypeId::of::<B>())
.or_insert_with(|| UpdatedValues::Buttonlike(HashMap::new()));
let UpdatedValues::Buttonlike(buttonlikes) = updated_values else {
panic!("Expected Buttonlike, found {:?}", updated_values);
};
buttonlikes.insert(Box::new(buttonlike), pressed);
}
pub fn update_axislike<A: Axislike>(&mut self, axislike: A, value: f32) {
let updated_values = self
.updated_values
.entry(TypeId::of::<A>())
.or_insert_with(|| UpdatedValues::Axislike(HashMap::new()));
let UpdatedValues::Axislike(axislikes) = updated_values else {
panic!("Expected Axislike, found {:?}", updated_values);
};
axislikes.insert(Box::new(axislike), value);
}
pub fn update_dualaxislike<D: DualAxislike>(&mut self, dualaxislike: D, value: Vec2) {
let updated_values = self
.updated_values
.entry(TypeId::of::<D>())
.or_insert_with(|| UpdatedValues::Dualaxislike(HashMap::new()));
let UpdatedValues::Dualaxislike(dualaxislikes) = updated_values else {
panic!("Expected DualAxislike, found {:?}", updated_values);
};
dualaxislikes.insert(Box::new(dualaxislike), value);
}
pub fn update_tripleaxislike<T: TripleAxislike>(&mut self, tripleaxislike: T, value: Vec3) {
let updated_values = self
.updated_values
.entry(TypeId::of::<T>())
.or_insert_with(|| UpdatedValues::Tripleaxislike(HashMap::new()));
let UpdatedValues::Tripleaxislike(tripleaxislikes) = updated_values else {
panic!("Expected TripleAxislike, found {:?}", updated_values);
};
tripleaxislikes.insert(Box::new(tripleaxislike), value);
}
pub fn pressed<B: Buttonlike + Hash + Eq + Clone>(&self, buttonlike: &B) -> bool {
let Some(updated_values) = self.updated_values.get(&TypeId::of::<B>()) else {
return false;
};
let UpdatedValues::Buttonlike(buttonlikes) = updated_values else {
panic!("Expected Buttonlike, found {:?}", updated_values);
};
let boxed_buttonlike: Box<dyn Buttonlike> = Box::new(buttonlike.clone());
buttonlikes.get(&boxed_buttonlike).copied().unwrap_or(false)
}
pub fn value<A: Axislike + Hash + Eq + Clone>(&self, axislike: &A) -> f32 {
let Some(updated_values) = self.updated_values.get(&TypeId::of::<A>()) else {
return 0.0;
};
let UpdatedValues::Axislike(axislikes) = updated_values else {
panic!("Expected Axislike, found {:?}", updated_values);
};
let boxed_axislike: Box<dyn Axislike> = Box::new(axislike.clone());
axislikes.get(&boxed_axislike).copied().unwrap_or(0.0)
}
pub fn pair<D: DualAxislike + Hash + Eq + Clone>(&self, dualaxislike: &D) -> Vec2 {
let Some(updated_values) = self.updated_values.get(&TypeId::of::<D>()) else {
return Vec2::ZERO;
};
let UpdatedValues::Dualaxislike(dualaxislikes) = updated_values else {
panic!("Expected DualAxislike, found {:?}", updated_values);
};
let boxed_dualaxislike: Box<dyn DualAxislike> = Box::new(dualaxislike.clone());
dualaxislikes
.get(&boxed_dualaxislike)
.copied()
.unwrap_or(Vec2::ZERO)
}
pub fn triple<T: TripleAxislike + Hash + Eq + Clone>(&self, tripleaxislike: &T) -> Vec3 {
let Some(updated_values) = self.updated_values.get(&TypeId::of::<T>()) else {
return Vec3::ZERO;
};
let UpdatedValues::Tripleaxislike(tripleaxislikes) = updated_values else {
panic!("Expected TripleAxislike, found {:?}", updated_values);
};
let boxed_tripleaxislike: Box<dyn TripleAxislike> = Box::new(tripleaxislike.clone());
tripleaxislikes
.get(&boxed_tripleaxislike)
.copied()
.unwrap_or(Vec3::ZERO)
}
}
#[derive(Debug, Reflect)]
enum UpdatedValues {
Buttonlike(HashMap<Box<dyn Buttonlike>, bool>),
Axislike(HashMap<Box<dyn Axislike>, f32>),
Dualaxislike(HashMap<Box<dyn DualAxislike>, Vec2>),
Tripleaxislike(HashMap<Box<dyn TripleAxislike>, Vec3>),
}
impl UpdatedValues {
fn from_input_control_kind(kind: InputControlKind) -> Self {
match kind {
InputControlKind::Button => Self::Buttonlike(HashMap::new()),
InputControlKind::Axis => Self::Axislike(HashMap::new()),
InputControlKind::DualAxis => Self::Dualaxislike(HashMap::new()),
InputControlKind::TripleAxis => Self::Tripleaxislike(HashMap::new()),
}
}
}
pub trait UpdatableInput: 'static {
type SourceData: Resource;
fn compute(central_input_store: ResMut<CentralInputStore>, source_data: Res<Self::SourceData>);
}
#[cfg(test)]
mod tests {
use super::*;
use leafwing_input_manager_macros::Actionlike;
use crate as leafwing_input_manager;
use crate::plugin::{CentralInputStorePlugin, InputManagerPlugin};
#[derive(Actionlike, Debug, PartialEq, Eq, Hash, Clone, Reflect)]
enum TestAction {
Run,
Jump,
}
#[test]
fn central_input_store_is_added_by_plugins() {
let mut app = App::new();
app.add_plugins(CentralInputStorePlugin);
assert!(app.world().contains_resource::<CentralInputStore>());
let mut app = App::new();
app.add_plugins(InputManagerPlugin::<TestAction>::default());
assert!(app.world().contains_resource::<CentralInputStore>());
}
#[test]
fn number_of_maps_matches_number_of_registered_input_kinds() {
let mut app = App::new();
app.add_plugins(CentralInputStorePlugin);
let central_input_store = app.world().resource::<CentralInputStore>();
assert_eq!(
central_input_store.updated_values.len(),
central_input_store.registered_input_kinds.len()
);
}
#[cfg(feature = "mouse")]
#[test]
fn compute_call_updates_central_store() {
use bevy::ecs::system::RunSystemOnce;
use bevy::prelude::*;
let mut world = World::new();
world.init_resource::<CentralInputStore>();
let mut mouse_button_input = ButtonInput::<MouseButton>::default();
mouse_button_input.press(MouseButton::Left);
assert!(mouse_button_input.pressed(MouseButton::Left));
dbg!(&mouse_button_input);
world.insert_resource(mouse_button_input);
world.run_system_once(MouseButton::compute);
let central_input_store = world.resource::<CentralInputStore>();
dbg!(central_input_store);
assert!(central_input_store.pressed(&MouseButton::Left));
}
}