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 (bevyengine#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.