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

Release the pressed ActionData when it isn't being updated #473

Merged
merged 3 commits into from
Feb 19, 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 @@ -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));
}
}
Loading