Skip to content

Commit 53919c3

Browse files
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 9849221 commit 53919c3

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
@@ -733,8 +733,30 @@ impl<'a, E: Event> ExactSizeIterator for EventIteratorWithId<'a, E> {
733733
}
734734
}
735735

736-
/// A system that calls [`Events::update`] once per frame.
737-
pub fn event_update_system<T: Event>(mut events: ResMut<Events<T>>) {
736+
#[doc(hidden)]
737+
#[derive(Resource, Default)]
738+
pub struct EventUpdateSignal(bool);
739+
740+
/// A system that queues a call to [`Events::update`].
741+
pub fn event_queue_update_system(signal: Option<ResMut<EventUpdateSignal>>) {
742+
if let Some(mut s) = signal {
743+
s.0 = true;
744+
}
745+
}
746+
747+
/// A system that calls [`Events::update`].
748+
pub fn event_update_system<T: Event>(
749+
signal: Option<ResMut<EventUpdateSignal>>,
750+
mut events: ResMut<Events<T>>,
751+
) {
752+
if let Some(mut s) = signal {
753+
// If we haven't got a signal to update the events, but we *could* get such a signal
754+
// return early and update the events later.
755+
if !std::mem::replace(&mut s.0, false) {
756+
return;
757+
}
758+
}
759+
738760
events.update();
739761
}
740762

crates/bevy_time/src/lib.rs

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

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

3227
use bevy_app::{prelude::*, RunFixedUpdateLoop};
28+
use bevy_ecs::event::{event_queue_update_system, EventUpdateSignal};
3329
use bevy_ecs::prelude::*;
30+
use bevy_utils::{tracing::warn, Duration, Instant};
31+
pub use crossbeam_channel::TrySendError;
32+
use crossbeam_channel::{Receiver, Sender};
3433

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

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

0 commit comments

Comments
 (0)