use std::any::TypeId;
use std::hash::Hash;
use std::marker::PhantomData;
use bevy::{
app::{App, PreUpdate},
ecs::{
schedule::IntoScheduleConfigs,
system::{StaticSystemParam, SystemParam},
},
math::{Vec2, Vec3},
platform::collections::{HashMap, HashSet},
prelude::{Res, ResMut, Resource},
reflect::Reflect,
};
use super::{Axislike, Buttonlike, DualAxislike, TripleAxislike};
use crate::buttonlike::ButtonValue;
use crate::{InputControlKind, plugin::InputManagerSystem};
#[derive(Resource, Default, Debug, Reflect)]
pub struct CentralInputStore {
updated_values: HashMap<TypeId, UpdatedValues>,
registered_input_kinds: HashSet<TypeId>,
}
impl CentralInputStore {
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, value: ButtonValue) {
let updated_values = self
.updated_values
.entry(TypeId::of::<B>())
.or_insert_with(|| UpdatedValues::Buttonlike(HashMap::default()));
let UpdatedValues::Buttonlike(buttonlikes) = updated_values else {
panic!("Expected Buttonlike, found {updated_values:?}");
};
buttonlikes.insert(Box::new(buttonlike), value);
}
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::default()));
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::default()));
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::default()));
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) -> Option<bool> {
let updated_values = self.updated_values.get(&TypeId::of::<B>())?;
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()
.map(|button| button.pressed)
}
pub fn button_value<B: Buttonlike + Hash + Eq + Clone>(&self, buttonlike: &B) -> f32 {
let Some(updated_values) = self.updated_values.get(&TypeId::of::<B>()) else {
return 0.0;
};
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()
.map(|button| button.value)
.unwrap_or(0.0)
}
pub fn value<A: Axislike + Hash + Eq + Clone>(&self, axislike: &A) -> Option<f32> {
let updated_values = self.updated_values.get(&TypeId::of::<A>())?;
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()
}
pub fn pair<D: DualAxislike + Hash + Eq + Clone>(&self, dualaxislike: &D) -> Option<Vec2> {
let updated_values = self.updated_values.get(&TypeId::of::<D>())?;
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()
}
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(Resource)]
pub struct EnabledInput<T> {
pub is_enabled: bool,
_p: PhantomData<T>,
}
unsafe impl<T> Send for EnabledInput<T> {}
unsafe impl<T> Sync for EnabledInput<T> {}
impl<T: UpdatableInput> Default for EnabledInput<T> {
fn default() -> Self {
Self {
is_enabled: true,
_p: PhantomData,
}
}
}
pub trait InputRegistration {
fn register_input_kind<I: UpdatableInput>(&mut self, kind: InputControlKind) -> &mut App;
}
impl InputRegistration for App {
fn register_input_kind<I: UpdatableInput>(&mut self, kind: InputControlKind) -> &mut App {
let mut central_input_store = self.world_mut().resource_mut::<CentralInputStore>();
if central_input_store
.registered_input_kinds
.contains(&TypeId::of::<I>())
{
return self;
}
central_input_store.updated_values.insert(
TypeId::of::<I>(),
UpdatedValues::from_input_control_kind(kind),
);
central_input_store
.registered_input_kinds
.insert(TypeId::of::<I>());
self.insert_resource(EnabledInput::<I>::default());
self.add_systems(
PreUpdate,
I::compute
.in_set(InputManagerSystem::Unify)
.run_if(input_is_enabled::<I>),
)
}
}
pub(crate) fn input_is_enabled<T: UpdatableInput>(enabled_input: Res<EnabledInput<T>>) -> bool {
enabled_input.is_enabled
}
#[allow(unused_variables)]
pub(crate) fn register_standard_input_kinds(app: &mut App) {
#[cfg(feature = "keyboard")]
app.register_input_kind::<bevy::input::keyboard::KeyCode>(InputControlKind::Button);
#[cfg(feature = "mouse")]
app.register_input_kind::<bevy::input::mouse::MouseButton>(InputControlKind::Button);
#[cfg(feature = "gamepad")]
app.register_input_kind::<bevy::input::gamepad::GamepadButton>(InputControlKind::Button);
#[cfg(feature = "gamepad")]
app.register_input_kind::<bevy::input::gamepad::GamepadAxis>(InputControlKind::Axis);
#[cfg(feature = "mouse")]
app.register_input_kind::<crate::prelude::MouseMove>(InputControlKind::DualAxis);
#[cfg(feature = "mouse")]
app.register_input_kind::<crate::prelude::MouseScroll>(InputControlKind::DualAxis);
}
#[derive(Debug, Reflect)]
enum UpdatedValues {
Buttonlike(HashMap<Box<dyn Buttonlike>, ButtonValue>),
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::default()),
InputControlKind::Axis => Self::Axislike(HashMap::default()),
InputControlKind::DualAxis => Self::Dualaxislike(HashMap::default()),
InputControlKind::TripleAxis => Self::Tripleaxislike(HashMap::default()),
}
}
}
pub trait UpdatableInput: 'static {
type SourceData: SystemParam;
fn compute(
central_input_store: ResMut<CentralInputStore>,
source_data: StaticSystemParam<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).unwrap();
let central_input_store = world.resource::<CentralInputStore>();
dbg!(central_input_store);
assert_eq!(central_input_store.pressed(&MouseButton::Left), Some(true));
}
}