Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add a clear() method to the EventReader that consumes the iterator #4693

Closed
wants to merge 15 commits into from
59 changes: 58 additions & 1 deletion crates/bevy_ecs/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,42 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
self.reader.len(&self.events)
}

/// Determines if are any events available to be read without consuming any.
/// Determines if no events are available to be read without consuming any.
/// If you need to consume the iterator you can use [`EventReader::clear`].
///
/// # Example
///
/// The following example shows a common pattern of this function in conjunction with `clear`
/// to avoid leaking events to the next schedule iteration while also checking if it was emitted.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// struct CollisionEvent;
///
/// fn play_collision_sound(events: EventReader<CollisionEvent>) {
/// if !events.is_empty() {
/// events.clear();
/// // Play a sound
/// }
/// }
/// # bevy_ecs::system::assert_is_system(play_collision_sound);
/// ```
pub fn is_empty(&self) -> bool {
self.len() == 0
}

/// Consumes the iterator.
///
/// This means all currently available events will be removed before the next frame.
/// This is useful when multiple events are sent in a single frame and you want
/// to react to one or more events without needing to know how many were sent.
/// In those situations you generally want to consume those events to make sure they don't appear in the next frame.
///
/// For more information see [`EventReader::is_empty()`].
pub fn clear(mut self) {
self.iter().last();
}
}

/// Sends events of type `T`.
Expand Down Expand Up @@ -727,6 +759,31 @@ mod tests {
assert!(reader.is_empty(&events));
}

#[test]
fn test_event_reader_clear() {
use bevy_ecs::prelude::*;

let mut world = World::new();
let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 });
world.insert_resource(events);

let mut reader = IntoSystem::into_system(|events: EventReader<TestEvent>| -> bool {
if !events.is_empty() {
events.clear();
false
} else {
true
}
});
reader.initialize(&mut world);

let is_empty = reader.run((), &mut world);
assert!(!is_empty, "EventReader should not be empty");
let is_empty = reader.run((), &mut world);
assert!(is_empty, "EventReader should be empty");
}

#[derive(Clone, PartialEq, Debug, Default)]
struct EmptyTestEvent;

Expand Down
9 changes: 5 additions & 4 deletions examples/games/breakout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,13 +411,14 @@ fn check_for_collisions(
}

fn play_collision_sound(
mut collision_events: EventReader<CollisionEvent>,
collision_events: EventReader<CollisionEvent>,
IceSentry marked this conversation as resolved.
Show resolved Hide resolved
audio: Res<Audio>,
sound: Res<CollisionSound>,
) {
// Play a sound once per frame if a collision occurred. `count` consumes the
// events, preventing them from triggering a sound on the next frame.
if collision_events.iter().count() > 0 {
// Play a sound once per frame if a collision occurred.
if !collision_events.is_empty() {
// This prevents events staying active on the next frame.
collision_events.clear();
audio.play(sound.0.clone());
}
}