diff --git a/RELEASES.md b/RELEASES.md
index b781b916..9ea020b9 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -23,7 +23,7 @@
- added `InputMap::clear`
- fixed [a bug](https://github.com/Leafwing-Studios/leafwing-input-manager/issues/430) related to incorrect axis data in `Chord` when not all buttons are pressed.
-### Code quality
+### Code Quality
- all non-insertion methods now take `&A: Actionlike` rather than `A: Actionlike` to avoid pointless cloning
- removed `multimap` dependency in favor of regular `HashMap` which allowed to derive `Reflect` for `InputMap`
@@ -34,7 +34,11 @@
- `InputMap::which_pressed` now returns a `HashMap`
- `handle_clashes` now takes a `HashMap`
- `ClashStrategy::UseActionOrder` has been removed
-
+- the `action_state` module has been pared down to something more reasonable in scope:
+ - timing-related code now lives in its own `timing` module
+ - `ActionStateDriver` code now lives in its own `action_driver` module
+ - `ActionDiff`-related code now lives in its own `action_diff` module
+
## Version 0.11.2
- fixed [a bug](https://github.com/Leafwing-Studios/leafwing-input-manager/issues/285) with mouse motion and mouse wheel events being improperly counted
@@ -51,7 +55,7 @@
## Version 0.11
-### Known issues
+### Known Issues
- `bevy_egui` integration and the `egui` feature flag have been temporarily removed to ensure a timely release
- gamepad input mocking is not completely functional due to upstream changes: see [#407](https://github.com/Leafwing-Studios/leafwing-input-manager/issues/407)
diff --git a/benches/action_state.rs b/benches/action_state.rs
index 7bbed104..7c194175 100644
--- a/benches/action_state.rs
+++ b/benches/action_state.rs
@@ -1,9 +1,7 @@
use bevy::{prelude::Reflect, utils::HashMap};
use criterion::{criterion_group, criterion_main, Criterion};
use leafwing_input_manager::{
- action_state::{ActionData, Timing},
- buttonlike::ButtonState,
- prelude::ActionState,
+ action_state::ActionData, buttonlike::ButtonState, prelude::ActionState, timing::Timing,
Actionlike,
};
diff --git a/examples/send_actions_over_network.rs b/examples/send_actions_over_network.rs
index 9bf6f3a5..525ea12d 100644
--- a/examples/send_actions_over_network.rs
+++ b/examples/send_actions_over_network.rs
@@ -9,7 +9,7 @@
use bevy::ecs::event::{Events, ManualEventReader};
use bevy::input::InputPlugin;
use bevy::prelude::*;
-use leafwing_input_manager::action_state::ActionDiffEvent;
+use leafwing_input_manager::action_diff::ActionDiffEvent;
use leafwing_input_manager::prelude::*;
use leafwing_input_manager::systems::generate_action_diffs;
diff --git a/src/action_diff.rs b/src/action_diff.rs
new file mode 100644
index 00000000..31ffb902
--- /dev/null
+++ b/src/action_diff.rs
@@ -0,0 +1,60 @@
+//! Serialization-friendly representation of changes to [`ActionState`](crate::action_state::ActionState).
+//!
+//! These are predominantly intended for use in networked games, where the server needs to know what the players are doing,
+//! and would like a compact, semantically-meaningful representation of the changes to the game state without needing to know
+//! about things like keybindings or input devices.
+
+use bevy::{
+ ecs::{entity::Entity, event::Event},
+ math::Vec2,
+};
+use serde::{Deserialize, Serialize};
+
+use crate::Actionlike;
+
+/// Stores presses and releases of buttons without timing information
+///
+/// These are typically accessed using the `Events` resource.
+/// Uses a minimal storage format, in order to facilitate transport over the network.
+///
+/// An `ActionState` can be fully reconstructed from a stream of `ActionDiff`.
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub enum ActionDiff {
+ /// The action was pressed
+ Pressed {
+ /// The value of the action
+ action: A,
+ },
+ /// The action was released
+ Released {
+ /// The value of the action
+ action: A,
+ },
+ /// The value of the action changed
+ ValueChanged {
+ /// The value of the action
+ action: A,
+ /// The new value of the action
+ value: f32,
+ },
+ /// The axis pair of the action changed
+ AxisPairChanged {
+ /// The value of the action
+ action: A,
+ /// The new value of the axis
+ axis_pair: Vec2,
+ },
+}
+
+/// Will store an `ActionDiff` as well as what generated it (either an Entity, or nothing if the
+/// input actions are represented by a `Resource`)
+///
+/// These are typically accessed using the `Events` resource.
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Event)]
+pub struct ActionDiffEvent {
+ /// If some: the entity that has the `ActionState` component
+ /// If none: `ActionState` is a Resource, not a component
+ pub owner: Option,
+ /// The `ActionDiff` that was generated
+ pub action_diffs: Vec>,
+}
diff --git a/src/action_driver.rs b/src/action_driver.rs
new file mode 100644
index 00000000..7a8c5cc1
--- /dev/null
+++ b/src/action_driver.rs
@@ -0,0 +1,253 @@
+//! Tools to control the [`ActionState`] of other entities automatically from other entities.
+
+#[allow(unused_imports)] // For the documentation
+use crate::action_state::ActionState;
+
+use bevy::utils::hashbrown::hash_set::Iter;
+use std::iter::Once;
+
+use bevy::{
+ ecs::{component::Component, entity::Entity},
+ utils::HashSet,
+};
+
+use crate::Actionlike;
+
+/// A component that allows the attached entity to drive the [`ActionState`] of the associated entity
+///
+/// # Examples
+///
+/// By default, [`update_action_state_from_interaction`](crate::systems::update_action_state_from_interaction) uses this component
+/// in order to connect `bevy::ui` buttons to the corresponding `ActionState`.
+///
+/// ```rust
+/// use bevy::prelude::*;
+/// use leafwing_input_manager::prelude::*;
+///
+/// #[derive(Actionlike, PartialEq, Eq, Hash, Clone, Copy, Reflect)]
+/// enum DanceDance {
+/// Left,
+/// Right,
+/// Up,
+/// Down,
+/// }
+///
+/// // Spawn entity to track dance inputs
+/// let mut world = World::new();
+/// let dance_tracker = world
+/// .spawn(ActionState::::default())
+/// .id();
+///
+/// // Spawn a button, which is wired up to the dance tracker
+/// // When used with InputManagerPlugin, this button will press the DanceDance::Left action when it is pressed.
+/// world
+/// .spawn(ButtonBundle::default())
+/// // This component links the button to the entity with the `ActionState` component
+/// .insert(ActionStateDriver {
+/// action: DanceDance::Left,
+/// targets: dance_tracker.into(),
+/// });
+///```
+///
+/// Writing your own systems that use the [`ActionStateDriver`] component is easy,
+/// although this should be reserved for cases where the entity whose value you want to check
+/// is distinct from the entity whose [`ActionState`] you want to set.
+/// Check the source code of [`update_action_state_from_interaction`](crate::systems::update_action_state_from_interaction) for an example of how this is done.
+#[derive(Debug, Component, Clone, PartialEq, Eq)]
+pub struct ActionStateDriver {
+ /// The action triggered by this entity
+ pub action: A,
+ /// The entity whose action state should be updated
+ pub targets: ActionStateDriverTarget,
+}
+
+/// Represents the entities that an ``ActionStateDriver`` targets.
+#[derive(Debug, Component, Clone, PartialEq, Eq)]
+pub enum ActionStateDriverTarget {
+ /// No targets
+ None,
+ /// Single target
+ Single(Entity),
+ /// Multiple targets
+ Multi(HashSet),
+}
+
+impl ActionStateDriverTarget {
+ /// Get an iterator for the entities targeted.
+ #[inline(always)]
+ pub fn iter(&self) -> impl Iterator- {
+ match self {
+ Self::None => ActionStateDriverTargetIterator::None,
+ Self::Single(entity) => {
+ ActionStateDriverTargetIterator::Single(std::iter::once(entity))
+ }
+ Self::Multi(entities) => ActionStateDriverTargetIterator::Multi(entities.iter()),
+ }
+ }
+
+ /// Insert an entity as a target.
+ #[inline(always)]
+ pub fn insert(&mut self, entity: Entity) {
+ // Don't want to copy a bunch of logic, switch out the ref, then replace it
+ // rust doesn't like in place replacement
+ *self = std::mem::replace(self, Self::None).with(entity);
+ }
+
+ /// Remove an entity as a target if it's in the target set.
+ #[inline(always)]
+ pub fn remove(&mut self, entity: Entity) {
+ // see insert
+ *self = std::mem::replace(self, Self::None).without(entity);
+ }
+
+ /// Add an entity as a target.
+ #[inline(always)]
+ pub fn add(&mut self, entities: impl Iterator
- ) {
+ for entity in entities {
+ self.insert(entity)
+ }
+ }
+
+ /// Get the number of targets.
+ #[inline(always)]
+ pub fn len(&self) -> usize {
+ match self {
+ Self::None => 0,
+ Self::Single(_) => 1,
+ Self::Multi(targets) => targets.len(),
+ }
+ }
+
+ /// Returns true if there are no targets.
+ #[inline(always)]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Add an entity as a target using a builder style pattern.
+ #[inline(always)]
+ pub fn with(mut self, entity: Entity) -> Self {
+ match self {
+ Self::None => Self::Single(entity),
+ Self::Single(og) => Self::Multi(HashSet::from([og, entity])),
+ Self::Multi(ref mut targets) => {
+ targets.insert(entity);
+ self
+ }
+ }
+ }
+
+ /// Remove an entity as a target if it's in the set using a builder style pattern.
+ pub fn without(self, entity: Entity) -> Self {
+ match self {
+ Self::None => Self::None,
+ Self::Single(_) => Self::None,
+ Self::Multi(mut targets) => {
+ targets.remove(&entity);
+ Self::from_iter(targets)
+ }
+ }
+ }
+}
+
+impl From for ActionStateDriverTarget {
+ fn from(value: Entity) -> Self {
+ Self::Single(value)
+ }
+}
+
+impl From<()> for ActionStateDriverTarget {
+ fn from(_value: ()) -> Self {
+ Self::None
+ }
+}
+
+impl FromIterator for ActionStateDriverTarget {
+ fn from_iter>(iter: T) -> Self {
+ let entities = HashSet::from_iter(iter);
+
+ match entities.len() {
+ 0 => Self::None,
+ 1 => Self::Single(entities.into_iter().next().unwrap()),
+ _ => Self::Multi(entities),
+ }
+ }
+}
+
+impl<'a> FromIterator<&'a Entity> for ActionStateDriverTarget {
+ fn from_iter>(iter: T) -> Self {
+ let entities = HashSet::from_iter(iter.into_iter().cloned());
+
+ match entities.len() {
+ 0 => Self::None,
+ 1 => Self::Single(entities.into_iter().next().unwrap()),
+ _ => Self::Multi(entities),
+ }
+ }
+}
+
+enum ActionStateDriverTargetIterator<'a> {
+ None,
+ Single(Once<&'a Entity>),
+ Multi(Iter<'a, Entity>),
+}
+
+impl<'a> Iterator for ActionStateDriverTargetIterator<'a> {
+ type Item = &'a Entity;
+
+ fn next(&mut self) -> Option {
+ match self {
+ Self::None => None,
+ Self::Single(iter) => iter.next(),
+ Self::Multi(iter) => iter.next(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::ActionStateDriverTarget;
+ use bevy::prelude::Entity;
+
+ #[test]
+ fn action_state_driver_targets() {
+ let mut target = ActionStateDriverTarget::from(());
+
+ assert_eq!(0, target.len());
+
+ target.insert(Entity::from_raw(0));
+ assert_eq!(1, target.len());
+
+ target.insert(Entity::from_raw(1));
+ assert_eq!(2, target.len());
+
+ target.remove(Entity::from_raw(0));
+ assert_eq!(1, target.len());
+
+ target.remove(Entity::from_raw(1));
+ assert_eq!(0, target.len());
+
+ target = target.with(Entity::from_raw(0));
+ assert_eq!(1, target.len());
+
+ target = target.without(Entity::from_raw(0));
+ assert_eq!(0, target.len());
+
+ target.add(
+ [
+ Entity::from_raw(0),
+ Entity::from_raw(1),
+ Entity::from_raw(2),
+ ]
+ .iter()
+ .cloned(),
+ );
+ assert_eq!(3, target.len());
+
+ let mut sum = 0;
+ for entity in target.iter() {
+ sum += entity.index();
+ }
+ assert_eq!(3, sum);
+ }
+}
diff --git a/src/action_state.rs b/src/action_state.rs
index 1c4a59a7..2003abb4 100644
--- a/src/action_state.rs
+++ b/src/action_state.rs
@@ -1,16 +1,15 @@
//! This module contains [`ActionState`] and its supporting methods and impls.
+use crate::action_diff::ActionDiff;
+use crate::timing::Timing;
use crate::Actionlike;
use crate::{axislike::DualAxisData, buttonlike::ButtonState};
-use bevy::ecs::{component::Component, entity::Entity};
-use bevy::math::Vec2;
-use bevy::prelude::{Event, Resource};
+use bevy::ecs::component::Component;
+use bevy::prelude::Resource;
use bevy::reflect::Reflect;
-use bevy::utils::hashbrown::hash_set::Iter;
-use bevy::utils::{Duration, Entry, HashMap, HashSet, Instant};
+use bevy::utils::{Duration, Entry, HashMap, Instant};
use serde::{Deserialize, Serialize};
-use std::iter::Once;
/// Metadata about an [`Actionlike`] action
///
@@ -605,302 +604,13 @@ impl ActionState {
}
}
-/// A component that allows the attached entity to drive the [`ActionState`] of the associated entity
-///
-/// # Examples
-///
-/// By default, [`update_action_state_from_interaction`](crate::systems::update_action_state_from_interaction) uses this component
-/// in order to connect `bevy::ui` buttons to the corresponding `ActionState`.
-///
-/// ```rust
-/// use bevy::prelude::*;
-/// use leafwing_input_manager::prelude::*;
-///
-/// #[derive(Actionlike, PartialEq, Eq, Hash, Clone, Copy, Reflect)]
-/// enum DanceDance {
-/// Left,
-/// Right,
-/// Up,
-/// Down,
-/// }
-///
-/// // Spawn entity to track dance inputs
-/// let mut world = World::new();
-/// let dance_tracker = world
-/// .spawn(ActionState::::default())
-/// .id();
-///
-/// // Spawn a button, which is wired up to the dance tracker
-/// // When used with InputManagerPlugin, this button will press the DanceDance::Left action when it is pressed.
-/// world
-/// .spawn(ButtonBundle::default())
-/// // This component links the button to the entity with the `ActionState` component
-/// .insert(ActionStateDriver {
-/// action: DanceDance::Left,
-/// targets: dance_tracker.into(),
-/// });
-///```
-///
-/// Writing your own systems that use the [`ActionStateDriver`] component is easy,
-/// although this should be reserved for cases where the entity whose value you want to check
-/// is distinct from the entity whose [`ActionState`] you want to set.
-/// Check the source code of [`update_action_state_from_interaction`](crate::systems::update_action_state_from_interaction) for an example of how this is done.
-#[derive(Debug, Component, Clone, PartialEq, Eq)]
-pub struct ActionStateDriver {
- /// The action triggered by this entity
- pub action: A,
- /// The entity whose action state should be updated
- pub targets: ActionStateDriverTarget,
-}
-
-/// Represents the entities that an ``ActionStateDriver`` targets.
-#[derive(Debug, Component, Clone, PartialEq, Eq)]
-pub enum ActionStateDriverTarget {
- /// No targets
- None,
- /// Single target
- Single(Entity),
- /// Multiple targets
- Multi(HashSet),
-}
-
-impl ActionStateDriverTarget {
- /// Get an iterator for the entities targeted.
- #[inline(always)]
- pub fn iter(&self) -> impl Iterator
- {
- match self {
- Self::None => ActionStateDriverTargetIterator::None,
- Self::Single(entity) => {
- ActionStateDriverTargetIterator::Single(std::iter::once(entity))
- }
- Self::Multi(entities) => ActionStateDriverTargetIterator::Multi(entities.iter()),
- }
- }
-
- /// Insert an entity as a target.
- #[inline(always)]
- pub fn insert(&mut self, entity: Entity) {
- // Don't want to copy a bunch of logic, switch out the ref, then replace it
- // rust doesn't like in place replacement
- *self = std::mem::replace(self, Self::None).with(entity);
- }
-
- /// Remove an entity as a target if it's in the target set.
- #[inline(always)]
- pub fn remove(&mut self, entity: Entity) {
- // see insert
- *self = std::mem::replace(self, Self::None).without(entity);
- }
-
- /// Add an entity as a target.
- #[inline(always)]
- pub fn add(&mut self, entities: impl Iterator
- ) {
- for entity in entities {
- self.insert(entity)
- }
- }
-
- /// Get the number of targets.
- #[inline(always)]
- pub fn len(&self) -> usize {
- match self {
- Self::None => 0,
- Self::Single(_) => 1,
- Self::Multi(targets) => targets.len(),
- }
- }
-
- /// Returns true if there are no targets.
- #[inline(always)]
- pub fn is_empty(&self) -> bool {
- self.len() == 0
- }
-
- /// Add an entity as a target using a builder style pattern.
- #[inline(always)]
- pub fn with(mut self, entity: Entity) -> Self {
- match self {
- Self::None => Self::Single(entity),
- Self::Single(og) => Self::Multi(HashSet::from([og, entity])),
- Self::Multi(ref mut targets) => {
- targets.insert(entity);
- self
- }
- }
- }
-
- /// Remove an entity as a target if it's in the set using a builder style pattern.
- pub fn without(self, entity: Entity) -> Self {
- match self {
- Self::None => Self::None,
- Self::Single(_) => Self::None,
- Self::Multi(mut targets) => {
- targets.remove(&entity);
- Self::from_iter(targets)
- }
- }
- }
-}
-
-impl From for ActionStateDriverTarget {
- fn from(value: Entity) -> Self {
- Self::Single(value)
- }
-}
-
-impl From<()> for ActionStateDriverTarget {
- fn from(_value: ()) -> Self {
- Self::None
- }
-}
-
-impl FromIterator for ActionStateDriverTarget {
- fn from_iter>(iter: T) -> Self {
- let entities = HashSet::from_iter(iter);
-
- match entities.len() {
- 0 => Self::None,
- 1 => Self::Single(entities.into_iter().next().unwrap()),
- _ => Self::Multi(entities),
- }
- }
-}
-
-impl<'a> FromIterator<&'a Entity> for ActionStateDriverTarget {
- fn from_iter>(iter: T) -> Self {
- let entities = HashSet::from_iter(iter.into_iter().cloned());
-
- match entities.len() {
- 0 => Self::None,
- 1 => Self::Single(entities.into_iter().next().unwrap()),
- _ => Self::Multi(entities),
- }
- }
-}
-
-enum ActionStateDriverTargetIterator<'a> {
- None,
- Single(Once<&'a Entity>),
- Multi(Iter<'a, Entity>),
-}
-
-impl<'a> Iterator for ActionStateDriverTargetIterator<'a> {
- type Item = &'a Entity;
-
- fn next(&mut self) -> Option {
- match self {
- Self::None => None,
- Self::Single(iter) => iter.next(),
- Self::Multi(iter) => iter.next(),
- }
- }
-}
-
-/// Stores information about when an action was pressed or released
-///
-/// This struct is principally used as a field on [`ActionData`],
-/// which itself lives inside an [`ActionState`].
-#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, Reflect)]
-pub struct Timing {
- /// The [`Instant`] at which the button was pressed or released
- /// Recorded as the [`Time`](bevy::time::Time) at the start of the tick after the state last changed.
- /// If this is none, [`Timing::tick`] has not been called yet.
- #[serde(skip)]
- pub instant_started: Option,
- /// The [`Duration`] for which the button has been pressed or released.
- ///
- /// This begins at [`Duration::ZERO`] when [`ActionState::update`] is called.
- pub current_duration: Duration,
- /// The [`Duration`] for which the button was pressed or released before the state last changed.
- pub previous_duration: Duration,
-}
-
-impl PartialOrd for Timing {
- fn partial_cmp(&self, other: &Self) -> Option {
- self.current_duration.partial_cmp(&other.current_duration)
- }
-}
-
-impl Timing {
- /// Advances the `current_duration` of this timer
- ///
- /// If the `instant_started` is None, it will be set to the current time.
- /// This design allows us to ensure that the timing is always synchronized with the start of each frame.
- pub fn tick(&mut self, current_instant: Instant, previous_instant: Instant) {
- if let Some(instant_started) = self.instant_started {
- self.current_duration = current_instant - instant_started;
- } else {
- self.current_duration = current_instant - previous_instant;
- self.instant_started = Some(previous_instant);
- }
- }
-
- /// Flips the metaphorical hourglass, storing `current_duration` in `previous_duration` and resetting `instant_started`
- ///
- /// This method is called whenever actions are pressed or released
- pub fn flip(&mut self) {
- self.previous_duration = self.current_duration;
- self.current_duration = Duration::ZERO;
- self.instant_started = None;
- }
-}
-
-/// Will store an `ActionDiff` as well as what generated it (either an Entity, or nothing if the
-/// input actions are represented by a `Resource`)
-///
-/// These are typically accessed using the `Events` resource.
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Event)]
-pub struct ActionDiffEvent {
- /// If some: the entity that has the `ActionState` component
- /// If none: `ActionState` is a Resource, not a component
- pub owner: Option,
- /// The `ActionDiff` that was generated
- pub action_diffs: Vec>,
-}
-
-/// Stores presses and releases of buttons without timing information
-///
-/// These are typically accessed using the `Events` resource.
-/// Uses a minimal storage format, in order to facilitate transport over the network.
-///
-/// An `ActionState` can be fully reconstructed from a stream of `ActionDiff`.
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub enum ActionDiff {
- /// The action was pressed
- Pressed {
- /// The value of the action
- action: A,
- },
- /// The action was released
- Released {
- /// The value of the action
- action: A,
- },
- /// The value of the action changed
- ValueChanged {
- /// The value of the action
- action: A,
- /// The new value of the action
- value: f32,
- },
- /// The axis pair of the action changed
- AxisPairChanged {
- /// The value of the action
- action: A,
- /// The new value of the axis
- axis_pair: Vec2,
- },
-}
-
#[cfg(test)]
mod tests {
use crate as leafwing_input_manager;
use crate::input_mocking::MockInput;
- use bevy::prelude::{Entity, Reflect};
+ use bevy::prelude::Reflect;
use leafwing_input_manager_macros::Actionlike;
- use super::ActionStateDriverTarget;
-
#[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)]
enum Action {
Run,
@@ -980,127 +690,4 @@ mod tests {
assert!(action_state.released(&Action::Run));
assert!(!action_state.just_released(&Action::Run));
}
-
- #[test]
- fn time_tick_ticks_away() {
- use crate::action_state::ActionState;
- use bevy::utils::{Duration, Instant};
-
- let mut action_state = ActionState::::default();
-
- // Actions start released (but not just released)
- assert!(action_state.released(&Action::Run));
- assert!(!action_state.just_released(&Action::Jump));
-
- // Ticking causes buttons that were just released to no longer be just released
- action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
- assert!(action_state.released(&Action::Jump));
- assert!(!action_state.just_released(&Action::Jump));
- action_state.press(&Action::Jump);
- assert!(action_state.just_pressed(&Action::Jump));
-
- // Ticking causes buttons that were just pressed to no longer be just pressed
- action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
- assert!(action_state.pressed(&Action::Jump));
- assert!(!action_state.just_pressed(&Action::Jump));
- }
-
- #[test]
- fn durations() {
- use crate::action_state::ActionState;
- use bevy::utils::{Duration, Instant};
-
- let mut action_state = ActionState::::default();
-
- // Actions start released
- assert!(action_state.released(&Action::Jump));
- assert_eq!(action_state.instant_started(&Action::Jump), None,);
- assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
- assert_eq!(
- action_state.previous_duration(&Action::Jump),
- Duration::ZERO
- );
-
- // Pressing a button swaps the state
- action_state.press(&Action::Jump);
- assert!(action_state.pressed(&Action::Jump));
- assert_eq!(action_state.instant_started(&Action::Jump), None);
- assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
- assert_eq!(
- action_state.previous_duration(&Action::Jump),
- Duration::ZERO
- );
-
- // Ticking time sets the instant for the new state
- let t0 = Instant::now();
- let t1 = t0 + Duration::new(1, 0);
-
- action_state.tick(t1, t0);
- assert_eq!(action_state.instant_started(&Action::Jump), Some(t0));
- assert_eq!(action_state.current_duration(&Action::Jump), t1 - t0);
- assert_eq!(
- action_state.previous_duration(&Action::Jump),
- Duration::ZERO
- );
-
- // Time passes
- let t2 = t1 + Duration::new(5, 0);
-
- // The duration is updated
- action_state.tick(t2, t1);
- assert_eq!(action_state.instant_started(&Action::Jump), Some(t0));
- assert_eq!(action_state.current_duration(&Action::Jump), t2 - t0);
- assert_eq!(
- action_state.previous_duration(&Action::Jump),
- Duration::ZERO
- );
-
- // Releasing again, swapping the current duration to the previous one
- action_state.release(&Action::Jump);
- assert_eq!(action_state.instant_started(&Action::Jump), None);
- assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
- assert_eq!(action_state.previous_duration(&Action::Jump), t2 - t0);
- }
-
- #[test]
- fn action_state_driver_targets() {
- let mut target = ActionStateDriverTarget::from(());
-
- assert_eq!(0, target.len());
-
- target.insert(Entity::from_raw(0));
- assert_eq!(1, target.len());
-
- target.insert(Entity::from_raw(1));
- assert_eq!(2, target.len());
-
- target.remove(Entity::from_raw(0));
- assert_eq!(1, target.len());
-
- target.remove(Entity::from_raw(1));
- assert_eq!(0, target.len());
-
- target = target.with(Entity::from_raw(0));
- assert_eq!(1, target.len());
-
- target = target.without(Entity::from_raw(0));
- assert_eq!(0, target.len());
-
- target.add(
- [
- Entity::from_raw(0),
- Entity::from_raw(1),
- Entity::from_raw(2),
- ]
- .iter()
- .cloned(),
- );
- assert_eq!(3, target.len());
-
- let mut sum = 0;
- for entity in target.iter() {
- sum += entity.index();
- }
- assert_eq!(3, sum);
- }
}
diff --git a/src/lib.rs b/src/lib.rs
index de7d5a9d..e20316f6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,6 +9,8 @@ use bevy::ecs::prelude::*;
use bevy::reflect::{FromReflect, Reflect, TypePath};
use std::hash::Hash;
+pub mod action_diff;
+pub mod action_driver;
pub mod action_state;
pub mod axislike;
pub mod buttonlike;
@@ -23,6 +25,7 @@ pub mod orientation;
pub mod plugin;
pub mod scan_codes;
pub mod systems;
+pub mod timing;
pub mod user_input;
// Importing the derive macro
@@ -30,7 +33,8 @@ pub use leafwing_input_manager_macros::Actionlike;
/// Everything you need to get started
pub mod prelude {
- pub use crate::action_state::{ActionState, ActionStateDriver};
+ pub use crate::action_driver::ActionStateDriver;
+ pub use crate::action_state::ActionState;
pub use crate::axislike::{
DeadZoneShape, DualAxis, MouseWheelAxisType, SingleAxis, VirtualDPad,
};
diff --git a/src/plugin.rs b/src/plugin.rs
index f4073f74..858ee47e 100644
--- a/src/plugin.rs
+++ b/src/plugin.rs
@@ -1,6 +1,6 @@
//! Contains main plugin exported by this crate.
-use crate::action_state::{ActionData, ActionState, Timing};
+use crate::action_state::{ActionData, ActionState};
use crate::axislike::{
AxisType, DeadZoneShape, DualAxis, DualAxisData, MouseMotionAxisType, MouseWheelAxisType,
SingleAxis, VirtualAxis, VirtualDPad,
@@ -8,6 +8,7 @@ use crate::axislike::{
use crate::buttonlike::{MouseMotionDirection, MouseWheelDirection};
use crate::clashing_inputs::ClashStrategy;
use crate::input_map::InputMap;
+use crate::timing::Timing;
use crate::user_input::{InputKind, Modifier, UserInput};
use crate::Actionlike;
use core::hash::Hash;
@@ -49,7 +50,7 @@ use bevy::ui::UiSystem;
/// - [`tick_action_state`](crate::systems::tick_action_state), which resets the `pressed` and `just_pressed` fields of the [`ActionState`] each frame
/// - [`update_action_state`](crate::systems::update_action_state), which collects [`Input`](bevy::input::Input) resources to update the [`ActionState`]
/// - [`update_action_state_from_interaction`](crate::systems::update_action_state_from_interaction), for triggering actions from buttons
-/// - powers the [`ActionStateDriver`](crate::action_state::ActionStateDriver) component based on an [`Interaction`](bevy::ui::Interaction) component
+/// - powers the [`ActionStateDriver`](crate::action_driver::ActionStateDriver) component based on an [`Interaction`](bevy::ui::Interaction) component
/// - [`release_on_disable`](crate::systems::release_on_disable), which resets action states when [`ToggleActions`] is flipped, to avoid persistent presses.
pub struct InputManagerPlugin {
_phantom: PhantomData,
@@ -71,7 +72,7 @@ impl InputManagerPlugin {
///
/// Inputs will not be processed; instead, [`ActionState`]
/// should be copied directly from the state provided by the client,
- /// or constructed from [`ActionDiff`](crate::action_state::ActionDiff) event streams.
+ /// or constructed from [`ActionDiff`](crate::action_diff::ActionDiff) event streams.
#[must_use]
pub fn server() -> Self {
Self {
diff --git a/src/systems.rs b/src/systems.rs
index b9916ccb..bad94b99 100644
--- a/src/systems.rs
+++ b/src/systems.rs
@@ -1,14 +1,10 @@
//! The systems that power each [`InputManagerPlugin`](crate::plugin::InputManagerPlugin).
#[cfg(feature = "ui")]
-use crate::action_state::ActionStateDriver;
+use crate::action_driver::ActionStateDriver;
use crate::{
- action_state::{ActionDiff, ActionState},
- clashing_inputs::ClashStrategy,
- input_map::InputMap,
- input_streams::InputStreams,
- plugin::ToggleActions,
- Actionlike,
+ action_state::ActionState, clashing_inputs::ClashStrategy, input_map::InputMap,
+ input_streams::InputStreams, plugin::ToggleActions, Actionlike,
};
use bevy::{ecs::prelude::*, prelude::ScanCode};
@@ -25,7 +21,8 @@ use bevy::{
utils::{HashMap, Instant},
};
-use crate::action_state::ActionDiffEvent;
+use crate::action_diff::{ActionDiff, ActionDiffEvent};
+
#[cfg(feature = "ui")]
use bevy::ui::Interaction;
#[cfg(feature = "egui")]
diff --git a/src/timing.rs b/src/timing.rs
new file mode 100644
index 00000000..b9200f67
--- /dev/null
+++ b/src/timing.rs
@@ -0,0 +1,151 @@
+//! Information about when an action was pressed or released.
+
+use bevy::{
+ reflect::Reflect,
+ utils::{Duration, Instant},
+};
+use serde::{Deserialize, Serialize};
+
+/// Stores information about when an action was pressed or released
+///
+/// This struct is principally used as a field on [`ActionData`](crate::action_state::ActionData),
+/// which itself lives inside an [`ActionState`](crate::action_state::ActionState).
+#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, Reflect)]
+pub struct Timing {
+ /// The [`Instant`] at which the button was pressed or released
+ /// Recorded as the [`Time`](bevy::time::Time) at the start of the tick after the state last changed.
+ /// If this is none, [`Timing::tick`] has not been called yet.
+ #[serde(skip)]
+ pub instant_started: Option,
+ /// The [`Duration`] for which the button has been pressed or released.
+ ///
+ /// This begins at [`Duration::ZERO`] when [`ActionState::update`](crate::action_state::ActionState::update) is called.
+ pub current_duration: Duration,
+ /// The [`Duration`] for which the button was pressed or released before the state last changed.
+ pub previous_duration: Duration,
+}
+
+impl PartialOrd for Timing {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ self.current_duration.partial_cmp(&other.current_duration)
+ }
+}
+
+impl Timing {
+ /// Advances the `current_duration` of this timer
+ ///
+ /// If the `instant_started` is None, it will be set to the current time.
+ /// This design allows us to ensure that the timing is always synchronized with the start of each frame.
+ pub fn tick(&mut self, current_instant: Instant, previous_instant: Instant) {
+ if let Some(instant_started) = self.instant_started {
+ self.current_duration = current_instant - instant_started;
+ } else {
+ self.current_duration = current_instant - previous_instant;
+ self.instant_started = Some(previous_instant);
+ }
+ }
+
+ /// Flips the metaphorical hourglass, storing `current_duration` in `previous_duration` and resetting `instant_started`
+ ///
+ /// This method is called whenever actions are pressed or released
+ pub fn flip(&mut self) {
+ self.previous_duration = self.current_duration;
+ self.current_duration = Duration::ZERO;
+ self.instant_started = None;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate as leafwing_input_manager;
+ use bevy::prelude::Reflect;
+ use leafwing_input_manager_macros::Actionlike;
+
+ #[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)]
+ enum Action {
+ Run,
+ Jump,
+ Hide,
+ }
+
+ #[test]
+ fn time_tick_ticks_away() {
+ use crate::action_state::ActionState;
+ use bevy::utils::{Duration, Instant};
+
+ let mut action_state = ActionState::::default();
+
+ // Actions start released (but not just released)
+ assert!(action_state.released(&Action::Run));
+ assert!(!action_state.just_released(&Action::Jump));
+
+ // Ticking causes buttons that were just released to no longer be just released
+ action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
+ assert!(action_state.released(&Action::Jump));
+ assert!(!action_state.just_released(&Action::Jump));
+ action_state.press(&Action::Jump);
+ assert!(action_state.just_pressed(&Action::Jump));
+
+ // Ticking causes buttons that were just pressed to no longer be just pressed
+ action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
+ assert!(action_state.pressed(&Action::Jump));
+ assert!(!action_state.just_pressed(&Action::Jump));
+ }
+
+ #[test]
+ fn durations() {
+ use crate::action_state::ActionState;
+ use bevy::utils::{Duration, Instant};
+
+ let mut action_state = ActionState::::default();
+
+ // Actions start released
+ assert!(action_state.released(&Action::Jump));
+ assert_eq!(action_state.instant_started(&Action::Jump), None,);
+ assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
+ assert_eq!(
+ action_state.previous_duration(&Action::Jump),
+ Duration::ZERO
+ );
+
+ // Pressing a button swaps the state
+ action_state.press(&Action::Jump);
+ assert!(action_state.pressed(&Action::Jump));
+ assert_eq!(action_state.instant_started(&Action::Jump), None);
+ assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
+ assert_eq!(
+ action_state.previous_duration(&Action::Jump),
+ Duration::ZERO
+ );
+
+ // Ticking time sets the instant for the new state
+ let t0 = Instant::now();
+ let t1 = t0 + Duration::new(1, 0);
+
+ action_state.tick(t1, t0);
+ assert_eq!(action_state.instant_started(&Action::Jump), Some(t0));
+ assert_eq!(action_state.current_duration(&Action::Jump), t1 - t0);
+ assert_eq!(
+ action_state.previous_duration(&Action::Jump),
+ Duration::ZERO
+ );
+
+ // Time passes
+ let t2 = t1 + Duration::new(5, 0);
+
+ // The duration is updated
+ action_state.tick(t2, t1);
+ assert_eq!(action_state.instant_started(&Action::Jump), Some(t0));
+ assert_eq!(action_state.current_duration(&Action::Jump), t2 - t0);
+ assert_eq!(
+ action_state.previous_duration(&Action::Jump),
+ Duration::ZERO
+ );
+
+ // Releasing again, swapping the current duration to the previous one
+ action_state.release(&Action::Jump);
+ assert_eq!(action_state.instant_started(&Action::Jump), None);
+ assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
+ assert_eq!(action_state.previous_duration(&Action::Jump), t2 - t0);
+ }
+}
diff --git a/tests/action_diffs.rs b/tests/action_diffs.rs
index 3c323747..7e448b55 100644
--- a/tests/action_diffs.rs
+++ b/tests/action_diffs.rs
@@ -1,8 +1,6 @@
use bevy::{input::InputPlugin, prelude::*};
-use leafwing_input_manager::action_state::ActionDiffEvent;
-use leafwing_input_manager::{
- action_state::ActionDiff, axislike::DualAxisData, prelude::*, systems::generate_action_diffs,
-};
+use leafwing_input_manager::action_diff::{ActionDiff, ActionDiffEvent};
+use leafwing_input_manager::{axislike::DualAxisData, prelude::*, systems::generate_action_diffs};
#[derive(Actionlike, Clone, Copy, Debug, Reflect, PartialEq, Eq, Hash)]
enum Action {