From 516a3b05804cf3d42360f9a2054e9e9c7faba623 Mon Sep 17 00:00:00 2001 From: Shute052 Date: Tue, 10 Sep 2024 20:21:49 +0800 Subject: [PATCH 1/2] Fix the broken deserialization of inputs and `InputMap`s --- RELEASES.md | 1 + macros/src/typetag.rs | 2 +- src/input_map.rs | 72 +++++- src/input_processing/dual_axis/custom.rs | 8 +- src/input_processing/single_axis/custom.rs | 8 +- src/plugin.rs | 42 +-- src/typetag.rs | 51 +++- src/user_input/chord.rs | 8 +- src/user_input/gamepad.rs | 14 +- src/user_input/keyboard.rs | 10 +- src/user_input/mod.rs | 59 +---- src/user_input/mouse.rs | 14 +- src/user_input/trait_reflection.rs | 161 ------------ src/user_input/trait_serde.rs | 287 +++++++++++++++++---- 14 files changed, 407 insertions(+), 330 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 5712cf53..1579882b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -31,6 +31,7 @@ ### Bugs (0.15.1) +- fixed the broken deserialization of inputs and `InputMap`s - `InputMap::get_pressed` and siblings now check if the action kind is buttonlike before checking if they are pressed or released, avoiding a debug-mode panic - `InputMap::merge` is now compatible with all input kinds, previously limited to buttons diff --git a/macros/src/typetag.rs b/macros/src/typetag.rs index 876db479..52b828e3 100644 --- a/macros/src/typetag.rs +++ b/macros/src/typetag.rs @@ -38,7 +38,7 @@ pub(crate) fn expand_serde_typetag(input: &ItemImpl) -> syn::Result impl<'de, #generics_params> #crate_path::typetag::RegisterTypeTag<'de, dyn #trait_path> for #self_ty #where_clause { fn register_typetag( - registry: &mut #crate_path::typetag::MapRegistry, + registry: &mut #crate_path::typetag::InfallibleMapRegistry, ) { #crate_path::typetag::Registry::register( registry, diff --git a/src/input_map.rs b/src/input_map.rs index d9632fb7..9f16841d 100644 --- a/src/input_map.rs +++ b/src/input_map.rs @@ -957,24 +957,17 @@ mod tests { use crate as leafwing_input_manager; use crate::prelude::*; - #[derive( - Actionlike, - Serialize, - Deserialize, - Clone, - Copy, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Debug, - Reflect, - )] + #[derive(Actionlike, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug, Reflect)] enum Action { Run, Jump, Hide, + #[actionlike(Axis)] + Axis, + #[actionlike(DualAxis)] + DualAxis, + #[actionlike(Axis)] + TripleAxis, } #[test] @@ -1117,4 +1110,55 @@ mod tests { input_map.clear_gamepad(); assert_eq!(input_map.gamepad(), None); } + + #[cfg(feature = "keyboard")] + #[test] + fn input_map_serde() { + use bevy::prelude::{App, KeyCode}; + use serde_test::{assert_tokens, Token}; + + let mut app = App::new(); + + // Add the plugin to register input deserializers + app.add_plugins(InputManagerPlugin::::default()); + + let input_map = InputMap::new([(Action::Hide, KeyCode::ControlLeft)]); + assert_tokens( + &input_map, + &[ + Token::Struct { + name: "InputMap", + len: 5, + }, + Token::Str("buttonlike_map"), + Token::Map { len: Some(1) }, + Token::UnitVariant { + name: "Action", + variant: "Hide", + }, + Token::Seq { len: Some(1) }, + Token::Map { len: Some(1) }, + Token::BorrowedStr("KeyCode"), + Token::UnitVariant { + name: "KeyCode", + variant: "ControlLeft", + }, + Token::MapEnd, + Token::SeqEnd, + Token::MapEnd, + Token::Str("axislike_map"), + Token::Map { len: Some(0) }, + Token::MapEnd, + Token::Str("dual_axislike_map"), + Token::Map { len: Some(0) }, + Token::MapEnd, + Token::Str("triple_axislike_map"), + Token::Map { len: Some(0) }, + Token::MapEnd, + Token::Str("associated_gamepad"), + Token::None, + Token::StructEnd, + ], + ); + } } diff --git a/src/input_processing/dual_axis/custom.rs b/src/input_processing/dual_axis/custom.rs index b3f4ec0c..351b536f 100644 --- a/src/input_processing/dual_axis/custom.rs +++ b/src/input_processing/dual_axis/custom.rs @@ -16,10 +16,10 @@ use dyn_hash::DynHash; use once_cell::sync::Lazy; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_flexitos::ser::require_erased_serialize_impl; -use serde_flexitos::{serialize_trait_object, MapRegistry, Registry}; +use serde_flexitos::{serialize_trait_object, Registry}; use crate::input_processing::DualAxisProcessor; -use crate::typetag::RegisterTypeTag; +use crate::typetag::{InfallibleMapRegistry, RegisterTypeTag}; /// A trait for creating custom processor that handles dual-axis input values, /// accepting a [`Vec2`] input and producing a [`Vec2`] output. @@ -287,8 +287,8 @@ impl<'de> Deserialize<'de> for Box { } /// Registry of deserializers for [`CustomDualAxisProcessor`]s. -static mut PROCESSOR_REGISTRY: Lazy>> = - Lazy::new(|| RwLock::new(MapRegistry::new("CustomDualAxisProcessor"))); +static mut PROCESSOR_REGISTRY: Lazy>> = + Lazy::new(|| RwLock::new(InfallibleMapRegistry::new("CustomDualAxisProcessor"))); /// A trait for registering a specific [`CustomDualAxisProcessor`]. pub trait RegisterDualAxisProcessorExt { diff --git a/src/input_processing/single_axis/custom.rs b/src/input_processing/single_axis/custom.rs index f54a6030..4286ede8 100644 --- a/src/input_processing/single_axis/custom.rs +++ b/src/input_processing/single_axis/custom.rs @@ -16,10 +16,10 @@ use dyn_hash::DynHash; use once_cell::sync::Lazy; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_flexitos::ser::require_erased_serialize_impl; -use serde_flexitos::{serialize_trait_object, MapRegistry, Registry}; +use serde_flexitos::{serialize_trait_object, Registry}; use crate::input_processing::AxisProcessor; -use crate::typetag::RegisterTypeTag; +use crate::typetag::{InfallibleMapRegistry, RegisterTypeTag}; /// A trait for creating custom processor that handles single-axis input values, /// accepting a `f32` input and producing a `f32` output. @@ -286,8 +286,8 @@ impl<'de> Deserialize<'de> for Box { } /// Registry of deserializers for [`CustomAxisProcessor`]s. -static mut PROCESSOR_REGISTRY: Lazy>> = - Lazy::new(|| RwLock::new(MapRegistry::new("CustomAxisProcessor"))); +static mut PROCESSOR_REGISTRY: Lazy>> = + Lazy::new(|| RwLock::new(InfallibleMapRegistry::new("CustomAxisProcessor"))); /// A trait for registering a specific [`CustomAxisProcessor`]. pub trait RegisterCustomAxisProcessorExt { diff --git a/src/plugin.rs b/src/plugin.rs index 5734366e..47e51133 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -201,33 +201,33 @@ impl Plugin #[cfg(feature = "mouse")] app.register_type::() .register_type::() - .register_user_input::() - .register_user_input::() - .register_user_input::() - .register_user_input::() - .register_user_input::() - .register_user_input::(); + .register_buttonlike_input::() + .register_axislike_input::() + .register_dual_axislike_input::() + .register_buttonlike_input::() + .register_axislike_input::() + .register_dual_axislike_input::(); #[cfg(feature = "keyboard")] - app.register_user_input::() - .register_user_input::() - .register_user_input::() - .register_user_input::() - .register_user_input::(); + app.register_buttonlike_input::() + .register_buttonlike_input::() + .register_axislike_input::() + .register_dual_axislike_input::() + .register_triple_axislike_input::(); #[cfg(feature = "gamepad")] - app.register_user_input::() - .register_user_input::() - .register_user_input::() - .register_user_input::() - .register_user_input::() - .register_user_input::(); + app.register_buttonlike_input::() + .register_axislike_input::() + .register_dual_axislike_input::() + .register_buttonlike_input::() + .register_axislike_input::() + .register_dual_axislike_input::(); // Chords - app.register_user_input::() - .register_user_input::() - .register_user_input::() - .register_user_input::(); + app.register_buttonlike_input::() + .register_axislike_input::() + .register_dual_axislike_input::() + .register_triple_axislike_input::(); // General-purpose reflection app.register_type::>() diff --git a/src/typetag.rs b/src/typetag.rs index eac99193..fcdcc4b7 100644 --- a/src/typetag.rs +++ b/src/typetag.rs @@ -1,9 +1,54 @@ //! Type tag registration for trait objects -pub use serde_flexitos::{MapRegistry, Registry}; +use std::collections::BTreeMap; + +pub use serde_flexitos::Registry; +use serde_flexitos::{DeserializeFn, GetError}; /// A trait for registering type tags. pub trait RegisterTypeTag<'de, T: ?Sized> { - /// Registers the specified type tag into the [`MapRegistry`]. - fn register_typetag(registry: &mut MapRegistry); + /// Registers the specified type tag into the [`InfallibleMapRegistry`]. + fn register_typetag(registry: &mut InfallibleMapRegistry); +} + +/// An infallible [`Registry`] that allows multiple registrations of deserializers. +pub struct InfallibleMapRegistry { + deserialize_fns: BTreeMap>>, + trait_object_name: &'static str, +} + +impl InfallibleMapRegistry { + /// Creates a new registry, using `trait_object_name` as the name of `O` for diagnostic purposes. + #[inline] + pub fn new(trait_object_name: &'static str) -> Self { + Self { + deserialize_fns: BTreeMap::new(), + trait_object_name, + } + } +} + +impl Registry for InfallibleMapRegistry { + type Identifier = I; + type TraitObject = O; + + #[inline] + fn register(&mut self, id: I, deserialize_fn: DeserializeFn) { + self.deserialize_fns + .entry(id) + .or_insert_with(|| Some(deserialize_fn)); + } + + #[inline] + fn get_deserialize_fn(&self, id: I) -> Result<&DeserializeFn, GetError> { + match self.deserialize_fns.get(&id) { + Some(Some(deserialize_fn)) => Ok(deserialize_fn), + _ => Err(GetError::NotRegistered { id }), + } + } + + #[inline] + fn get_trait_object_name(&self) -> &'static str { + self.trait_object_name + } } diff --git a/src/user_input/chord.rs b/src/user_input/chord.rs index 4f690aef..698ba447 100644 --- a/src/user_input/chord.rs +++ b/src/user_input/chord.rs @@ -104,7 +104,6 @@ impl ButtonlikeChord { } } -#[serde_typetag] impl UserInput for ButtonlikeChord { /// [`ButtonlikeChord`] acts as a virtual button. #[inline] @@ -126,6 +125,7 @@ impl UserInput for ButtonlikeChord { } } +#[serde_typetag] impl Buttonlike for ButtonlikeChord { /// Checks if all the inner inputs within the chord are active simultaneously. #[must_use] @@ -193,7 +193,6 @@ impl AxislikeChord { } } -#[serde_typetag] impl UserInput for AxislikeChord { /// [`AxislikeChord`] acts as a virtual axis. #[inline] @@ -208,6 +207,7 @@ impl UserInput for AxislikeChord { } } +#[serde_typetag] impl Axislike for AxislikeChord { fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 { if self.button.pressed(input_store, gamepad) { @@ -248,7 +248,6 @@ impl DualAxislikeChord { } } -#[serde_typetag] impl UserInput for DualAxislikeChord { /// [`DualAxislikeChord`] acts as a virtual dual-axis. #[inline] @@ -263,6 +262,7 @@ impl UserInput for DualAxislikeChord { } } +#[serde_typetag] impl DualAxislike for DualAxislikeChord { fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec2 { if self.button.pressed(input_store, gamepad) { @@ -309,7 +309,6 @@ impl TripleAxislikeChord { } } -#[serde_typetag] impl UserInput for TripleAxislikeChord { /// [`TripleAxislikeChord`] acts as a virtual triple-axis. #[inline] @@ -324,6 +323,7 @@ impl UserInput for TripleAxislikeChord { } } +#[serde_typetag] impl TripleAxislike for TripleAxislikeChord { fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec3 { if self.button.pressed(input_store, gamepad) { diff --git a/src/user_input/gamepad.rs b/src/user_input/gamepad.rs index 5b805089..3be5d901 100644 --- a/src/user_input/gamepad.rs +++ b/src/user_input/gamepad.rs @@ -119,7 +119,6 @@ impl GamepadControlDirection { pub const RIGHT_RIGHT: Self = Self::positive(GamepadAxisType::RightStickX); } -#[serde_typetag] impl UserInput for GamepadControlDirection { /// [`GamepadControlDirection`] acts as a virtual button. #[inline] @@ -134,6 +133,7 @@ impl UserInput for GamepadControlDirection { } } +#[serde_typetag] impl Buttonlike for GamepadControlDirection { /// Checks if there is any recent stick movement along the specified direction. #[must_use] @@ -199,6 +199,7 @@ impl UserInput for GamepadAxis { } } +#[serde_typetag] impl Axislike for GamepadAxis { fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 { read_axis_value(input_store, gamepad, self.axis_type) @@ -279,7 +280,6 @@ impl GamepadControlAxis { pub const RIGHT_Z: Self = Self::new(GamepadAxisType::RightZ); } -#[serde_typetag] impl UserInput for GamepadControlAxis { /// [`GamepadControlAxis`] acts as an axis input. #[inline] @@ -297,6 +297,7 @@ impl UserInput for GamepadControlAxis { } } +#[serde_typetag] impl Axislike for GamepadControlAxis { /// Retrieves the current value of this axis after processing by the associated processors. #[must_use] @@ -403,7 +404,6 @@ impl GamepadStick { }; } -#[serde_typetag] impl UserInput for GamepadStick { /// [`GamepadStick`] acts as a dual-axis input. #[inline] @@ -423,6 +423,7 @@ impl UserInput for GamepadStick { } } +#[serde_typetag] impl DualAxislike for GamepadStick { /// Retrieves the current X and Y values of this stick after processing by the associated processors. #[must_use] @@ -534,6 +535,7 @@ impl UserInput for GamepadButton { } } +#[serde_typetag] impl Buttonlike for GamepadButton { /// WARNING: The supplied gamepad is ignored, as the button is already specific to a gamepad. fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool { @@ -560,7 +562,6 @@ impl Buttonlike for GamepadButton { } // Built-in support for Bevy's GamepadButtonType. -#[serde_typetag] impl UserInput for GamepadButtonType { /// [`GamepadButtonType`] acts as a button. #[inline] @@ -576,6 +577,7 @@ impl UserInput for GamepadButtonType { } } +#[serde_typetag] impl Buttonlike for GamepadButtonType { /// Checks if the specified button is currently pressed down. #[must_use] @@ -700,7 +702,6 @@ impl GamepadVirtualAxis { pub const ACTION_PAD_Y: Self = Self::new(GamepadButtonType::South, GamepadButtonType::North); } -#[serde_typetag] impl UserInput for GamepadVirtualAxis { /// [`GamepadVirtualAxis`] acts as an axis input. #[inline] @@ -715,6 +716,7 @@ impl UserInput for GamepadVirtualAxis { } } +#[serde_typetag] impl Axislike for GamepadVirtualAxis { /// Retrieves the current value of this axis after processing by the associated processors. #[must_use] @@ -868,7 +870,6 @@ impl GamepadVirtualDPad { ); } -#[serde_typetag] impl UserInput for GamepadVirtualDPad { /// [`GamepadVirtualDPad`] acts as a dual-axis input. #[inline] @@ -888,6 +889,7 @@ impl UserInput for GamepadVirtualDPad { } } +#[serde_typetag] impl DualAxislike for GamepadVirtualDPad { /// Retrieves the current X and Y values of this D-pad after processing by the associated processors. #[must_use] diff --git a/src/user_input/keyboard.rs b/src/user_input/keyboard.rs index 98ae420d..74fef219 100644 --- a/src/user_input/keyboard.rs +++ b/src/user_input/keyboard.rs @@ -19,7 +19,6 @@ use super::updating::{CentralInputStore, UpdatableInput}; use super::{Axislike, Buttonlike, DualAxislike}; // Built-in support for Bevy's KeyCode -#[serde_typetag] impl UserInput for KeyCode { /// [`KeyCode`] acts as a button. #[inline] @@ -52,6 +51,7 @@ impl UpdatableInput for KeyCode { } } +#[serde_typetag] impl Buttonlike for KeyCode { /// Checks if the specified key is currently pressed down. #[must_use] @@ -150,7 +150,6 @@ impl ModifierKey { } } -#[serde_typetag] impl UserInput for ModifierKey { /// [`ModifierKey`] acts as a button. #[inline] @@ -165,6 +164,7 @@ impl UserInput for ModifierKey { } } +#[serde_typetag] impl Buttonlike for ModifierKey { /// Checks if the specified modifier key is currently pressed down. #[must_use] @@ -320,7 +320,6 @@ impl KeyboardVirtualAxis { }; } -#[serde_typetag] impl UserInput for KeyboardVirtualAxis { /// [`KeyboardVirtualAxis`] acts as a virtual axis input. #[inline] @@ -335,6 +334,7 @@ impl UserInput for KeyboardVirtualAxis { } } +#[serde_typetag] impl Axislike for KeyboardVirtualAxis { /// Retrieves the current value of this axis after processing by the associated processors. #[must_use] @@ -498,7 +498,6 @@ impl KeyboardVirtualDPad { }; } -#[serde_typetag] impl UserInput for KeyboardVirtualDPad { /// [`KeyboardVirtualDPad`] acts as a virtual dual-axis input. #[inline] @@ -518,6 +517,7 @@ impl UserInput for KeyboardVirtualDPad { } } +#[serde_typetag] impl DualAxislike for KeyboardVirtualDPad { /// Retrieves the current X and Y values of this D-pad after processing by the associated processors. #[must_use] @@ -626,7 +626,6 @@ impl KeyboardVirtualDPad3D { } } -#[serde_typetag] impl UserInput for KeyboardVirtualDPad3D { /// [`KeyboardVirtualDPad3D`] acts as a virtual triple-axis input. #[inline] @@ -648,6 +647,7 @@ impl UserInput for KeyboardVirtualDPad3D { } } +#[serde_typetag] impl TripleAxislike for KeyboardVirtualDPad3D { /// Retrieves the current X, Y, and Z values of this D-pad. #[must_use] diff --git a/src/user_input/mod.rs b/src/user_input/mod.rs index e1c23ffe..7f45e49c 100644 --- a/src/user_input/mod.rs +++ b/src/user_input/mod.rs @@ -111,48 +111,7 @@ mod trait_serde; pub mod updating; /// A trait for defining the behavior expected from different user input sources. -/// -/// Implementers of this trait should provide methods for accessing and processing user input data. -/// -/// # Examples -/// -/// ```rust -/// use std::hash::{Hash, Hasher}; -/// use bevy::prelude::*; -/// use bevy::math::{Vec2, FloatOrd}; -/// use serde::{Deserialize, Serialize}; -/// use leafwing_input_manager::prelude::*; -/// use leafwing_input_manager::axislike::{DualAxisType}; -/// use leafwing_input_manager::clashing_inputs::BasicInputs; -/// -/// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] -/// pub struct MouseScrollAlwaysFiveOnYAxis; -/// -/// // Add this attribute for ensuring proper serialization and deserialization. -/// #[serde_typetag] -/// impl UserInput for MouseScrollAlwaysFiveOnYAxis { -/// fn kind(&self) -> InputControlKind { -/// // Returns the kind of input this represents. -/// // -/// // In this case, it represents an axial input. -/// InputControlKind::Axis -/// } -/// -/// fn decompose(&self) -> BasicInputs { -/// // Gets the most basic form of this input for clashing input detection. -/// // -/// // This input is not buttonlike, so it uses `None`. -/// BasicInputs::None -/// } -/// } -/// -/// // Remember to register your input - it will ensure everything works smoothly! -/// let mut app = App::new(); -/// app.register_user_input::(); -/// ``` -pub trait UserInput: - Send + Sync + Debug + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize -{ +pub trait UserInput: Send + Sync + Debug { /// Defines the kind of behavior that the input should be. fn kind(&self) -> InputControlKind; @@ -167,7 +126,9 @@ pub trait UserInput: } /// A trait used for buttonlike user inputs, which can be pressed or released. -pub trait Buttonlike: UserInput { +pub trait Buttonlike: + UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize +{ /// Checks if the input is currently active. fn pressed(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> bool; @@ -216,7 +177,9 @@ pub trait Buttonlike: UserInput { } /// A trait used for axis-like user inputs, which provide a continuous value. -pub trait Axislike: UserInput { +pub trait Axislike: + UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize +{ /// Gets the current value of the input as an `f32`. fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32; @@ -241,7 +204,9 @@ pub trait Axislike: UserInput { } /// A trait used for dual-axis-like user inputs, which provide separate X and Y values. -pub trait DualAxislike: UserInput { +pub trait DualAxislike: + UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize +{ /// Gets the values of this input along the X and Y axes (if applicable). fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec2; @@ -266,7 +231,9 @@ pub trait DualAxislike: UserInput { } /// A trait used for triple-axis-like user inputs, which provide separate X, Y, and Z values. -pub trait TripleAxislike: UserInput { +pub trait TripleAxislike: + UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize +{ /// Gets the values of this input along the X, Y, and Z axes (if applicable). fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec3; diff --git a/src/user_input/mouse.rs b/src/user_input/mouse.rs index c0dde0ee..ef1a086c 100644 --- a/src/user_input/mouse.rs +++ b/src/user_input/mouse.rs @@ -18,7 +18,6 @@ use super::updating::{CentralInputStore, UpdatableInput}; use super::{Axislike, Buttonlike, DualAxislike}; // Built-in support for Bevy's MouseButton -#[serde_typetag] impl UserInput for MouseButton { /// [`MouseButton`] acts as a button. #[inline] @@ -51,6 +50,7 @@ impl UpdatableInput for MouseButton { } } +#[serde_typetag] impl Buttonlike for MouseButton { /// Checks if the specified button is currently pressed down. #[inline] @@ -130,7 +130,6 @@ impl MouseMoveDirection { pub const RIGHT: Self = Self(DualAxisDirection::Right); } -#[serde_typetag] impl UserInput for MouseMoveDirection { /// [`MouseMoveDirection`] acts as a virtual button. #[inline] @@ -145,6 +144,7 @@ impl UserInput for MouseMoveDirection { } } +#[serde_typetag] impl Buttonlike for MouseMoveDirection { /// Checks if there is any recent mouse movement along the specified direction. #[must_use] @@ -223,7 +223,6 @@ impl MouseMoveAxis { }; } -#[serde_typetag] impl UserInput for MouseMoveAxis { /// [`MouseMoveAxis`] acts as an axis input. #[inline] @@ -241,6 +240,7 @@ impl UserInput for MouseMoveAxis { } } +#[serde_typetag] impl Axislike for MouseMoveAxis { /// Retrieves the amount of the mouse movement along the specified axis /// after processing by the associated processors. @@ -335,7 +335,6 @@ impl UpdatableInput for MouseMove { } } -#[serde_typetag] impl UserInput for MouseMove { /// [`MouseMove`] acts as a dual-axis input. #[inline] @@ -355,6 +354,7 @@ impl UserInput for MouseMove { } } +#[serde_typetag] impl DualAxislike for MouseMove { /// Retrieves the mouse displacement after processing by the associated processors. #[must_use] @@ -440,7 +440,6 @@ impl MouseScrollDirection { pub const RIGHT: Self = Self(DualAxisDirection::Right); } -#[serde_typetag] impl UserInput for MouseScrollDirection { /// [`MouseScrollDirection`] acts as a virtual button. #[inline] @@ -455,6 +454,7 @@ impl UserInput for MouseScrollDirection { } } +#[serde_typetag] impl Buttonlike for MouseScrollDirection { /// Checks if there is any recent mouse wheel movement along the specified direction. #[must_use] @@ -540,7 +540,6 @@ impl MouseScrollAxis { }; } -#[serde_typetag] impl UserInput for MouseScrollAxis { /// [`MouseScrollAxis`] acts as an axis input. #[inline] @@ -558,6 +557,7 @@ impl UserInput for MouseScrollAxis { } } +#[serde_typetag] impl Axislike for MouseScrollAxis { /// Retrieves the amount of the mouse wheel movement along the specified axis /// after processing by the associated processors. @@ -664,7 +664,6 @@ impl UpdatableInput for MouseScroll { } } -#[serde_typetag] impl UserInput for MouseScroll { /// [`MouseScroll`] acts as an axis input. #[inline] @@ -684,6 +683,7 @@ impl UserInput for MouseScroll { } } +#[serde_typetag] impl DualAxislike for MouseScroll { /// Retrieves the mouse scroll movement on both axes after processing by the associated processors. #[must_use] diff --git a/src/user_input/trait_reflection.rs b/src/user_input/trait_reflection.rs index 912ff17a..fce961e6 100644 --- a/src/user_input/trait_reflection.rs +++ b/src/user_input/trait_reflection.rs @@ -17,167 +17,6 @@ use bevy::reflect::{ use dyn_eq::DynEq; -mod user_input { - use super::*; - - use crate::user_input::UserInput; - - dyn_clone::clone_trait_object!(UserInput); - dyn_eq::eq_trait_object!(UserInput); - dyn_hash::hash_trait_object!(UserInput); - - impl Reflect for Box { - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(Self::type_info()) - } - - fn into_any(self: Box) -> Box { - self - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn into_reflect(self: Box) -> Box { - self - } - - fn as_reflect(&self) -> &dyn Reflect { - self - } - - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } - - fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), bevy::reflect::ApplyError> { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { - *self = value.clone(); - Ok(()) - } else { - Err(bevy::reflect::ApplyError::MismatchedTypes { - from_type: self - .reflect_type_ident() - .unwrap_or_default() - .to_string() - .into_boxed_str(), - to_type: self - .reflect_type_ident() - .unwrap_or_default() - .to_string() - .into_boxed_str(), - }) - } - } - - fn apply(&mut self, value: &dyn Reflect) { - Self::try_apply(self, value).unwrap(); - } - - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } - - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Value - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Value(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Value(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Value(self) - } - - fn clone_value(&self) -> Box { - Box::new(self.clone()) - } - - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - let type_id = TypeId::of::(); - Hash::hash(&type_id, &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) - } - - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - value - .as_any() - .downcast_ref::() - .map(|value| self.dyn_eq(value)) - .or(Some(false)) - } - - fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) - } - } - - impl Typed for Box { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::())) - } - } - - impl TypePath for Box { - fn type_path() -> &'static str { - static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| { - { - format!("std::boxed::Box", module_path!()) - } - }) - } - - fn short_type_path() -> &'static str { - static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| "Box".to_string()) - } - - fn type_ident() -> Option<&'static str> { - Some("Box") - } - - fn crate_name() -> Option<&'static str> { - module_path!().split(':').next() - } - - fn module_path() -> Option<&'static str> { - Some(module_path!()) - } - } - - impl GetTypeRegistration for Box { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration - } - } - - impl FromReflect for Box { - fn from_reflect(reflect: &dyn Reflect) -> Option { - Some(reflect.as_any().downcast_ref::()?.clone()) - } - } -} - mod buttonlike { use super::*; diff --git a/src/user_input/trait_serde.rs b/src/user_input/trait_serde.rs index 80797090..d125ad4d 100644 --- a/src/user_input/trait_serde.rs +++ b/src/user_input/trait_serde.rs @@ -3,81 +3,94 @@ use std::sync::RwLock; use bevy::app::App; +use bevy::prelude::TypePath; use bevy::reflect::GetTypeRegistration; use once_cell::sync::Lazy; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_flexitos::ser::require_erased_serialize_impl; -use serde_flexitos::{serialize_trait_object, MapRegistry, Registry}; +use serde_flexitos::{serialize_trait_object, Registry}; -use crate::typetag::RegisterTypeTag; - -use super::{Axislike, Buttonlike, DualAxislike, TripleAxislike, UserInput}; - -/// Registry of deserializers for [`UserInput`]s. -static mut USER_INPUT_REGISTRY: Lazy>> = - Lazy::new(|| RwLock::new(MapRegistry::new("UserInput"))); +use super::{Axislike, Buttonlike, DualAxislike, TripleAxislike}; +use crate::typetag::{InfallibleMapRegistry, RegisterTypeTag}; /// Registry of deserializers for [`Buttonlike`]s. -static mut BUTTONLIKE_REGISTRY: Lazy>> = - Lazy::new(|| RwLock::new(MapRegistry::new("Buttonlike"))); +static mut BUTTONLIKE_REGISTRY: Lazy>> = + Lazy::new(|| RwLock::new(InfallibleMapRegistry::new("Buttonlike"))); /// Registry of deserializers for [`Axislike`]s. -static mut AXISLIKE_REGISTRY: Lazy>> = - Lazy::new(|| RwLock::new(MapRegistry::new("Axislike"))); +static mut AXISLIKE_REGISTRY: Lazy>> = + Lazy::new(|| RwLock::new(InfallibleMapRegistry::new("Axislike"))); /// Registry of deserializers for [`DualAxislike`]s. -static mut DUAL_AXISLIKE_REGISTRY: Lazy>> = - Lazy::new(|| RwLock::new(MapRegistry::new("DualAxislike"))); +static mut DUAL_AXISLIKE_REGISTRY: Lazy>> = + Lazy::new(|| RwLock::new(InfallibleMapRegistry::new("DualAxislike"))); /// Registry of deserializers for [`TripleAxislike`]s. -static mut TRIPLE_AXISLIKE_REGISTRY: Lazy>> = - Lazy::new(|| RwLock::new(MapRegistry::new("TripleAxislike"))); +static mut TRIPLE_AXISLIKE_REGISTRY: Lazy>> = + Lazy::new(|| RwLock::new(InfallibleMapRegistry::new("TripleAxislike"))); -/// A trait for registering a specific [`UserInput`]. +/// A trait for registering inputs. pub trait RegisterUserInput { - /// Registers the specified [`UserInput`]. - fn register_user_input<'de, T>(&mut self) -> &mut Self + /// Registers the specified [`Buttonlike`]. + fn register_buttonlike_input<'de, T>(&mut self) -> &mut Self + where + T: RegisterTypeTag<'de, dyn Buttonlike> + GetTypeRegistration + TypePath; + + /// Registers the specified [`Axislike`]. + fn register_axislike_input<'de, T>(&mut self) -> &mut Self + where + T: RegisterTypeTag<'de, dyn Axislike> + GetTypeRegistration + TypePath; + + /// Registers the specified [`DualAxislike`]. + fn register_dual_axislike_input<'de, T>(&mut self) -> &mut Self + where + T: RegisterTypeTag<'de, dyn DualAxislike> + GetTypeRegistration; + + /// Registers the specified [`TripleAxislike`]. + fn register_triple_axislike_input<'de, T>(&mut self) -> &mut Self where - T: RegisterTypeTag<'de, dyn UserInput> + GetTypeRegistration; + T: RegisterTypeTag<'de, dyn TripleAxislike> + GetTypeRegistration; } impl RegisterUserInput for App { - fn register_user_input<'de, T>(&mut self) -> &mut Self + fn register_buttonlike_input<'de, T>(&mut self) -> &mut Self where - T: RegisterTypeTag<'de, dyn UserInput> + GetTypeRegistration, + T: RegisterTypeTag<'de, dyn Buttonlike> + GetTypeRegistration, { - let mut registry = unsafe { USER_INPUT_REGISTRY.write().unwrap() }; + let mut registry = unsafe { BUTTONLIKE_REGISTRY.write().unwrap() }; T::register_typetag(&mut registry); self.register_type::(); self } -} -mod user_input { - use super::*; + fn register_axislike_input<'de, T>(&mut self) -> &mut Self + where + T: RegisterTypeTag<'de, dyn Axislike> + GetTypeRegistration, + { + let mut registry = unsafe { AXISLIKE_REGISTRY.write().unwrap() }; + T::register_typetag(&mut registry); + self.register_type::(); + self + } - impl<'a> Serialize for dyn UserInput + 'a { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - // Check that `UserInput` has `erased_serde::Serialize` as a super trait, - // preventing infinite recursion at runtime. - const fn __check_erased_serialize_super_trait() { - require_erased_serialize_impl::(); - } - serialize_trait_object(serializer, self.reflect_short_type_path(), self) - } + fn register_dual_axislike_input<'de, T>(&mut self) -> &mut Self + where + T: RegisterTypeTag<'de, dyn DualAxislike> + GetTypeRegistration, + { + let mut registry = unsafe { DUAL_AXISLIKE_REGISTRY.write().unwrap() }; + T::register_typetag(&mut registry); + self.register_type::(); + self } - impl<'de> Deserialize<'de> for Box { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let registry = unsafe { USER_INPUT_REGISTRY.read().unwrap() }; - registry.deserialize_trait_object(deserializer) - } + fn register_triple_axislike_input<'de, T>(&mut self) -> &mut Self + where + T: RegisterTypeTag<'de, dyn TripleAxislike> + GetTypeRegistration, + { + let mut registry = unsafe { TRIPLE_AXISLIKE_REGISTRY.write().unwrap() }; + T::register_typetag(&mut registry); + self.register_type::(); + self } } @@ -91,9 +104,9 @@ mod buttonlike { where S: Serializer, { - // Check that `UserInput` has `erased_serde::Serialize` as a super trait, + // Check that `Buttonlike` has `erased_serde::Serialize` as a super trait, // preventing infinite recursion at runtime. - const fn __check_erased_serialize_super_trait() { + const fn __check_erased_serialize_super_trait() { require_erased_serialize_impl::(); } serialize_trait_object(serializer, self.reflect_short_type_path(), self) @@ -121,9 +134,9 @@ mod axislike { where S: Serializer, { - // Check that `UserInput` has `erased_serde::Serialize` as a super trait, + // Check that `Axislike` has `erased_serde::Serialize` as a super trait, // preventing infinite recursion at runtime. - const fn __check_erased_serialize_super_trait() { + const fn __check_erased_serialize_super_trait() { require_erased_serialize_impl::(); } serialize_trait_object(serializer, self.reflect_short_type_path(), self) @@ -151,9 +164,9 @@ mod dualaxislike { where S: Serializer, { - // Check that `UserInput` has `erased_serde::Serialize` as a super trait, + // Check that `DualAxislike` has `erased_serde::Serialize` as a super trait, // preventing infinite recursion at runtime. - const fn __check_erased_serialize_super_trait() { + const fn __check_erased_serialize_super_trait() { require_erased_serialize_impl::(); } serialize_trait_object(serializer, self.reflect_short_type_path(), self) @@ -181,9 +194,9 @@ mod tripleaxislike { where S: Serializer, { - // Check that `UserInput` has `erased_serde::Serialize` as a super trait, + // Check that `TripleAxislike` has `erased_serde::Serialize` as a super trait, // preventing infinite recursion at runtime. - const fn __check_erased_serialize_super_trait() { + const fn __check_erased_serialize_super_trait() { require_erased_serialize_impl::(); } serialize_trait_object(serializer, self.reflect_short_type_path(), self) @@ -200,3 +213,169 @@ mod tripleaxislike { } } } + +#[cfg(any(feature = "keyboard", feature = "mouse"))] +#[cfg(test)] +mod tests { + use crate as leafwing_input_manager; + use bevy::prelude::{App, Reflect}; + use leafwing_input_manager_macros::Actionlike; + + #[derive(Actionlike, Debug, Clone, PartialEq, Eq, Hash, Reflect)] + pub enum Action { + Foo, + } + + fn register_input_deserializers() { + let mut app = App::new(); + + // Add the plugin to register input deserializers + app.add_plugins(crate::prelude::InputManagerPlugin::::default()); + } + + #[cfg(feature = "keyboard")] + #[test] + fn test_button_serde() { + use crate::prelude::Buttonlike; + use bevy::prelude::KeyCode; + use serde_test::{assert_tokens, Token}; + + register_input_deserializers(); + + let boxed_input: Box = Box::new(KeyCode::KeyB); + assert_tokens( + &boxed_input, + &[ + Token::Map { len: Some(1) }, + Token::BorrowedStr("KeyCode"), + Token::UnitVariant { + name: "KeyCode", + variant: "KeyB", + }, + Token::MapEnd, + ], + ); + } + + #[cfg(feature = "mouse")] + #[test] + fn test_axis_serde() { + use crate::prelude::{Axislike, MouseScrollAxis}; + use serde_test::{assert_tokens, Token}; + + register_input_deserializers(); + + let boxed_input: Box = Box::new(MouseScrollAxis::Y); + assert_tokens( + &boxed_input, + &[ + Token::Map { len: Some(1) }, + Token::BorrowedStr("MouseScrollAxis"), + Token::Struct { + name: "MouseScrollAxis", + len: 2, + }, + Token::BorrowedStr("axis"), + Token::Enum { + name: "DualAxisType", + }, + Token::Str("Y"), + Token::Unit, + Token::BorrowedStr("processors"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + Token::MapEnd, + ], + ); + } + + #[cfg(feature = "mouse")] + #[test] + fn test_dual_axis_serde() { + use crate::prelude::{DualAxislike, MouseMove}; + use serde_test::{assert_tokens, Token}; + + register_input_deserializers(); + + let boxed_input: Box = Box::new(MouseMove::default()); + assert_tokens( + &boxed_input, + &[ + Token::Map { len: Some(1) }, + Token::BorrowedStr("MouseMove"), + Token::Struct { + name: "MouseMove", + len: 1, + }, + Token::Str("processors"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + Token::MapEnd, + ], + ); + } + + #[cfg(feature = "keyboard")] + #[test] + fn test_triple_axis_serde() { + use crate::prelude::{KeyboardVirtualDPad3D, TripleAxislike}; + use bevy::prelude::KeyCode; + use serde_test::{assert_tokens, Token}; + + register_input_deserializers(); + + let boxed_input: Box = Box::new(KeyboardVirtualDPad3D::new( + KeyCode::KeyW, + KeyCode::KeyS, + KeyCode::KeyA, + KeyCode::KeyD, + KeyCode::KeyF, + KeyCode::KeyB, + )); + assert_tokens( + &boxed_input, + &[ + Token::Map { len: Some(1) }, + Token::BorrowedStr("KeyboardVirtualDPad3D"), + Token::Struct { + name: "KeyboardVirtualDPad3D", + len: 6, + }, + Token::Str("up"), + Token::UnitVariant { + name: "KeyCode", + variant: "KeyW", + }, + Token::Str("down"), + Token::UnitVariant { + name: "KeyCode", + variant: "KeyS", + }, + Token::Str("left"), + Token::UnitVariant { + name: "KeyCode", + variant: "KeyA", + }, + Token::Str("right"), + Token::UnitVariant { + name: "KeyCode", + variant: "KeyD", + }, + Token::Str("forward"), + Token::UnitVariant { + name: "KeyCode", + variant: "KeyF", + }, + Token::Str("backward"), + Token::UnitVariant { + name: "KeyCode", + variant: "KeyB", + }, + Token::StructEnd, + Token::MapEnd, + ], + ); + } +} From 2cf06d1c17dba2aef88d5719fa32d6453f79dd9e Mon Sep 17 00:00:00 2001 From: Shute052 Date: Tue, 10 Sep 2024 21:32:44 +0800 Subject: [PATCH 2/2] docs --- src/typetag.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/typetag.rs b/src/typetag.rs index fcdcc4b7..313ab526 100644 --- a/src/typetag.rs +++ b/src/typetag.rs @@ -11,7 +11,8 @@ pub trait RegisterTypeTag<'de, T: ?Sized> { fn register_typetag(registry: &mut InfallibleMapRegistry); } -/// An infallible [`Registry`] that allows multiple registrations of deserializers. +/// An infallible version of [`MapRegistry`](serde_flexitos::MapRegistry) +/// that allows multiple registrations of deserializers. pub struct InfallibleMapRegistry { deserialize_fns: BTreeMap>>, trait_object_name: &'static str,