Skip to content

Commit

Permalink
Release the pressed ActionData when it isn't being updated (#473)
Browse files Browse the repository at this point in the history
* Release the pressed `ActionData` when it isn't being updated

* Add test
  • Loading branch information
Shute052 authored Feb 19, 2024
1 parent 687327e commit 19af9d4
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 16 deletions.
1 change: 1 addition & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
### Bugs

- fixed compilation issues with no-default-features
- fixed [a bug](https://github.com/Leafwing-Studios/leafwing-input-manager/issues/471) related to incorrect updating of `ActionState`.

## Version 0.12

Expand Down
110 changes: 94 additions & 16 deletions src/action_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ impl<A: Actionlike> ActionState<A> {
/// The `action_data` is typically constructed from [`InputMap::which_pressed`](crate::input_map::InputMap),
/// which reads from the assorted [`Input`](bevy::input::Input) resources.
pub fn update(&mut self, action_data: HashMap<A, ActionData>) {
for (action, action_datum) in self.action_data.iter_mut() {
if !action_data.contains_key(action) {
action_datum.state.release();
}
}
for (action, action_datum) in action_data {
match self.action_data.entry(action) {
Entry::Occupied(occupied_entry) => {
Expand Down Expand Up @@ -585,30 +590,28 @@ impl<A: Actionlike> ActionState<A> {
#[cfg(test)]
mod tests {
use crate as leafwing_input_manager;
use crate::action_state::ActionState;
use crate::clashing_inputs::ClashStrategy;
use crate::input_map::InputMap;
use crate::input_mocking::MockInput;
use bevy::prelude::Reflect;
use crate::input_streams::InputStreams;
use bevy::input::InputPlugin;
use bevy::prelude::*;
use bevy::utils::{Duration, Instant};
use leafwing_input_manager_macros::Actionlike;

#[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)]
enum Action {
Run,
Jump,
Hide,
}

#[test]
fn press_lifecycle() {
use crate::action_state::ActionState;
use crate::clashing_inputs::ClashStrategy;
use crate::input_map::InputMap;
use crate::input_streams::InputStreams;
use bevy::input::InputPlugin;
use bevy::prelude::*;
use bevy::utils::{Duration, Instant};

let mut app = App::new();
app.add_plugins(InputPlugin);

#[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, bevy::prelude::Reflect)]
enum Action {
Run,
Jump,
Hide,
}

// Action state
let mut action_state = ActionState::<Action>::default();

Expand Down Expand Up @@ -668,4 +671,79 @@ mod tests {
assert!(action_state.released(&Action::Run));
assert!(!action_state.just_released(&Action::Run));
}

#[test]
fn update_with_clashes_prioritizing_longest() {
#[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)]
enum Action {
One,
Two,
OneAndTwo,
}

// Input map
use bevy::prelude::KeyCode::*;
let mut input_map = InputMap::default();
input_map.insert(Action::One, Key1);
input_map.insert(Action::Two, Key2);
input_map.insert_chord(Action::OneAndTwo, [Key1, Key2]);

let mut app = App::new();
app.add_plugins(InputPlugin);

// Action state
let mut action_state = ActionState::<Action>::default();

// Starting state
let input_streams = InputStreams::from_world(&app.world, None);
action_state
.update(input_map.which_pressed(&input_streams, ClashStrategy::PrioritizeLongest));
assert!(action_state.released(&Action::One));
assert!(action_state.released(&Action::Two));
assert!(action_state.released(&Action::OneAndTwo));

// Pressing One
app.send_input(Key1);
app.update();
let input_streams = InputStreams::from_world(&app.world, None);

action_state
.update(input_map.which_pressed(&input_streams, ClashStrategy::PrioritizeLongest));

assert!(action_state.pressed(&Action::One));
assert!(action_state.released(&Action::Two));
assert!(action_state.released(&Action::OneAndTwo));

// Waiting
action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
action_state
.update(input_map.which_pressed(&input_streams, ClashStrategy::PrioritizeLongest));

assert!(action_state.pressed(&Action::One));
assert!(action_state.released(&Action::Two));
assert!(action_state.released(&Action::OneAndTwo));

// Pressing Two
app.send_input(Key2);
app.update();
let input_streams = InputStreams::from_world(&app.world, None);

action_state
.update(input_map.which_pressed(&input_streams, ClashStrategy::PrioritizeLongest));

// Now only the longest OneAndTwo has been pressed,
// while both One and Two have been released
assert!(action_state.released(&Action::One));
assert!(action_state.released(&Action::Two));
assert!(action_state.pressed(&Action::OneAndTwo));

// Waiting
action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
action_state
.update(input_map.which_pressed(&input_streams, ClashStrategy::PrioritizeLongest));

assert!(action_state.released(&Action::One));
assert!(action_state.released(&Action::Two));
assert!(action_state.pressed(&Action::OneAndTwo));
}
}

0 comments on commit 19af9d4

Please sign in to comment.