Skip to content

Commit

Permalink
Fix broken deserialization for inputs and InputMaps (#622)
Browse files Browse the repository at this point in the history
* Fix the broken deserialization of inputs and `InputMap`s

* docs
  • Loading branch information
Shute052 authored Sep 10, 2024
1 parent 7d500aa commit cd50ba1
Show file tree
Hide file tree
Showing 14 changed files with 408 additions and 330 deletions.
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

0 comments on commit cd50ba1

Please sign in to comment.