Skip to content

Commit

Permalink
Add a clear() method to the EventReader that consumes the iterator (b…
Browse files Browse the repository at this point in the history
…evyengine#4693)

# Objective

- It's pretty common to want to check if an EventReader has received one or multiple events while also needing to consume the iterator to "clear" the EventReader.
- The current approach is to do something like `events.iter().count() > 0` or `events.iter().last().is_some()`. It's not immediately obvious that the purpose of that is to consume the events and check if there were any events. My solution doesn't really solve that part, but it encapsulates the pattern.

## Solution

- Add a `.clear()` method that consumes the iterator.
	- It takes the EventReader by value to make sure it isn't used again after it has been called.

---

## Migration Guide

Not a breaking change, but if you ever found yourself in a situation where you needed to consume the EventReader and check if there was any events you can now use

```rust
fn system(events: EventReader<MyEvent>) {
	if !events.is_empty {
		events.clear();
		// Process the fact that one or more event was received
	}
}
```


Co-authored-by: Charles <IceSentry@users.noreply.github.com>
  • Loading branch information
2 people authored and robtfm committed May 17, 2022
1 parent e4af3b2 commit 3e493d3
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 5 deletions.
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>,
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());
}
}

0 comments on commit 3e493d3

Please sign in to comment.