Skip to content

Commit 2cffd14

Browse files
alice-i-cecilehymm
andauthored
Ensure that events are updated even when using a bare-bones Bevy App (#13808)
# Objective 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 #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 #11528, and introduced by #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. ## Solution 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! ## Testing 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. ## Outstanding - [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 <[email protected]>
1 parent 435d9bc commit 2cffd14

File tree

6 files changed

+290
-16
lines changed

6 files changed

+290
-16
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/collections.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,41 @@ impl<E: Event> ExactSizeIterator for SendBatchIds<E> {
366366
self.event_count.saturating_sub(self.last_count)
367367
}
368368
}
369+
370+
#[cfg(test)]
371+
mod tests {
372+
use crate::{self as bevy_ecs, event::Events};
373+
use bevy_ecs_macros::Event;
374+
375+
#[test]
376+
fn iter_current_update_events_iterates_over_current_events() {
377+
#[derive(Event, Clone)]
378+
struct TestEvent;
379+
380+
let mut test_events = Events::<TestEvent>::default();
381+
382+
// Starting empty
383+
assert_eq!(test_events.len(), 0);
384+
assert_eq!(test_events.iter_current_update_events().count(), 0);
385+
test_events.update();
386+
387+
// Sending one event
388+
test_events.send(TestEvent);
389+
390+
assert_eq!(test_events.len(), 1);
391+
assert_eq!(test_events.iter_current_update_events().count(), 1);
392+
test_events.update();
393+
394+
// Sending two events on the next frame
395+
test_events.send(TestEvent);
396+
test_events.send(TestEvent);
397+
398+
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
399+
assert_eq!(test_events.iter_current_update_events().count(), 2);
400+
test_events.update();
401+
402+
// Sending zero events
403+
assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2
404+
assert_eq!(test_events.iter_current_update_events().count(), 0);
405+
}
406+
}

crates/bevy_ecs/src/event/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub use bevy_ecs_macros::Event;
1313
pub use collections::{Events, SendBatchIds};
1414
pub use iterators::{EventIterator, EventIteratorWithId, EventParIter};
1515
pub use reader::{EventReader, ManualEventReader};
16-
pub use registry::EventRegistry;
16+
pub use registry::{EventRegistry, ShouldUpdateEvents};
1717
pub use update::{
1818
event_update_condition, event_update_system, signal_event_update_system, EventUpdates,
1919
};

crates/bevy_ecs/src/event/registry.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,29 @@ struct RegisteredEvent {
1717
update: unsafe fn(MutUntyped),
1818
}
1919

20-
/// A registry of all of the [`Events`] in the [`World`], used by [`event_update_system`]
20+
/// A registry of all of the [`Events`] in the [`World`], used by [`event_update_system`](crate::event::update::event_update_system)
2121
/// to update all of the events.
2222
#[derive(Resource, Default)]
2323
pub struct EventRegistry {
24-
pub(super) needs_update: bool,
24+
/// Should the events be updated?
25+
///
26+
/// This field is generally automatically updated by the [`signal_event_update_system`](crate::event::update::signal_event_update_system).
27+
pub should_update: ShouldUpdateEvents,
2528
event_updates: Vec<RegisteredEvent>,
2629
}
2730

31+
/// Controls whether or not the events in an [`EventRegistry`] should be updated.
32+
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
33+
pub enum ShouldUpdateEvents {
34+
/// Without any fixed timestep, events should always be updated each frame.
35+
#[default]
36+
Always,
37+
/// We need to wait until at least one pass of the fixed update schedules to update the events.
38+
Waiting,
39+
/// At least one pass of the fixed update schedules has occurred, and the events are ready to be updated.
40+
Ready,
41+
}
42+
2843
impl EventRegistry {
2944
/// Registers an event type to be updated.
3045
pub fn register_event<T: Event>(world: &mut World) {

crates/bevy_ecs/src/event/update.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,19 @@ use bevy_ecs_macros::SystemSet;
1010
#[cfg(feature = "bevy_reflect")]
1111
use std::hash::Hash;
1212

13+
use super::registry::ShouldUpdateEvents;
14+
1315
#[doc(hidden)]
1416
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
1517
pub struct EventUpdates;
1618

1719
/// Signals the [`event_update_system`] to run after `FixedUpdate` systems.
20+
///
21+
/// This will change the behavior of the [`EventRegistry`] to only run after a fixed update cycle has passed.
22+
/// Normally, this will simply run every frame.
1823
pub fn signal_event_update_system(signal: Option<ResMut<EventRegistry>>) {
1924
if let Some(mut registry) = signal {
20-
registry.needs_update = true;
25+
registry.should_update = ShouldUpdateEvents::Ready;
2126
}
2227
}
2328

@@ -26,16 +31,32 @@ pub fn event_update_system(world: &mut World, mut last_change_tick: Local<Tick>)
2631
if world.contains_resource::<EventRegistry>() {
2732
world.resource_scope(|world, mut registry: Mut<EventRegistry>| {
2833
registry.run_updates(world, *last_change_tick);
29-
// Disable the system until signal_event_update_system runs again.
30-
registry.needs_update = false;
34+
35+
registry.should_update = match registry.should_update {
36+
// If we're always updating, keep doing so.
37+
ShouldUpdateEvents::Always => ShouldUpdateEvents::Always,
38+
// Disable the system until signal_event_update_system runs again.
39+
ShouldUpdateEvents::Waiting | ShouldUpdateEvents::Ready => {
40+
ShouldUpdateEvents::Waiting
41+
}
42+
};
3143
});
3244
}
3345
*last_change_tick = world.change_tick();
3446
}
3547

3648
/// A run condition for [`event_update_system`].
37-
pub fn event_update_condition(signal: Option<Res<EventRegistry>>) -> bool {
38-
// If we haven't got a signal to update the events, but we *could* get such a signal
39-
// return early and update the events later.
40-
signal.map_or(false, |signal| signal.needs_update)
49+
///
50+
/// If [`signal_event_update_system`] has been run at least once,
51+
/// we will wait for it to be run again before updating the events.
52+
///
53+
/// Otherwise, we will always update the events.
54+
pub fn event_update_condition(maybe_signal: Option<Res<EventRegistry>>) -> bool {
55+
match maybe_signal {
56+
Some(signal) => match signal.should_update {
57+
ShouldUpdateEvents::Always | ShouldUpdateEvents::Ready => true,
58+
ShouldUpdateEvents::Waiting => false,
59+
},
60+
None => true,
61+
}
4162
}

0 commit comments

Comments
 (0)