Optimize .nth() and .last() for event iterators (#7530)

# Objective

Motivated by #7469.

`EventReader` iterators use the default implementations for `.nth()` and `.last()`, which includes iterating over and throwing out all events before the desired one.

## Solution

Add specialized implementations for these methods that directly updates the unread event counter and returns a reference to the desired event.

TODO:

- [x] Add a unit test.
- [x] ~~Add a benchmark, to see if the compiler was doing this automatically already.~~ *On second thought, this doesn't feel like a very useful thing to include in the benchmark suite.*
This commit is contained in:
JoJoJet 2023-02-16 23:34:18 +00:00
parent b1646e9cee
commit a95033b288

View file

@ -378,6 +378,17 @@ impl<'a, E: Event> Iterator for ManualEventIterator<'a, E> {
self.iter.next().map(|(event, _)| event)
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.iter.nth(n).map(|(event, _)| event)
}
fn last(self) -> Option<Self::Item>
where
Self: Sized,
{
self.iter.last().map(|(event, _)| event)
}
fn count(self) -> usize {
self.iter.count()
}
@ -449,6 +460,27 @@ impl<'a, E: Event> Iterator for ManualEventIteratorWithId<'a, E> {
}
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
if let Some(EventInstance { event_id, event }) = self.chain.nth(n) {
self.reader.last_event_count += n + 1;
self.unread -= n + 1;
Some((event, *event_id))
} else {
self.reader.last_event_count += self.unread;
self.unread = 0;
None
}
}
fn last(self) -> Option<Self::Item>
where
Self: Sized,
{
let EventInstance { event_id, event } = self.chain.last()?;
self.reader.last_event_count += self.unread;
Some((event, *event_id))
}
fn count(self) -> usize {
self.reader.last_event_count += self.unread;
self.unread
@ -880,6 +912,63 @@ mod tests {
assert!(is_empty, "EventReader should be empty");
}
#[allow(clippy::iter_nth_zero)]
#[test]
fn test_event_iter_nth() {
use bevy_ecs::prelude::*;
let mut world = World::new();
world.init_resource::<Events<TestEvent>>();
world.send_event(TestEvent { i: 0 });
world.send_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 });
world.send_event(TestEvent { i: 4 });
let mut schedule = Schedule::new();
schedule.add_system(|mut events: EventReader<TestEvent>| {
let mut iter = events.iter();
assert_eq!(iter.next(), Some(&TestEvent { i: 0 }));
assert_eq!(iter.nth(2), Some(&TestEvent { i: 3 }));
assert_eq!(iter.nth(1), None);
assert!(events.is_empty());
});
schedule.run(&mut world);
}
#[test]
fn test_event_iter_last() {
use bevy_ecs::prelude::*;
let mut world = World::new();
world.init_resource::<Events<TestEvent>>();
let mut reader =
IntoSystem::into_system(|mut events: EventReader<TestEvent>| -> Option<TestEvent> {
events.iter().last().copied()
});
reader.initialize(&mut world);
let last = reader.run((), &mut world);
assert!(last.is_none(), "EventReader should be empty");
world.send_event(TestEvent { i: 0 });
let last = reader.run((), &mut world);
assert_eq!(last, Some(TestEvent { i: 0 }));
world.send_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 });
let last = reader.run((), &mut world);
assert_eq!(last, Some(TestEvent { i: 3 }));
let last = reader.run((), &mut world);
assert!(last.is_none(), "EventReader should be empty");
}
#[derive(Clone, PartialEq, Debug, Default)]
struct EmptyTestEvent;