Add a clear() method to the EventReader that consumes the iterator (#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>
This commit is contained in:
Charles 2022-05-13 00:57:04 +00:00
parent 0166c4f7fc
commit dfee7879c3
2 changed files with 63 additions and 5 deletions

View file

@ -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`.
@ -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;

View file

@ -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());
}
}