Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix broken deserialization for inputs and InputMaps #622

Merged
merged 2 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion macros/src/typetag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub(crate) fn expand_serde_typetag(input: &ItemImpl) -> syn::Result<TokenStream>

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<dyn #trait_path>,
registry: &mut #crate_path::typetag::InfallibleMapRegistry<dyn #trait_path>,
) {
#crate_path::typetag::Registry::register(
registry,
Expand Down
72 changes: 58 additions & 14 deletions src/input_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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::<Action>::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,
],
);
}
}
8 changes: 4 additions & 4 deletions src/input_processing/dual_axis/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -287,8 +287,8 @@ impl<'de> Deserialize<'de> for Box<dyn CustomDualAxisProcessor> {
}

/// Registry of deserializers for [`CustomDualAxisProcessor`]s.
static mut PROCESSOR_REGISTRY: Lazy<RwLock<MapRegistry<dyn CustomDualAxisProcessor>>> =
Lazy::new(|| RwLock::new(MapRegistry::new("CustomDualAxisProcessor")));
static mut PROCESSOR_REGISTRY: Lazy<RwLock<InfallibleMapRegistry<dyn CustomDualAxisProcessor>>> =
Lazy::new(|| RwLock::new(InfallibleMapRegistry::new("CustomDualAxisProcessor")));

/// A trait for registering a specific [`CustomDualAxisProcessor`].
pub trait RegisterDualAxisProcessorExt {
Expand Down
8 changes: 4 additions & 4 deletions src/input_processing/single_axis/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -286,8 +286,8 @@ impl<'de> Deserialize<'de> for Box<dyn CustomAxisProcessor> {
}

/// Registry of deserializers for [`CustomAxisProcessor`]s.
static mut PROCESSOR_REGISTRY: Lazy<RwLock<MapRegistry<dyn CustomAxisProcessor>>> =
Lazy::new(|| RwLock::new(MapRegistry::new("CustomAxisProcessor")));
static mut PROCESSOR_REGISTRY: Lazy<RwLock<InfallibleMapRegistry<dyn CustomAxisProcessor>>> =
Lazy::new(|| RwLock::new(InfallibleMapRegistry::new("CustomAxisProcessor")));

/// A trait for registering a specific [`CustomAxisProcessor`].
pub trait RegisterCustomAxisProcessorExt {
Expand Down
42 changes: 21 additions & 21 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,33 +201,33 @@ impl<A: Actionlike + TypePath + bevy::reflect::GetTypeRegistration> Plugin
#[cfg(feature = "mouse")]
app.register_type::<AccumulatedMouseMovement>()
.register_type::<AccumulatedMouseScroll>()
.register_user_input::<MouseMoveDirection>()
.register_user_input::<MouseMoveAxis>()
.register_user_input::<MouseMove>()
.register_user_input::<MouseScrollDirection>()
.register_user_input::<MouseScrollAxis>()
.register_user_input::<MouseScroll>();
.register_buttonlike_input::<MouseMoveDirection>()
.register_axislike_input::<MouseMoveAxis>()
.register_dual_axislike_input::<MouseMove>()
.register_buttonlike_input::<MouseScrollDirection>()
.register_axislike_input::<MouseScrollAxis>()
.register_dual_axislike_input::<MouseScroll>();

#[cfg(feature = "keyboard")]
app.register_user_input::<KeyCode>()
.register_user_input::<ModifierKey>()
.register_user_input::<KeyboardVirtualAxis>()
.register_user_input::<KeyboardVirtualDPad>()
.register_user_input::<KeyboardVirtualDPad3D>();
app.register_buttonlike_input::<KeyCode>()
.register_buttonlike_input::<ModifierKey>()
.register_axislike_input::<KeyboardVirtualAxis>()
.register_dual_axislike_input::<KeyboardVirtualDPad>()
.register_triple_axislike_input::<KeyboardVirtualDPad3D>();

#[cfg(feature = "gamepad")]
app.register_user_input::<GamepadControlDirection>()
.register_user_input::<GamepadControlAxis>()
.register_user_input::<GamepadStick>()
.register_user_input::<GamepadButtonType>()
.register_user_input::<GamepadVirtualAxis>()
.register_user_input::<GamepadVirtualDPad>();
app.register_buttonlike_input::<GamepadControlDirection>()
.register_axislike_input::<GamepadControlAxis>()
.register_dual_axislike_input::<GamepadStick>()
.register_buttonlike_input::<GamepadButtonType>()
.register_axislike_input::<GamepadVirtualAxis>()
.register_dual_axislike_input::<GamepadVirtualDPad>();

// Chords
app.register_user_input::<ButtonlikeChord>()
.register_user_input::<AxislikeChord>()
.register_user_input::<DualAxislikeChord>()
.register_user_input::<TripleAxislikeChord>();
app.register_buttonlike_input::<ButtonlikeChord>()
.register_axislike_input::<AxislikeChord>()
.register_dual_axislike_input::<DualAxislikeChord>()
.register_triple_axislike_input::<TripleAxislikeChord>();

// General-purpose reflection
app.register_type::<ActionState<A>>()
Expand Down
52 changes: 49 additions & 3 deletions src/typetag.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,55 @@
//! 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<T>);
/// Registers the specified type tag into the [`InfallibleMapRegistry`].
fn register_typetag(registry: &mut InfallibleMapRegistry<T>);
}

/// An infallible version of [`MapRegistry`](serde_flexitos::MapRegistry)
/// that allows multiple registrations of deserializers.
pub struct InfallibleMapRegistry<O: ?Sized, I = &'static str> {
deserialize_fns: BTreeMap<I, Option<DeserializeFn<O>>>,
trait_object_name: &'static str,
}

impl<O: ?Sized, I> InfallibleMapRegistry<O, I> {
/// 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<O: ?Sized, I: Ord> Registry for InfallibleMapRegistry<O, I> {
type Identifier = I;
type TraitObject = O;

#[inline]
fn register(&mut self, id: I, deserialize_fn: DeserializeFn<O>) {
self.deserialize_fns
.entry(id)
.or_insert_with(|| Some(deserialize_fn));
}

#[inline]
fn get_deserialize_fn(&self, id: I) -> Result<&DeserializeFn<O>, GetError<I>> {
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
}
}
8 changes: 4 additions & 4 deletions src/user_input/chord.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ impl ButtonlikeChord {
}
}

#[serde_typetag]
impl UserInput for ButtonlikeChord {
/// [`ButtonlikeChord`] acts as a virtual button.
#[inline]
Expand All @@ -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]
Expand Down Expand Up @@ -193,7 +193,6 @@ impl AxislikeChord {
}
}

#[serde_typetag]
impl UserInput for AxislikeChord {
/// [`AxislikeChord`] acts as a virtual axis.
#[inline]
Expand All @@ -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) {
Expand Down Expand Up @@ -248,7 +248,6 @@ impl DualAxislikeChord {
}
}

#[serde_typetag]
impl UserInput for DualAxislikeChord {
/// [`DualAxislikeChord`] acts as a virtual dual-axis.
#[inline]
Expand All @@ -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) {
Expand Down Expand Up @@ -309,7 +309,6 @@ impl TripleAxislikeChord {
}
}

#[serde_typetag]
impl UserInput for TripleAxislikeChord {
/// [`TripleAxislikeChord`] acts as a virtual triple-axis.
#[inline]
Expand All @@ -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) {
Expand Down
Loading