Skip to content

Commit

Permalink
Created an EventMutator for when you want to mutate an event before r…
Browse files Browse the repository at this point in the history
…eading (#13818)

# Objective

- Often in games you will want to create chains of systems that modify
some event. For example, a chain of damage systems that handle a
DamageEvent and modify the underlying value before the health system
finally consumes the event. Right now this requires either:

* Using a component added to the entity
* Consuming and refiring events

Neither is ideal when really all we want to do is read the events value,
modify it, and write it back.

## Solution

- Create an EventMutator class similar to EventReader but with ResMut<T>
and iterators that return &mut so that events can be mutated.

## Testing

- I replicated all the existing tests for EventReader to make sure
behavior was the same (I believe) and added a number of tests specific
to testing that 1) events can actually be mutated, and that 2)
EventReader sees changes from EventMutator for events it hasn't already
seen.

## Migration Guide

Users currently using `ManualEventReader` should use `EventCursor`
instead. `ManualEventReader` will be removed in Bevy 0.16. Additionally,
`Events::get_reader` has been replaced by `Events::get_cursor`.

Users currently directly accessing the `Events` resource for mutation
should move to `EventMutator` if possible.

---------

Co-authored-by: poopy <gonesbird@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
  • Loading branch information
3 people committed Jul 8, 2024
1 parent 8df10d2 commit ec1aa48
Show file tree
Hide file tree
Showing 17 changed files with 1,099 additions and 393 deletions.
4 changes: 2 additions & 2 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
};
pub use bevy_derive::AppLabel;
use bevy_ecs::{
event::{event_update_system, ManualEventReader},
event::{event_update_system, EventCursor},
intern::Interned,
prelude::*,
schedule::{ScheduleBuildSettings, ScheduleLabel},
Expand Down Expand Up @@ -818,7 +818,7 @@ impl App {
/// This should be called after every [`update()`](App::update) otherwise you risk
/// dropping possible [`AppExit`] events.
pub fn should_exit(&self) -> Option<AppExit> {
let mut reader = ManualEventReader::default();
let mut reader = EventCursor::default();

let events = self.world().get_resource::<Events<AppExit>>()?;
let mut events = reader.read(events);
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ mod tests {
use bevy_core::TaskPoolPlugin;
use bevy_ecs::prelude::*;
use bevy_ecs::{
event::ManualEventReader,
event::EventCursor,
schedule::{LogLevel, ScheduleBuildSettings},
};
use bevy_log::LogPlugin;
Expand Down Expand Up @@ -1300,7 +1300,7 @@ mod tests {
gate_opener.open(b_path);
gate_opener.open(c_path);

let mut reader = ManualEventReader::default();
let mut reader = EventCursor::default();
run_app_until(&mut app, |world| {
let events = world.resource::<Events<AssetEvent<LoadedFolder>>>();
let asset_server = world.resource::<AssetServer>();
Expand Down
36 changes: 29 additions & 7 deletions crates/bevy_ecs/src/event/collections.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate as bevy_ecs;
use bevy_ecs::{
event::{Event, EventId, EventInstance, ManualEventReader},
event::{Event, EventCursor, EventId, EventInstance},
system::Resource,
};
#[cfg(feature = "bevy_reflect")]
Expand Down Expand Up @@ -153,15 +153,37 @@ impl<E: Event> Events<E> {
self.send(Default::default())
}

/// Gets a new [`ManualEventReader`]. This will include all events already in the event buffers.
pub fn get_reader(&self) -> ManualEventReader<E> {
ManualEventReader::default()
/// Gets a new [`EventCursor`]. This will include all events already in the event buffers.
pub fn get_cursor(&self) -> EventCursor<E> {
EventCursor::default()
}

/// Gets a new [`ManualEventReader`]. This will ignore all events already in the event buffers.
/// Gets a new [`EventCursor`]. This will ignore all events already in the event buffers.
/// It will read all future events.
pub fn get_reader_current(&self) -> ManualEventReader<E> {
ManualEventReader {
pub fn get_cursor_current(&self) -> EventCursor<E> {
EventCursor {
last_event_count: self.event_count,
..Default::default()
}
}

#[deprecated(
since = "0.14.0",
note = "`get_reader` has been deprecated. Please use `get_cursor` instead."
)]
/// Gets a new [`EventCursor`]. This will include all events already in the event buffers.
pub fn get_reader(&self) -> EventCursor<E> {
EventCursor::default()
}

#[deprecated(
since = "0.14.0",
note = "`get_reader_current` has been replaced. Please use `get_cursor_current` instead."
)]
/// Gets a new [`EventCursor`]. This will ignore all events already in the event buffers.
/// It will read all future events.
pub fn get_reader_current(&self) -> EventCursor<E> {
EventCursor {
last_event_count: self.event_count,
..Default::default()
}
Expand Down
150 changes: 150 additions & 0 deletions crates/bevy_ecs/src/event/event_cursor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use crate as bevy_ecs;
use bevy_ecs::event::{
Event, EventIterator, EventIteratorWithId, EventMutIterator, EventMutIteratorWithId, Events,
};
#[cfg(feature = "multi_threaded")]
use bevy_ecs::event::{EventMutParIter, EventParIter};
use std::marker::PhantomData;

// Deprecated in favor of `EventCursor`, there is no nice way to deprecate this
// because generic constraints are not allowed in type aliases, so this will always
// 'dead code'. Hence the `#[allow(dead_code)]`.
#[deprecated(
since = "0.14.0",
note = "`ManualEventReader` has been replaced. Please use `EventCursor` instead."
)]
#[doc(alias = "EventCursor")]
#[allow(dead_code)]
pub type ManualEventReader<E> = EventCursor<E>;

/// Stores the state for an [`EventReader`] or [`EventMutator`].
///
/// Access to the [`Events<E>`] resource is required to read any incoming events.
///
/// In almost all cases, you should just use an [`EventReader`] or [`EventMutator`],
/// which will automatically manage the state for you.
///
/// However, this type can be useful if you need to manually track events,
/// such as when you're attempting to send and receive events of the same type in the same system.
///
/// # Example
///
/// ```
/// use bevy_ecs::prelude::*;
/// use bevy_ecs::event::{Event, Events, EventCursor};
///
/// #[derive(Event, Clone, Debug)]
/// struct MyEvent;
///
/// /// A system that both sends and receives events using a [`Local`] [`EventCursor`].
/// fn send_and_receive_events(
/// // The `Local` `SystemParam` stores state inside the system itself, rather than in the world.
/// // `EventCursor<T>` is the internal state of `EventMutator<T>`, which tracks which events have been seen.
/// mut local_event_reader: Local<EventCursor<MyEvent>>,
/// // We can access the `Events` resource mutably, allowing us to both read and write its contents.
/// mut events: ResMut<Events<MyEvent>>,
/// ) {
/// // We must collect the events to resend, because we can't mutate events while we're iterating over the events.
/// let mut events_to_resend = Vec::new();
///
/// for event in local_event_reader.read(&mut events) {
/// events_to_resend.push(event.clone());
/// }
///
/// for event in events_to_resend {
/// events.send(MyEvent);
/// }
/// }
///
/// # bevy_ecs::system::assert_is_system(send_and_receive_events);
/// ```
#[derive(Debug)]
pub struct EventCursor<E: Event> {
pub(super) last_event_count: usize,
pub(super) _marker: PhantomData<E>,
}

impl<E: Event> Default for EventCursor<E> {
fn default() -> Self {
EventCursor {
last_event_count: 0,
_marker: Default::default(),
}
}
}

impl<E: Event> Clone for EventCursor<E> {
fn clone(&self) -> Self {
EventCursor {
last_event_count: self.last_event_count,
_marker: PhantomData,
}
}
}

#[allow(clippy::len_without_is_empty)] // Check fails since the is_empty implementation has a signature other than `(&self) -> bool`
impl<E: Event> EventCursor<E> {
/// See [`EventReader::read`]
pub fn read<'a>(&'a mut self, events: &'a Events<E>) -> EventIterator<'a, E> {
self.read_with_id(events).without_id()
}

/// See [`EventMutator::read`]
pub fn read_mut<'a>(&'a mut self, events: &'a mut Events<E>) -> EventMutIterator<'a, E> {
self.read_mut_with_id(events).without_id()
}

/// See [`EventReader::read_with_id`]
pub fn read_with_id<'a>(&'a mut self, events: &'a Events<E>) -> EventIteratorWithId<'a, E> {
EventIteratorWithId::new(self, events)
}

/// See [`EventMutator::read_with_id`]
pub fn read_mut_with_id<'a>(
&'a mut self,
events: &'a mut Events<E>,
) -> EventMutIteratorWithId<'a, E> {
EventMutIteratorWithId::new(self, events)
}

/// See [`EventReader::par_read`]
#[cfg(feature = "multi_threaded")]
pub fn par_read<'a>(&'a mut self, events: &'a Events<E>) -> EventParIter<'a, E> {
EventParIter::new(self, events)
}

/// See [`EventMutator::par_read`]
#[cfg(feature = "multi_threaded")]
pub fn par_read_mut<'a>(&'a mut self, events: &'a mut Events<E>) -> EventMutParIter<'a, E> {
EventMutParIter::new(self, events)
}

/// See [`EventReader::len`]
pub fn len(&self, events: &Events<E>) -> usize {
// The number of events in this reader is the difference between the most recent event
// and the last event seen by it. This will be at most the number of events contained
// with the events (any others have already been dropped)
// TODO: Warn when there are dropped events, or return e.g. a `Result<usize, (usize, usize)>`
events
.event_count
.saturating_sub(self.last_event_count)
.min(events.len())
}

/// Amount of events we missed.
pub fn missed_events(&self, events: &Events<E>) -> usize {
events
.oldest_event_count()
.saturating_sub(self.last_event_count)
}

/// See [`EventReader::is_empty()`]
pub fn is_empty(&self, events: &Events<E>) -> bool {
self.len(events) == 0
}

/// See [`EventReader::clear()`]
pub fn clear(&mut self, events: &Events<E>) {
self.last_event_count = events.event_count;
}
}
16 changes: 8 additions & 8 deletions crates/bevy_ecs/src/event/iterators.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate as bevy_ecs;
#[cfg(feature = "multi_threaded")]
use bevy_ecs::batching::BatchingStrategy;
use bevy_ecs::event::{Event, EventId, EventInstance, Events, ManualEventReader};
use bevy_ecs::event::{Event, EventCursor, EventId, EventInstance, Events};
use bevy_utils::detailed_trace;
use std::{iter::Chain, slice::Iter};

/// An iterator that yields any unread events from an [`EventReader`] or [`ManualEventReader`].
/// An iterator that yields any unread events from an [`EventReader`] or [`EventCursor`].
#[derive(Debug)]
pub struct EventIterator<'a, E: Event> {
iter: EventIteratorWithId<'a, E>,
Expand Down Expand Up @@ -43,17 +43,17 @@ impl<'a, E: Event> ExactSizeIterator for EventIterator<'a, E> {
}
}

/// An iterator that yields any unread events (and their IDs) from an [`EventReader`] or [`ManualEventReader`].
/// An iterator that yields any unread events (and their IDs) from an [`EventReader`] or [`EventCursor`].
#[derive(Debug)]
pub struct EventIteratorWithId<'a, E: Event> {
reader: &'a mut ManualEventReader<E>,
reader: &'a mut EventCursor<E>,
chain: Chain<Iter<'a, EventInstance<E>>, Iter<'a, EventInstance<E>>>,
unread: usize,
}

impl<'a, E: Event> EventIteratorWithId<'a, E> {
/// Creates a new iterator that yields any `events` that have not yet been seen by `reader`.
pub fn new(reader: &'a mut ManualEventReader<E>, events: &'a Events<E>) -> Self {
pub fn new(reader: &'a mut EventCursor<E>, events: &'a Events<E>) -> Self {
let a_index = reader
.last_event_count
.saturating_sub(events.events_a.start_event_count);
Expand Down Expand Up @@ -139,10 +139,10 @@ impl<'a, E: Event> ExactSizeIterator for EventIteratorWithId<'a, E> {
}

/// A parallel iterator over `Event`s.
#[derive(Debug)]
#[cfg(feature = "multi_threaded")]
#[derive(Debug)]
pub struct EventParIter<'a, E: Event> {
reader: &'a mut ManualEventReader<E>,
reader: &'a mut EventCursor<E>,
slices: [&'a [EventInstance<E>]; 2],
batching_strategy: BatchingStrategy,
unread: usize,
Expand All @@ -151,7 +151,7 @@ pub struct EventParIter<'a, E: Event> {
#[cfg(feature = "multi_threaded")]
impl<'a, E: Event> EventParIter<'a, E> {
/// Creates a new parallel iterator over `events` that have not yet been seen by `reader`.
pub fn new(reader: &'a mut ManualEventReader<E>, events: &'a Events<E>) -> Self {
pub fn new(reader: &'a mut EventCursor<E>, events: &'a Events<E>) -> Self {
let a_index = reader
.last_event_count
.saturating_sub(events.events_a.start_event_count);
Expand Down
Loading

0 comments on commit ec1aa48

Please sign in to comment.