Skip to content

Commit

Permalink
Handle just_pressed/just_released in the FixedUpdate schedule (#…
Browse files Browse the repository at this point in the history
…522)

* both tests pass

* simplify implementation

* clippy

* update benchmarks

* add consume tests

* fmt

* clean up tests

* fmt

* remove disable/enable logic

* update

* update timing test

* fix tests for timing feature

* lint

* add release notes

* lint

---------

Co-authored-by: Charles Bournhonesque <[email protected]>
  • Loading branch information
cBournhonesque and cbournhonesque-sc authored Jun 10, 2024
1 parent b80eb52 commit a513ad0
Show file tree
Hide file tree
Showing 6 changed files with 367 additions and 2 deletions.
3 changes: 3 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

### Enhancements

- Inputs are now handled correctly in the `FixedUpdate` schedule! Previously, the `ActionState`s were only updated in the `PreUpdate` schedule, so you could have situations where an action was
marked as `just_pressed` multiple times in a row (if the `FixedUpdate` schedule ran multiple times in a frame) or was missed entirely (if the `FixedUpdate` schedule ran 0 times in a frame).

#### Input Processors

Input processors allow you to create custom logic for axis-like input manipulation.
Expand Down
2 changes: 2 additions & 0 deletions benches/action_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ fn criterion_benchmark(c: &mut Criterion) {
action,
ActionData {
state: ButtonState::JustPressed,
update_state: ButtonState::Released,
fixed_update_state: ButtonState::Released,
value: 0.0,
axis_pair: None,
#[cfg(feature = "timing")]
Expand Down
28 changes: 28 additions & 0 deletions src/action_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ use serde::{Deserialize, Serialize};
pub struct ActionData {
/// Is the action pressed or released?
pub state: ButtonState,
/// The `state` of the action in the `Main` schedule
pub update_state: ButtonState,
/// The `state` of the action in the `FixedMain` schedule
pub fixed_update_state: ButtonState,
/// The "value" of the binding that triggered the action.
///
/// See [`ActionState::value`] for more details.
Expand Down Expand Up @@ -136,6 +140,30 @@ impl<A: Actionlike> Default for ActionState<A> {
}

impl<A: Actionlike> ActionState<A> {
/// We are about to enter the `Main` schedule, so we:
/// - save all the changes applied to `state` into the `fixed_update_state`
/// - switch to loading the `update_state`
pub(crate) fn swap_to_update_state(&mut self) {
for (_action, action_datum) in self.action_data.iter_mut() {
// save the changes applied to `state` into `fixed_update_state`
action_datum.fixed_update_state = action_datum.state;
// switch to loading the `update_state` into `state`
action_datum.state = action_datum.update_state;
}
}

/// We are about to enter the `FixedMain` schedule, so we:
/// - save all the changes applied to `state` into the `update_state`
/// - switch to loading the `fixed_update_state`
pub(crate) fn swap_to_fixed_update_state(&mut self) {
for (_action, action_datum) in self.action_data.iter_mut() {
// save the changes applied to `state` into `update_state`
action_datum.update_state = action_datum.state;
// switch to loading the `fixed_update_state` into `state`
action_datum.state = action_datum.fixed_update_state;
}
}

/// Updates the [`ActionState`] based on a vector of [`ActionData`], ordered by [`Actionlike::id`](Actionlike).
///
/// The `action_data` is typically constructed from [`InputMap::which_pressed`](crate::input_map::InputMap),
Expand Down
38 changes: 36 additions & 2 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ use core::hash::Hash;
use core::marker::PhantomData;
use std::fmt::Debug;

use bevy::app::{App, Plugin};
use bevy::app::{App, Plugin, RunFixedMainLoop};
use bevy::ecs::prelude::*;
use bevy::input::{ButtonState, InputSystem};
use bevy::prelude::{PostUpdate, PreUpdate};
use bevy::prelude::{FixedPostUpdate, PostUpdate, PreUpdate};
use bevy::reflect::TypePath;
use bevy::time::run_fixed_main_schedule;
#[cfg(feature = "ui")]
use bevy::ui::UiSystem;

Expand Down Expand Up @@ -100,6 +101,7 @@ impl<A: Actionlike + TypePath + bevy::reflect::GetTypeRegistration> Plugin

match self.machine {
Machine::Client => {
// Main schedule
app.add_systems(
PreUpdate,
tick_action_state::<A>
Expand Down Expand Up @@ -142,6 +144,38 @@ impl<A: Actionlike + TypePath + bevy::reflect::GetTypeRegistration> Plugin
update_action_state_from_interaction::<A>
.in_set(InputManagerSystem::ManualControl),
);

// FixedMain schedule
app.add_systems(
RunFixedMainLoop,
(
swap_to_fixed_update::<A>,
// we want to update the ActionState only once, even if the FixedMain schedule runs multiple times
update_action_state::<A>,
)
.chain()
.before(run_fixed_main_schedule),
);

#[cfg(feature = "ui")]
app.configure_sets(bevy::app::FixedPreUpdate, InputManagerSystem::ManualControl);
#[cfg(feature = "ui")]
app.add_systems(
bevy::app::FixedPreUpdate,
update_action_state_from_interaction::<A>
.in_set(InputManagerSystem::ManualControl),
);
app.add_systems(FixedPostUpdate, release_on_input_map_removed::<A>);
app.add_systems(
FixedPostUpdate,
tick_action_state::<A>
.in_set(InputManagerSystem::Tick)
.before(InputManagerSystem::Update),
);
app.add_systems(
RunFixedMainLoop,
swap_to_update::<A>.after(run_fixed_main_schedule),
);
}
Machine::Server => {
app.add_systems(
Expand Down
32 changes: 32 additions & 0 deletions src/systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,38 @@ use bevy::ui::Interaction;
#[cfg(feature = "egui")]
use bevy_egui::EguiContext;

/// We are about to enter the `Main` schedule, so we:
/// - save all the changes applied to `state` into the `fixed_update_state`
/// - switch to loading the `update_state`
pub fn swap_to_update<A: Actionlike>(
mut query: Query<&mut ActionState<A>>,
action_state: Option<ResMut<ActionState<A>>>,
) {
if let Some(mut action_state) = action_state {
action_state.swap_to_update_state();
}

for mut action_state in query.iter_mut() {
action_state.swap_to_update_state();
}
}

/// We are about to enter the `FixedMain` schedule, so we:
/// - save all the changes applied to `state` into the `update_state`
/// - switch to loading the `fixed_update_state`
pub fn swap_to_fixed_update<A: Actionlike>(
mut query: Query<&mut ActionState<A>>,
action_state: Option<ResMut<ActionState<A>>>,
) {
if let Some(mut action_state) = action_state {
action_state.swap_to_fixed_update_state();
}

for mut action_state in query.iter_mut() {
action_state.swap_to_fixed_update_state();
}
}

/// Advances actions timer.
///
/// Clears the just-pressed and just-released values of all [`ActionState`]s.
Expand Down
Loading

0 comments on commit a513ad0

Please sign in to comment.