From 7109a007b5ec654db79e84ddca1ee038082efe98 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 29 Jan 2024 11:41:27 -0500 Subject: [PATCH] Add an example demonstrating how to send and receive events in the same system (#11574) # Objective - Sending and receiving events of the same type in the same system is a reasonably common need, generally due to event filtering. - However, actually doing so is non-trivial, as the borrow checker simultaneous hates mutable and immutable access. ## Solution - Demonstrate two sensible patterns for doing so. - Update the `ManualEventReader` docs to be more clear and link to this example. --------- Co-authored-by: Alice Cecile Co-authored-by: Joona Aalto Co-authored-by: ickk --- Cargo.toml | 11 ++ crates/bevy_ecs/src/event.rs | 39 ++++++ examples/README.md | 1 + examples/ecs/send_and_receive_events.rs | 165 ++++++++++++++++++++++++ 4 files changed, 216 insertions(+) create mode 100644 examples/ecs/send_and_receive_events.rs diff --git a/Cargo.toml b/Cargo.toml index a66d198a13f142..eab962b73db88b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1402,6 +1402,17 @@ description = "Illustrates event creation, activation, and reception" category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "send_and_receive_events" +path = "examples/ecs/send_and_receive_events.rs" +doc-scrape-examples = true + +[package.metadata.example.send_and_receive_events] +name = "Send and receive events" +description = "Demonstrates how to send and receive events of the same type in a single system" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "fixed_timestep" path = "examples/ecs/fixed_timestep.rs" diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 8b25ab2a2cbc84..554b17195fbeb5 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -556,7 +556,46 @@ impl<'w, E: Event> EventWriter<'w, E> { } /// Stores the state for an [`EventReader`]. +/// /// Access to the [`Events`] resource is required to read any incoming events. +/// +/// In almost all cases, you should just use an [`EventReader`], +/// 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, ManualEventReader}; +/// +/// #[derive(Event, Clone, Debug)] +/// struct MyEvent; +/// +/// /// A system that both sends and receives events using a [`Local`] [`ManualEventReader`]. +/// fn send_and_receive_manual_event_reader( +/// // The `Local` `SystemParam` stores state inside the system itself, rather than in the world. +/// // `ManualEventReader` is the internal state of `EventReader`, which tracks which events have been seen. +/// mut local_event_reader: Local>, +/// // We can access the `Events` resource mutably, allowing us to both read and write its contents. +/// mut events: ResMut>, +/// ) { +/// // 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(&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_manual_event_reader); +/// ``` #[derive(Debug)] pub struct ManualEventReader { last_event_count: usize, diff --git a/examples/README.md b/examples/README.md index 350485648f9b47..175fc632a4a3c2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -239,6 +239,7 @@ Example | Description [Parallel Query](../examples/ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` [Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed earlier in the current frame [Run Conditions](../examples/ecs/run_conditions.rs) | Run systems only when one or multiple conditions are met +[Send and receive events](../examples/ecs/send_and_receive_events.rs) | Demonstrates how to send and receive events of the same type in a single system [Startup System](../examples/ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up) [State](../examples/ecs/state.rs) | Illustrates how to use States to control transitioning from a Menu state to an InGame state [System Closure](../examples/ecs/system_closure.rs) | Show how to use closures as systems, and how to configure `Local` variables by capturing external state diff --git a/examples/ecs/send_and_receive_events.rs b/examples/ecs/send_and_receive_events.rs new file mode 100644 index 00000000000000..b186937f029ee7 --- /dev/null +++ b/examples/ecs/send_and_receive_events.rs @@ -0,0 +1,165 @@ +//! From time to time, you may find that you want to both send and receive an event of the same type in a single system. +//! +//! Of course, this results in an error: the borrows of [`EventWriter`] and [`EventReader`] overlap, +//! if and only if the [`Event`] type is the same. +//! One system parameter borrows the [`Events`] resource mutably, and another system parameter borrows the [`Events`] resource immutably. +//! If Bevy allowed this, this would violate Rust's rules against aliased mutability. +//! In other words, this would be Undefined Behavior (UB)! +//! +//! There are two ways to solve this problem: +//! +//! 1. Use [`ParamSet`] to check out the [`EventWriter`] and [`EventReader`] one at a time. +//! 2. Use a [`Local`] [`ManualEventReader`] instead of an [`EventReader`], and use [`ResMut`] to access [`Events`]. +//! +//! In the first case, you're being careful to only check out only one of the [`EventWriter`] or [`EventReader`] at a time. +//! By "temporally" seperating them, you avoid the overlap. +//! +//! In the second case, you only ever have one access to the underlying [`Events`] resource at a time. +//! But in exchange, you have to manually keep track of which events you've already read. +//! +//! Let's look at an example of each. + +use bevy::core::FrameCount; +use bevy::ecs::event::ManualEventReader; +use bevy::prelude::*; + +fn main() { + let mut app = App::new(); + app.add_plugins(MinimalPlugins) + .add_event::() + .add_systems(Update, read_and_write_different_event_types) + .add_systems( + Update, + ( + send_events, + debug_events, + send_and_receive_param_set, + debug_events, + send_and_receive_manual_event_reader, + debug_events, + ) + .chain(), + ); + // We're just going to run a few frames, so we can see and understand the output. + app.update(); + // By running for longer than one frame, we can see that we're caching our cursor in the event queue properly. + app.update(); +} + +#[derive(Event)] +struct A; + +#[derive(Event)] +struct B; + +// This works fine, because the types are different, +// so the borrows of the `EventWriter` and `EventReader` don't overlap. +// Note that these borrowing rules are checked at system initialization time, +// not at compile time, as Bevy uses internal unsafe code to split the `World` into disjoint pieces. +fn read_and_write_different_event_types(mut a: EventWriter, mut b: EventReader) { + for _ in b.read() {} + a.send(A); +} + +/// A dummy event type. +#[derive(Debug, Clone, Event)] +struct DebugEvent { + resend_from_param_set: bool, + resend_from_local_event_reader: bool, + times_sent: u8, +} + +/// A system that sends all combinations of events. +fn send_events(mut events: EventWriter, frame_count: Res) { + info!("Sending events for frame {:?}", *frame_count); + + events.send(DebugEvent { + resend_from_param_set: false, + resend_from_local_event_reader: false, + times_sent: 1, + }); + events.send(DebugEvent { + resend_from_param_set: true, + resend_from_local_event_reader: false, + times_sent: 1, + }); + events.send(DebugEvent { + resend_from_param_set: false, + resend_from_local_event_reader: true, + times_sent: 1, + }); + events.send(DebugEvent { + resend_from_param_set: true, + resend_from_local_event_reader: true, + times_sent: 1, + }); +} + +/// A system that prints all events sent since the last time this system ran. +/// +/// Note that some events will be printed twice, because they were sent twice. +fn debug_events(mut events: EventReader) { + for event in events.read() { + println!("{:?}", event); + } +} + +/// A system that both sends and receives events using [`ParamSet`]. +fn send_and_receive_param_set( + mut param_set: ParamSet<(EventReader, EventWriter)>, + frame_count: Res, +) { + info!( + "Sending and receiving events for frame {} with a `ParamSet`", + frame_count.0 + ); + + // We must collect the events to resend, because we can't access the writer while we're iterating over the reader. + let mut events_to_resend = Vec::new(); + + // This is p0, as the first parameter in the `ParamSet` is the reader. + for event in param_set.p0().read() { + if event.resend_from_param_set { + events_to_resend.push(event.clone()); + } + } + + // This is p1, as the second parameter in the `ParamSet` is the writer. + for mut event in events_to_resend { + event.times_sent += 1; + param_set.p1().send(event); + } +} + +/// A system that both sends and receives events using a [`Local`] [`ManualEventReader`]. +fn send_and_receive_manual_event_reader( + // The `Local` `SystemParam` stores state inside the system itself, rather than in the world. + // `ManualEventReader` is the internal state of `EventReader`, which tracks which events have been seen. + mut local_event_reader: Local>, + // We can access the `Events` resource mutably, allowing us to both read and write its contents. + mut events: ResMut>, + frame_count: Res, +) { + info!( + "Sending and receiving events for frame {} with a `Local", + frame_count.0 + ); + + // 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(&events) { + if event.resend_from_local_event_reader { + // For simplicity, we're cloning the event. + // In this case, since we have mutable access to the `Events` resource, + // we could also just mutate the event in-place, + // or drain the event queue into our `events_to_resend` vector. + events_to_resend.push(event.clone()); + } + } + + for mut event in events_to_resend { + event.times_sent += 1; + events.send(event); + } +}