Skip to content

Commit 0c199dd

Browse files
maniwanialice-i-cecile
authored andcommitted
Wait until FixedUpdate can see events before dropping them (#10077)
## Objective Currently, events are dropped after two frames. This cadence wasn't *chosen* for a specific reason, double buffering just lets events persist for at least two frames. Events only need to be dropped at a predictable point so that the event queues don't grow forever (i.e. events should never cause a memory leak). Events (and especially input events) need to be observable by systems in `FixedUpdate`, but as-is events are dropped before those systems even get a chance to see them. ## Solution Instead of unconditionally dropping events in `First`, require `FixedUpdate` to first queue the buffer swap (if the `TimePlugin` has been installed). This way, events are only dropped after a frame that runs `FixedUpdate`. ## Future Work In the same way we have independent copies of `Time` for tracking time in `Main` and `FixedUpdate`, we will need independent copies of `Input` for tracking press/release status correctly in `Main` and `FixedUpdate`. -- Every run of `FixedUpdate` covers a specific timespan. For example, if the fixed timestep `Δt` is 10ms, the first three `FixedUpdate` runs cover `[0ms, 10ms)`, `[10ms, 20ms)`, and `[20ms, 30ms)`. `FixedUpdate` can run many times in one frame. For truly framerate-independent behavior, each `FixedUpdate` should only see the events that occurred in its covered timespan, but what happens right now is the first step in the frame reads all pending events. Fixing that will require timestamped events. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent 9cfc481 commit 0c199dd

File tree

3 files changed

+33
-7
lines changed

3 files changed

+33
-7
lines changed

crates/bevy_app/src/app.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ impl Default for App {
190190
app.init_resource::<AppTypeRegistry>();
191191

192192
app.add_plugins(MainSchedulePlugin);
193+
193194
app.add_event::<AppExit>();
194195

195196
#[cfg(feature = "bevy_ci_testing")]

crates/bevy_ecs/src/event.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -749,8 +749,30 @@ impl<'a, E: Event> ExactSizeIterator for EventIteratorWithId<'a, E> {
749749
}
750750
}
751751

752-
/// A system that calls [`Events::update`] once per frame.
753-
pub fn event_update_system<T: Event>(mut events: ResMut<Events<T>>) {
752+
#[doc(hidden)]
753+
#[derive(Resource, Default)]
754+
pub struct EventUpdateSignal(bool);
755+
756+
/// A system that queues a call to [`Events::update`].
757+
pub fn event_queue_update_system(signal: Option<ResMut<EventUpdateSignal>>) {
758+
if let Some(mut s) = signal {
759+
s.0 = true;
760+
}
761+
}
762+
763+
/// A system that calls [`Events::update`].
764+
pub fn event_update_system<T: Event>(
765+
signal: Option<ResMut<EventUpdateSignal>>,
766+
mut events: ResMut<Events<T>>,
767+
) {
768+
if let Some(mut s) = signal {
769+
// If we haven't got a signal to update the events, but we *could* get such a signal
770+
// return early and update the events later.
771+
if !std::mem::replace(&mut s.0, false) {
772+
return;
773+
}
774+
}
775+
754776
events.update();
755777
}
756778

crates/bevy_time/src/lib.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,18 @@ pub use time::*;
1919
pub use timer::*;
2020
pub use virt::*;
2121

22-
use bevy_ecs::system::{Res, ResMut};
23-
use bevy_utils::{tracing::warn, Duration, Instant};
24-
pub use crossbeam_channel::TrySendError;
25-
use crossbeam_channel::{Receiver, Sender};
26-
2722
pub mod prelude {
2823
//! The Bevy Time Prelude.
2924
#[doc(hidden)]
3025
pub use crate::{Fixed, Real, Time, Timer, TimerMode, Virtual};
3126
}
3227

3328
use bevy_app::{prelude::*, RunFixedUpdateLoop};
29+
use bevy_ecs::event::{event_queue_update_system, EventUpdateSignal};
3430
use bevy_ecs::prelude::*;
31+
use bevy_utils::{tracing::warn, Duration, Instant};
32+
pub use crossbeam_channel::TrySendError;
33+
use crossbeam_channel::{Receiver, Sender};
3534

3635
/// Adds time functionality to Apps.
3736
#[derive(Default)]
@@ -61,6 +60,10 @@ impl Plugin for TimePlugin {
6160
)
6261
.add_systems(RunFixedUpdateLoop, run_fixed_update_schedule);
6362

63+
// ensure the events are not dropped until `FixedUpdate` systems can observe them
64+
app.init_resource::<EventUpdateSignal>()
65+
.add_systems(FixedUpdate, event_queue_update_system);
66+
6467
#[cfg(feature = "bevy_ci_testing")]
6568
if let Some(ci_testing_config) = app
6669
.world

0 commit comments

Comments
 (0)