Skip to content

Commit 4eaba9a

Browse files
alice-i-cecilehymm
authored andcommitted
Ensure that events are updated even when using a bare-bones Bevy App (bevyengine#13808)
As discovered in Leafwing-Studios/leafwing-input-manager#538, there appears to be some real weirdness going on in how event updates are processed between Bevy 0.13 and Bevy 0.14. To identify the cause and prevent regression, I've added tests to validate the intended behavior. My initial suspicion was that this would be fixed by bevyengine#13762, but that doesn't seem to be the case. Instead, events appear to never be updated at all when using `bevy_app` by itself. This is part of the problem resolved by bevyengine#11528, and introduced by bevyengine#10077. After some investigation, it appears that `signal_event_update_system` is never added using a bare-bones `App`, and so event updates are always skipped. This can be worked around by adding your own copy to a later-in-the-frame schedule, but that's not a very good fix. Ensure that if we're not using a `FixedUpdate` schedule, events are always updated every frame. To do this, I've modified the logic of `event_update_condition` and `event_update_system` to clearly and correctly differentiate between the two cases: where we're waiting for a "you should update now" signal and where we simply don't care. To encode this, I've added the `ShouldUpdateEvents` enum, replacing a simple `bool` in `EventRegistry`'s `needs_update` field. Now, both tests pass as expected, without having to manually add a system! I've written two parallel unit tests to cover the intended behavior: 1. Test that `iter_current_update_events` works as expected in `bevy_ecs`. 2. Test that `iter_current_update_events` works as expected in `bevy_app` I've also added a test to verify that event updating works correctly in the presence of a fixed main schedule, and a second test to verify that fixed updating works at all to help future authors narrow down failures. - [x] figure out why the `bevy_app` version of this test fails but the `bevy_ecs` version does not - [x] figure out why `EventRegistry::run_updates` isn't working properly - [x] figure out why `EventRegistry::run_updates` is never getting called - [x] figure out why `event_update_condition` is always returning false - [x] figure out why `EventRegistry::needs_update` is always false - [x] verify that the problem is a missing `signal_events_update_system` --------- Co-authored-by: Mike <mike.hsu@gmail.com>
1 parent eca7e87 commit 4eaba9a

File tree

4 files changed

+287
-15
lines changed

4 files changed

+287
-15
lines changed

crates/bevy_app/src/app.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,7 @@ mod tests {
925925
change_detection::{DetectChanges, ResMut},
926926
component::Component,
927927
entity::Entity,
928-
event::EventWriter,
928+
event::{Event, EventWriter, Events},
929929
query::With,
930930
removal_detection::RemovedComponents,
931931
schedule::{IntoSystemConfigs, ScheduleLabel},
@@ -1274,4 +1274,41 @@ mod tests {
12741274
.init_non_send_resource::<NonSendTestResource>()
12751275
.init_resource::<TestResource>();
12761276
}
1277+
1278+
#[test]
1279+
fn events_should_be_updated_once_per_update() {
1280+
#[derive(Event, Clone)]
1281+
struct TestEvent;
1282+
1283+
let mut app = App::new();
1284+
app.add_event::<TestEvent>();
1285+
1286+
// Starts empty
1287+
let test_events = app.world().resource::<Events<TestEvent>>();
1288+
assert_eq!(test_events.len(), 0);
1289+
assert_eq!(test_events.iter_current_update_events().count(), 0);
1290+
app.update();
1291+
1292+
// Sending one event
1293+
app.world_mut().send_event(TestEvent);
1294+
1295+
let test_events = app.world().resource::<Events<TestEvent>>();
1296+
assert_eq!(test_events.len(), 1);
1297+
assert_eq!(test_events.iter_current_update_events().count(), 1);
1298+
app.update();
1299+
1300+
// Sending two events on the next frame
1301+
app.world_mut().send_event(TestEvent);
1302+
app.world_mut().send_event(TestEvent);
1303+
1304+
let test_events = app.world().resource::<Events<TestEvent>>();
1305+
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
1306+
assert_eq!(test_events.iter_current_update_events().count(), 2);
1307+
app.update();
1308+
1309+
// Sending zero events
1310+
let test_events = app.world().resource::<Events<TestEvent>>();
1311+
assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2
1312+
assert_eq!(test_events.iter_current_update_events().count(), 0);
1313+
}
12771314
}

crates/bevy_ecs/src/event.rs

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,10 +1010,25 @@ struct RegisteredEvent {
10101010
/// to update all of the events.
10111011
#[derive(Resource, Default)]
10121012
pub struct EventRegistry {
1013-
needs_update: bool,
1013+
/// Should the events be updated?
1014+
///
1015+
/// This field is generally automatically updated by the [`signal_event_update_system`](crate::event::update::signal_event_update_system).
1016+
pub should_update: ShouldUpdateEvents,
10141017
event_updates: Vec<RegisteredEvent>,
10151018
}
10161019

1020+
/// Controls whether or not the events in an [`EventRegistry`] should be updated.
1021+
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1022+
pub enum ShouldUpdateEvents {
1023+
/// Without any fixed timestep, events should always be updated each frame.
1024+
#[default]
1025+
Always,
1026+
/// We need to wait until at least one pass of the fixed update schedules to update the events.
1027+
Waiting,
1028+
/// At least one pass of the fixed update schedules has occurred, and the events are ready to be updated.
1029+
Ready,
1030+
}
1031+
10171032
impl EventRegistry {
10181033
/// Registers an event type to be updated.
10191034
pub fn register_event<T: Event>(world: &mut World) {
@@ -1058,9 +1073,12 @@ impl EventRegistry {
10581073
pub struct EventUpdates;
10591074

10601075
/// Signals the [`event_update_system`] to run after `FixedUpdate` systems.
1076+
///
1077+
/// This will change the behavior of the [`EventRegistry`] to only run after a fixed update cycle has passed.
1078+
/// Normally, this will simply run every frame.
10611079
pub fn signal_event_update_system(signal: Option<ResMut<EventRegistry>>) {
10621080
if let Some(mut registry) = signal {
1063-
registry.needs_update = true;
1081+
registry.should_update = ShouldUpdateEvents::Ready;
10641082
}
10651083
}
10661084

@@ -1069,18 +1087,34 @@ pub fn event_update_system(world: &mut World, mut last_change_tick: Local<Tick>)
10691087
if world.contains_resource::<EventRegistry>() {
10701088
world.resource_scope(|world, mut registry: Mut<EventRegistry>| {
10711089
registry.run_updates(world, *last_change_tick);
1072-
// Disable the system until signal_event_update_system runs again.
1073-
registry.needs_update = false;
1090+
1091+
registry.should_update = match registry.should_update {
1092+
// If we're always updating, keep doing so.
1093+
ShouldUpdateEvents::Always => ShouldUpdateEvents::Always,
1094+
// Disable the system until signal_event_update_system runs again.
1095+
ShouldUpdateEvents::Waiting | ShouldUpdateEvents::Ready => {
1096+
ShouldUpdateEvents::Waiting
1097+
}
1098+
};
10741099
});
10751100
}
10761101
*last_change_tick = world.change_tick();
10771102
}
10781103

10791104
/// A run condition for [`event_update_system`].
1080-
pub fn event_update_condition(signal: Option<Res<EventRegistry>>) -> bool {
1081-
// If we haven't got a signal to update the events, but we *could* get such a signal
1082-
// return early and update the events later.
1083-
signal.map_or(false, |signal| signal.needs_update)
1105+
///
1106+
/// If [`signal_event_update_system`] has been run at least once,
1107+
/// we will wait for it to be run again before updating the events.
1108+
///
1109+
/// Otherwise, we will always update the events.
1110+
pub fn event_update_condition(maybe_signal: Option<Res<EventRegistry>>) -> bool {
1111+
match maybe_signal {
1112+
Some(signal) => match signal.should_update {
1113+
ShouldUpdateEvents::Always | ShouldUpdateEvents::Ready => true,
1114+
ShouldUpdateEvents::Waiting => false,
1115+
},
1116+
None => true,
1117+
}
10841118
}
10851119

10861120
/// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch.
@@ -1541,4 +1575,42 @@ mod tests {
15411575
});
15421576
schedule.run(&mut world);
15431577
}
1578+
1579+
#[test]
1580+
fn iter_current_update_events_iterates_over_current_events() {
1581+
#[derive(Event, Clone)]
1582+
struct TestEvent;
1583+
1584+
let mut test_events = Events::<TestEvent>::default();
1585+
1586+
// Starting empty
1587+
assert_eq!(test_events.len(), 0);
1588+
assert_eq!(test_events.iter_current_update_events().count(), 0);
1589+
test_events.update();
1590+
1591+
// Sending one event
1592+
test_events.send(TestEvent);
1593+
1594+
assert_eq!(test_events.len(), 1);
1595+
assert_eq!(test_events.iter_current_update_events().count(), 1);
1596+
test_events.update();
1597+
1598+
// Sending two events on the next frame
1599+
test_events.send(TestEvent);
1600+
test_events.send(TestEvent);
1601+
1602+
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
1603+
assert_eq!(test_events.iter_current_update_events().count(), 2);
1604+
test_events.update();
1605+
1606+
// Sending zero events
1607+
assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2
1608+
assert_eq!(test_events.iter_current_update_events().count(), 0);
1609+
}
15441610
}
1611+
1612+
// #[cfg(test)]
1613+
// mod tests {
1614+
// use crate::{self as bevy_ecs, event::Events};
1615+
// use bevy_ecs_macros::Event;
1616+
// }

crates/bevy_ecs/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub mod prelude {
4545
change_detection::{DetectChanges, DetectChangesMut, Mut, Ref},
4646
component::Component,
4747
entity::{Entity, EntityMapper},
48-
event::{Event, EventReader, EventWriter, Events},
48+
event::{Event, EventReader, EventWriter, Events, ShouldUpdateEvents},
4949
query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
5050
removal_detection::RemovedComponents,
5151
schedule::{

crates/bevy_time/src/lib.rs

Lines changed: 168 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub mod prelude {
3030
}
3131

3232
use bevy_app::{prelude::*, RunFixedMainLoop};
33-
use bevy_ecs::event::signal_event_update_system;
33+
use bevy_ecs::event::{signal_event_update_system, EventRegistry, ShouldUpdateEvents};
3434
use bevy_ecs::prelude::*;
3535
use bevy_utils::{tracing::warn, Duration, Instant};
3636
pub use crossbeam_channel::TrySendError;
@@ -65,8 +65,11 @@ impl Plugin for TimePlugin {
6565
app.add_systems(First, time_system.in_set(TimeSystem))
6666
.add_systems(RunFixedMainLoop, run_fixed_main_schedule);
6767

68-
// ensure the events are not dropped until `FixedMain` systems can observe them
68+
// Ensure the events are not dropped until `FixedMain` systems can observe them
6969
app.add_systems(FixedPostUpdate, signal_event_update_system);
70+
let mut event_registry = app.world_mut().resource_mut::<EventRegistry>();
71+
// We need to start in a waiting state so that the events are not updated until the first fixed update
72+
event_registry.should_update = ShouldUpdateEvents::Waiting;
7073
}
7174
}
7275

@@ -141,9 +144,13 @@ fn time_system(
141144

142145
#[cfg(test)]
143146
mod tests {
144-
use crate::{Fixed, Time, TimePlugin, TimeUpdateStrategy};
145-
use bevy_app::{App, Startup, Update};
146-
use bevy_ecs::event::{Event, EventReader, EventWriter};
147+
use crate::{Fixed, Time, TimePlugin, TimeUpdateStrategy, Virtual};
148+
use bevy_app::{App, FixedUpdate, Startup, Update};
149+
use bevy_ecs::{
150+
event::{Event, EventReader, EventRegistry, EventWriter, Events, ShouldUpdateEvents},
151+
system::{Local, Res, ResMut, Resource},
152+
};
153+
use bevy_utils::Duration;
147154
use std::error::Error;
148155

149156
#[derive(Event)]
@@ -159,6 +166,91 @@ mod tests {
159166
}
160167
}
161168

169+
#[derive(Event)]
170+
struct DummyEvent;
171+
172+
#[derive(Resource, Default)]
173+
struct FixedUpdateCounter(u8);
174+
175+
fn count_fixed_updates(mut counter: ResMut<FixedUpdateCounter>) {
176+
counter.0 += 1;
177+
}
178+
179+
fn report_time(
180+
mut frame_count: Local<u64>,
181+
virtual_time: Res<Time<Virtual>>,
182+
fixed_time: Res<Time<Fixed>>,
183+
) {
184+
println!(
185+
"Virtual time on frame {}: {:?}",
186+
*frame_count,
187+
virtual_time.elapsed()
188+
);
189+
println!(
190+
"Fixed time on frame {}: {:?}",
191+
*frame_count,
192+
fixed_time.elapsed()
193+
);
194+
195+
*frame_count += 1;
196+
}
197+
198+
#[test]
199+
fn fixed_main_schedule_should_run_with_time_plugin_enabled() {
200+
// Set the time step to just over half the fixed update timestep
201+
// This way, it will have not accumulated enough time to run the fixed update after one update
202+
// But will definitely have enough time after two updates
203+
let fixed_update_timestep = Time::<Fixed>::default().timestep();
204+
let time_step = fixed_update_timestep / 2 + Duration::from_millis(1);
205+
206+
let mut app = App::new();
207+
app.add_plugins(TimePlugin)
208+
.add_systems(FixedUpdate, count_fixed_updates)
209+
.add_systems(Update, report_time)
210+
.init_resource::<FixedUpdateCounter>()
211+
.insert_resource(TimeUpdateStrategy::ManualDuration(time_step));
212+
213+
// Frame 0
214+
// Fixed update should not have run yet
215+
app.update();
216+
217+
assert!(Duration::ZERO < fixed_update_timestep);
218+
let counter = app.world().resource::<FixedUpdateCounter>();
219+
assert_eq!(counter.0, 0, "Fixed update should not have run yet");
220+
221+
// Frame 1
222+
// Fixed update should not have run yet
223+
app.update();
224+
225+
assert!(time_step < fixed_update_timestep);
226+
let counter = app.world().resource::<FixedUpdateCounter>();
227+
assert_eq!(counter.0, 0, "Fixed update should not have run yet");
228+
229+
// Frame 2
230+
// Fixed update should have run now
231+
app.update();
232+
233+
assert!(2 * time_step > fixed_update_timestep);
234+
let counter = app.world().resource::<FixedUpdateCounter>();
235+
assert_eq!(counter.0, 1, "Fixed update should have run once");
236+
237+
// Frame 3
238+
// Fixed update should have run exactly once still
239+
app.update();
240+
241+
assert!(3 * time_step < 2 * fixed_update_timestep);
242+
let counter = app.world().resource::<FixedUpdateCounter>();
243+
assert_eq!(counter.0, 1, "Fixed update should have run once");
244+
245+
// Frame 4
246+
// Fixed update should have run twice now
247+
app.update();
248+
249+
assert!(4 * time_step > 2 * fixed_update_timestep);
250+
let counter = app.world().resource::<FixedUpdateCounter>();
251+
assert_eq!(counter.0, 2, "Fixed update should have run twice");
252+
}
253+
162254
#[test]
163255
fn events_get_dropped_regression_test_11528() -> Result<(), impl Error> {
164256
let (tx1, rx1) = std::sync::mpsc::channel();
@@ -199,4 +291,75 @@ mod tests {
199291
// Check event type 2 has been dropped
200292
rx2.try_recv()
201293
}
294+
295+
#[test]
296+
fn event_update_should_wait_for_fixed_main() {
297+
// Set the time step to just over half the fixed update timestep
298+
// This way, it will have not accumulated enough time to run the fixed update after one update
299+
// But will definitely have enough time after two updates
300+
let fixed_update_timestep = Time::<Fixed>::default().timestep();
301+
let time_step = fixed_update_timestep / 2 + Duration::from_millis(1);
302+
303+
fn send_event(mut events: ResMut<Events<DummyEvent>>) {
304+
events.send(DummyEvent);
305+
}
306+
307+
let mut app = App::new();
308+
app.add_plugins(TimePlugin)
309+
.add_event::<DummyEvent>()
310+
.init_resource::<FixedUpdateCounter>()
311+
.add_systems(Startup, send_event)
312+
.add_systems(FixedUpdate, count_fixed_updates)
313+
.insert_resource(TimeUpdateStrategy::ManualDuration(time_step));
314+
315+
for frame in 0..10 {
316+
app.update();
317+
let fixed_updates_seen = app.world().resource::<FixedUpdateCounter>().0;
318+
let events = app.world().resource::<Events<DummyEvent>>();
319+
let n_total_events = events.len();
320+
let n_current_events = events.iter_current_update_events().count();
321+
let event_registry = app.world().resource::<EventRegistry>();
322+
let should_update = event_registry.should_update;
323+
324+
println!("Frame {frame}, {fixed_updates_seen} fixed updates seen. Should update: {should_update:?}");
325+
println!("Total events: {n_total_events} | Current events: {n_current_events}",);
326+
327+
match frame {
328+
0 | 1 => {
329+
assert_eq!(fixed_updates_seen, 0);
330+
assert_eq!(n_total_events, 1);
331+
assert_eq!(n_current_events, 1);
332+
assert_eq!(should_update, ShouldUpdateEvents::Waiting);
333+
}
334+
2 => {
335+
assert_eq!(fixed_updates_seen, 1); // Time to trigger event updates
336+
assert_eq!(n_total_events, 1);
337+
assert_eq!(n_current_events, 1);
338+
assert_eq!(should_update, ShouldUpdateEvents::Ready); // Prepping first update
339+
}
340+
3 => {
341+
assert_eq!(fixed_updates_seen, 1);
342+
assert_eq!(n_total_events, 1);
343+
assert_eq!(n_current_events, 0); // First update has occurred
344+
assert_eq!(should_update, ShouldUpdateEvents::Waiting);
345+
}
346+
4 => {
347+
assert_eq!(fixed_updates_seen, 2); // Time to trigger the second update
348+
assert_eq!(n_total_events, 1);
349+
assert_eq!(n_current_events, 0);
350+
assert_eq!(should_update, ShouldUpdateEvents::Ready); // Prepping second update
351+
}
352+
5 => {
353+
assert_eq!(fixed_updates_seen, 2);
354+
assert_eq!(n_total_events, 0); // Second update has occurred
355+
assert_eq!(n_current_events, 0);
356+
assert_eq!(should_update, ShouldUpdateEvents::Waiting);
357+
}
358+
_ => {
359+
assert_eq!(n_total_events, 0); // No more events are sent
360+
assert_eq!(n_current_events, 0);
361+
}
362+
}
363+
}
364+
}
202365
}

0 commit comments

Comments
 (0)