Skip to content

Commit

Permalink
Derive Reflect and Asset for InputMap<A> (#426)
Browse files Browse the repository at this point in the history
* impl TypePath for InputMap

* add a note to RELEASES

* implement reflection for most types

* allow large enum variant

* fix allow large enum

* update releases

* update registered types

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
  • Loading branch information
barsoosayque and alice-i-cecile authored Dec 18, 2023
1 parent 1913afe commit 9d60abb
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 25 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ itertools = "0.12"
serde = { version = "1.0", features = ["derive"] }
fixedbitset = "0.4.2"
once_cell = "1.17.1"
multimap = "0.9.0"

[dev-dependencies]
bevy = { version = "0.12", default-features = false, features = [
Expand Down
2 changes: 2 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Removed `multimap` dependency in favor of regular `HashMap` which allowed to derive `Reflect` for `InputMap`.
- Register types in the reflection system.
- added support in `ActionDiff` for value and axis_pair changes
- Added `InputMap::Clear`.

Expand Down
36 changes: 22 additions & 14 deletions src/input_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ use crate::input_streams::InputStreams;
use crate::user_input::{InputKind, Modifier, UserInput};
use crate::Actionlike;

use bevy::asset::Asset;
use bevy::ecs::component::Component;
use bevy::ecs::system::Resource;
use bevy::input::gamepad::Gamepad;
use bevy::reflect::TypeUuid;
use bevy::utils::HashMap;
use multimap::MultiMap;
use bevy::reflect::Reflect;
use bevy::utils::{Entry, HashMap};
use serde::{Deserialize, Serialize};

use core::fmt::Debug;
Expand Down Expand Up @@ -70,18 +70,19 @@ input_map.insert(MouseButton::Left, Action::Run)
input_map.clear_action(Action::Hide);
```
**/
#[derive(Resource, Component, Debug, Clone, PartialEq, Eq, TypeUuid, Serialize, Deserialize)]
#[uuid = "D7DECC78-8573-42FF-851A-F0344C7D05C9"]
#[derive(
Resource, Component, Debug, Clone, PartialEq, Eq, Asset, Reflect, Serialize, Deserialize,
)]
pub struct InputMap<A: Actionlike> {
/// The usize stored here is the index of the input in the Actionlike iterator
map: MultiMap<A, UserInput>,
map: HashMap<A, Vec<UserInput>>,
associated_gamepad: Option<Gamepad>,
}

impl<A: Actionlike> Default for InputMap<A> {
fn default() -> Self {
InputMap {
map: MultiMap::default(),
map: HashMap::default(),
associated_gamepad: None,
}
}
Expand Down Expand Up @@ -165,13 +166,20 @@ impl<A: Actionlike> InputMap<A> {
let input = input.into();

// Check for existing copies of the input: insertion should be idempotent
if let Some(vec) = self.map.get_vec(&action) {
if let Some(vec) = self.map.get(&action) {
if vec.contains(&input) {
return self;
}
}

self.map.insert(action, input);
match self.map.entry(action) {
Entry::Occupied(mut entry) => {
entry.get_mut().push(input);
}
Entry::Vacant(entry) => {
entry.insert(vec![input]);
}
};

self
}
Expand Down Expand Up @@ -398,18 +406,18 @@ impl<A: Actionlike> InputMap<A> {
impl<A: Actionlike> InputMap<A> {
/// Returns an iterator over actions with their inputs
pub fn iter(&self) -> impl Iterator<Item = (&A, &Vec<UserInput>)> {
self.map.iter_all()
self.map.iter()
}
/// Returns a reference to the inputs mapped to `action`
#[must_use]
pub fn get(&self, action: A) -> Option<&Vec<UserInput>> {
self.map.get_vec(&action)
self.map.get(&action)
}

/// Returns a mutable reference to the inputs mapped to `action`
#[must_use]
pub fn get_mut(&mut self, action: A) -> Option<&mut Vec<UserInput>> {
self.map.get_vec_mut(&action)
self.map.get_mut(&action)
}

/// How many input bindings are registered total?
Expand Down Expand Up @@ -451,7 +459,7 @@ impl<A: Actionlike> InputMap<A> {
///
/// Returns `Some(input)` if found.
pub fn remove_at(&mut self, action: A, index: usize) -> Option<UserInput> {
let input_vec = self.map.get_vec_mut(&action)?;
let input_vec = self.map.get_mut(&action)?;
if input_vec.len() <= index {
None
} else {
Expand All @@ -463,7 +471,7 @@ impl<A: Actionlike> InputMap<A> {
///
/// Returns [`Some`] with index if the input was found, or [`None`] if no matching input was found.
pub fn remove(&mut self, action: A, input: impl Into<UserInput>) -> Option<usize> {
let input_vec = self.map.get_vec_mut(&action)?;
let input_vec = self.map.get_mut(&action)?;
let user_input = input.into();
let index = input_vec.iter().position(|i| i == &user_input)?;
input_vec.remove(index);
Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use crate::action_state::ActionState;
use crate::input_map::InputMap;
use bevy::ecs::prelude::*;
use bevy::reflect::Reflect;
use bevy::reflect::{FromReflect, Reflect, TypePath};
use std::hash::Hash;
use std::marker::PhantomData;

Expand Down Expand Up @@ -83,7 +83,9 @@ pub mod prelude {
/// Ultimate,
/// }
/// ```
pub trait Actionlike: Eq + Hash + Send + Sync + Clone + Hash + Reflect + 'static {
pub trait Actionlike:
Eq + Hash + Send + Sync + Clone + Hash + Reflect + TypePath + FromReflect + 'static
{
/// The number of variants of this action type
fn n_variants() -> usize;

Expand Down
32 changes: 30 additions & 2 deletions src/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
//! Contains main plugin exported by this crate.
use crate::action_state::{ActionData, ActionState, Timing};
use crate::axislike::{
AxisType, DeadZoneShape, DualAxis, DualAxisData, MouseMotionAxisType, MouseWheelAxisType,
SingleAxis, VirtualAxis, VirtualDPad,
};
use crate::buttonlike::{MouseMotionDirection, MouseWheelDirection};
use crate::clashing_inputs::ClashStrategy;
use crate::prelude::ActionState;
use crate::dynamic_action::DynAction;
use crate::input_map::InputMap;
use crate::user_input::{InputKind, Modifier, UserInput};
use crate::Actionlike;
use core::hash::Hash;
use core::marker::PhantomData;
use std::fmt::Debug;

use bevy::app::{App, Plugin};
use bevy::ecs::prelude::*;
use bevy::input::InputSystem;
use bevy::input::{ButtonState, InputSystem};
use bevy::prelude::{PostUpdate, PreUpdate};
use bevy::reflect::TypePath;
#[cfg(feature = "ui")]
Expand Down Expand Up @@ -151,6 +159,26 @@ impl<A: Actionlike + TypePath> Plugin for InputManagerPlugin<A> {
};

app.register_type::<ActionState<A>>()
.register_type::<InputMap<A>>()
.register_type::<UserInput>()
.register_type::<InputKind>()
.register_type::<ActionData>()
.register_type::<Modifier>()
.register_type::<ActionState<A>>()
.register_type::<Timing>()
.register_type::<VirtualDPad>()
.register_type::<VirtualAxis>()
.register_type::<SingleAxis>()
.register_type::<DualAxis>()
.register_type::<AxisType>()
.register_type::<MouseWheelAxisType>()
.register_type::<MouseMotionAxisType>()
.register_type::<DualAxisData>()
.register_type::<DeadZoneShape>()
.register_type::<ButtonState>()
.register_type::<MouseWheelDirection>()
.register_type::<MouseMotionDirection>()
.register_type::<DynAction>()
// Resources
.init_resource::<ToggleActions<A>>()
.init_resource::<ClashStrategy>();
Expand Down
12 changes: 6 additions & 6 deletions src/user_input.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
//! Helpful abstractions over user inputs of all sorts
use bevy::input::keyboard::ScanCode;
use bevy::input::{gamepad::GamepadButtonType, keyboard::KeyCode, mouse::MouseButton};
use bevy::reflect::Reflect;
use bevy::utils::HashSet;
use serde::{Deserialize, Serialize};

use crate::axislike::VirtualAxis;
use crate::scan_codes::QwertyScanCode;
use crate::{
axislike::{AxisType, DualAxis, SingleAxis, VirtualDPad},
buttonlike::{MouseMotionDirection, MouseWheelDirection},
};
use bevy::prelude::ScanCode;
use bevy::utils::HashSet;
use serde::{Deserialize, Serialize};

/// Some combination of user input, which may cross input-mode boundaries.
///
/// For example, this may store mouse, keyboard or gamepad input, including cross-device chords!
///
/// Suitable for use in an [`InputMap`](crate::input_map::InputMap)
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Reflect)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum UserInput {
/// A single button
Single(InputKind),
Expand Down Expand Up @@ -358,7 +358,7 @@ impl From<Modifier> for UserInput {
///
/// Please contact the maintainers if you need support for another type!
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Reflect)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum InputKind {
/// A button on a gamepad
GamepadButton(GamepadButtonType),
Expand Down Expand Up @@ -452,7 +452,7 @@ impl From<Modifier> for InputKind {
///
/// This buttonlike input is stored in [`InputKind`], and will be triggered whenever either of these buttons are pressed.
/// This will be decomposed into both values when converted into [`RawInputs`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Reflect)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum Modifier {
/// Corresponds to [`KeyCode::AltLeft`] and [`KeyCode::AltRight`].
Alt,
Expand Down

0 comments on commit 9d60abb

Please sign in to comment.