From e8d4da41ccc47389f735aaae98b4c6b7a100fb21 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 8 Aug 2024 11:20:19 -0400 Subject: [PATCH] Remove action consuming (#582) --- RELEASES.md | 2 + examples/consuming_actions.rs | 135 -------------------------------- src/action_state/action_data.rs | 13 +-- src/action_state/mod.rs | 73 ----------------- tests/fixed_update.rs | 63 +-------------- 5 files changed, 4 insertions(+), 282 deletions(-) delete mode 100644 examples/consuming_actions.rs diff --git a/RELEASES.md b/RELEASES.md index 6791879c..e1f86d58 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -135,6 +135,8 @@ Input processors allow you to create custom logic for axis-like input manipulati - removed the `no_ui_priority` feature. To get this behavior, now just turn off the default `ui` feature - removed the `orientation` module, migrating to `bevy_math::Rot2` - use the types provided in `bevy_math` instead +- remove action consuming (and various `consume` / `consumed` methods) to reduce complexity and avoid confusing overlap with action disabling + - write your own logic for cases where this was used: generally by working off of `ActionDiff` events that are consumed ### Migration Guide (0.15) diff --git a/examples/consuming_actions.rs b/examples/consuming_actions.rs deleted file mode 100644 index 7e19ceb4..00000000 --- a/examples/consuming_actions.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! Demonstrates how to "consume" actions, so they can only be responded to by a single system - -use bevy::prelude::*; -use leafwing_input_manager::prelude::*; - -use menu_mocking::*; - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .add_plugins(InputManagerPlugin::::default()) - .init_resource::>() - .insert_resource(InputMap::::new([ - (MenuAction::CloseWindow, KeyCode::Escape), - (MenuAction::OpenMainMenu, KeyCode::KeyM), - (MenuAction::OpenSubMenu, KeyCode::KeyS), - ])) - .init_resource::() - .init_resource::() - .add_systems(Update, report_menus) - .add_systems(Update, open_main_menu) - .add_systems(Update, open_sub_menu) - // We want to ensure that if both the main menu and submenu are open, - // only the submenu is closed if the user hits (or holds) Escape - .add_systems(Update, close_menu::.before(close_menu::)) - // We can do this by ordering our systems and using `ActionState::consume` - .add_systems(Update, close_menu::) - .run(); -} - -#[derive(Actionlike, Debug, Clone, Reflect, PartialEq, Eq, Hash)] -enum MenuAction { - CloseWindow, - OpenMainMenu, - OpenSubMenu, -} - -// A simple "visualization" of app state -fn report_menus(main_menu: Res, submenu: Res) { - if main_menu.is_changed() { - if main_menu.is_open() { - println!("The main menu is now open.") - } else { - println!("The main menu is now closed.") - } - } - - if submenu.is_changed() { - if submenu.is_open() { - println!("The submenu is now open.") - } else { - println!("The submenu is now closed.") - } - } -} - -fn open_main_menu(action_state: Res>, mut menu_state: ResMut) { - if action_state.just_pressed(&MenuAction::OpenMainMenu) && !menu_state.is_open() { - menu_state.open(); - } -} - -fn open_sub_menu(action_state: Res>, mut menu_state: ResMut) { - if action_state.just_pressed(&MenuAction::OpenSubMenu) && !menu_state.is_open() { - menu_state.open(); - } -} - -// We want to ensure that, e.g., the submenu is closed in preference to the main menu if both are open. -// If you can, use a real focus system for this logic. -// However, workarounds of this sort are necessary in bevy_egui -// as it is an immediate mode UI library -fn close_menu( - mut action_state: ResMut>, - mut menu_status: ResMut, -) { - if action_state.pressed(&MenuAction::CloseWindow) && menu_status.is_open() { - println!("Closing the top window, as requested."); - menu_status.close(); - // Because the action is consumed, further systems won't see this action as pressed, - // and it cannot be pressed again until after the next time it would be released. - action_state.consume(&MenuAction::CloseWindow); - } -} - -// A quick mock of some UI behavior for demonstration purposes -mod menu_mocking { - use bevy::prelude::Resource; - - pub trait Menu { - fn is_open(&self) -> bool; - - fn open(&mut self); - - fn close(&mut self); - } - - #[derive(Resource, Default)] - pub struct MainMenu { - is_open: bool, - } - - impl Menu for MainMenu { - fn is_open(&self) -> bool { - self.is_open - } - - fn open(&mut self) { - self.is_open = true; - } - - fn close(&mut self) { - self.is_open = false; - } - } - - #[derive(Resource, Default)] - pub struct SubMenu { - is_open: bool, - } - - impl Menu for SubMenu { - fn is_open(&self) -> bool { - self.is_open - } - - fn open(&mut self) { - self.is_open = true; - } - - fn close(&mut self) { - self.is_open = false; - } - } -} diff --git a/src/action_state/action_data.rs b/src/action_state/action_data.rs index b4ec51f1..13b8d26d 100644 --- a/src/action_state/action_data.rs +++ b/src/action_state/action_data.rs @@ -43,10 +43,7 @@ impl ActionData { data.state.tick(); #[cfg(feature = "timing")] - // Durations should not advance while actions are consumed - if !data.consumed { - data.timing.tick(_current_instant, _previous_instant); - } + data.timing.tick(_current_instant, _previous_instant); } ActionKindData::Axis(ref mut _data) => {} ActionKindData::DualAxis(ref mut _data) => {} @@ -117,11 +114,6 @@ pub struct ButtonData { /// When was the button pressed / released, and how long has it been held for? #[cfg(feature = "timing")] pub timing: Timing, - /// Was this action consumed by [`ActionState::consume`](super::ActionState::consume)? - /// - /// Actions that are consumed cannot be pressed again until they are explicitly released. - /// This ensures that consumed actions are not immediately re-pressed by continued inputs. - pub consumed: bool, } impl ButtonData { @@ -132,7 +124,6 @@ impl ButtonData { fixed_update_state: ButtonState::JustPressed, #[cfg(feature = "timing")] timing: Timing::NEW, - consumed: false, }; /// The default data for a button that was just released. @@ -142,7 +133,6 @@ impl ButtonData { fixed_update_state: ButtonState::JustReleased, #[cfg(feature = "timing")] timing: Timing::NEW, - consumed: false, }; /// The default data for a button that is released, @@ -156,7 +146,6 @@ impl ButtonData { fixed_update_state: ButtonState::Released, #[cfg(feature = "timing")] timing: Timing::NEW, - consumed: false, }; /// Is the action currently pressed? diff --git a/src/action_state/mod.rs b/src/action_state/mod.rs index 534dfdbb..7b967161 100644 --- a/src/action_state/mod.rs +++ b/src/action_state/mod.rs @@ -591,11 +591,6 @@ impl ActionState { let action_data = self.button_data_mut_or_default(action); - // Consumed actions cannot be pressed until they are released - if action_data.consumed { - return; - } - #[cfg(feature = "timing")] if action_data.state.released() { action_data.timing.flip(); @@ -614,9 +609,6 @@ impl ActionState { let action_data = self.button_data_mut_or_default(action); - // Once released, consumed actions can be pressed again - action_data.consumed = false; - #[cfg(feature = "timing")] if action_data.state.pressed() { action_data.timing.flip(); @@ -651,71 +643,6 @@ impl ActionState { } } - /// Consumes the `action` - /// - /// The action will be released, and will not be able to be pressed again - /// until it would have otherwise been released by [`ActionState::release`], - /// [`ActionState::reset`], [`ActionState::reset_all`] or [`ActionState::update`]. - /// - /// No initial instant will be recorded - /// Instead, this is set through [`ActionState::tick()`] - /// - /// # Example - /// - /// ```rust - /// use bevy::prelude::Reflect; - /// use leafwing_input_manager::prelude::*; - /// - /// #[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)] - /// enum Action { - /// Eat, - /// Sleep, - /// } - /// - /// let mut action_state = ActionState::::default(); - /// - /// action_state.press(&Action::Eat); - /// assert!(action_state.pressed(&Action::Eat)); - /// - /// // Consuming actions releases them - /// action_state.consume(&Action::Eat); - /// assert!(action_state.released(&Action::Eat)); - /// - /// // Doesn't work, as the action was consumed - /// action_state.press(&Action::Eat); - /// assert!(action_state.released(&Action::Eat)); - /// - /// // Releasing consumed actions allows them to be pressed again - /// action_state.release(&Action::Eat); - /// action_state.press(&Action::Eat); - /// assert!(action_state.pressed(&Action::Eat)); - /// ``` - #[inline] - pub fn consume(&mut self, action: &A) { - let action_data = self.button_data_mut_or_default(action); - - // This is the only difference from action_state.release(&action) - action_data.consumed = true; - action_data.state.release(); - #[cfg(feature = "timing")] - action_data.timing.flip(); - } - - /// Consumes all actions - #[inline] - pub fn consume_all(&mut self) { - for action in self.keys() { - self.consume(&action); - } - } - - /// Is this `action` currently consumed? - #[inline] - #[must_use] - pub fn consumed(&self, action: &A) -> bool { - matches!(self.button_data(action), Some(action_data) if action_data.consumed) - } - /// Is the entire [`ActionState`] currently disabled? pub fn disabled(&self) -> bool { self.disabled diff --git a/tests/fixed_update.rs b/tests/fixed_update.rs index 87afc570..a244eccb 100644 --- a/tests/fixed_update.rs +++ b/tests/fixed_update.rs @@ -6,7 +6,7 @@ use bevy::time::TimeUpdateStrategy; use bevy::MinimalPlugins; use leafwing_input_manager::action_state::ActionState; use leafwing_input_manager::input_map::InputMap; -use leafwing_input_manager::plugin::{InputManagerPlugin, InputManagerSystem}; +use leafwing_input_manager::plugin::InputManagerPlugin; use leafwing_input_manager::prelude::Buttonlike; use leafwing_input_manager_macros::Actionlike; use std::time::Duration; @@ -205,64 +205,3 @@ fn frame_with_two_fixed_timestep() { Duration::from_millis(18) ); } - -/// Check that if the action is consumed in FU1, it will still be consumed in F2. -/// (i.e. consuming is shared between the `FixedMain` and `Main` schedules) -#[test] -fn test_consume_in_fixed_update() { - let mut app = build_app(Duration::from_millis(5), Duration::from_millis(5)); - - app.add_systems( - FixedPostUpdate, - |mut action: ResMut>| { - action.consume(&TestAction::Up); - }, - ); - - KeyCode::ArrowUp.press(app.world_mut()); - - // Frame 1: the FixedUpdate schedule should run once and the button should be just_pressed only once - app.update(); - check_update_just_pressed_count(&mut app, 1); - check_fixed_update_run_count(&mut app, 1); - check_fixed_update_just_pressed_count(&mut app, 1); - reset_counters(&mut app); - - // the button should still be consumed, even after we exit the FixedUpdate schedule - assert!( - app.world() - .get_resource::>() - .unwrap() - .button_data(&TestAction::Up) - .unwrap() - .consumed, - ); -} - -/// Check that if the action is consumed in F1, it will still be consumed in FU1. -/// (i.e. consuming is shared between the `FixedMain` and `Main` schedules) -#[test] -fn test_consume_in_update() { - let mut app = build_app(Duration::from_millis(5), Duration::from_millis(5)); - - KeyCode::ArrowUp.press(app.world_mut()); - fn consume_action(mut action: ResMut>) { - action.consume(&TestAction::Up); - } - - app.add_systems( - PreUpdate, - consume_action.in_set(InputManagerSystem::ManualControl), - ); - - app.add_systems(FixedUpdate, |action: Res>| { - // check that the action is still consumed in the FixedMain schedule - assert!( - action.consumed(&TestAction::Up), - "Action should still be consumed in FixedUpdate" - ); - }); - - // the FixedUpdate schedule should run once - app.update(); -}