mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 06:30:19 +00:00
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 <alice.i.cecil@gmail.com> Co-authored-by: Joona Aalto <jondolf.dev@gmail.com> Co-authored-by: ickk <git@ickk.io>
This commit is contained in:
parent
45967b03b5
commit
149a313850
4 changed files with 216 additions and 0 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -1402,6 +1402,17 @@ description = "Illustrates event creation, activation, and reception"
|
||||||
category = "ECS (Entity Component System)"
|
category = "ECS (Entity Component System)"
|
||||||
wasm = false
|
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]]
|
[[example]]
|
||||||
name = "fixed_timestep"
|
name = "fixed_timestep"
|
||||||
path = "examples/ecs/fixed_timestep.rs"
|
path = "examples/ecs/fixed_timestep.rs"
|
||||||
|
|
|
@ -556,7 +556,46 @@ impl<'w, E: Event> EventWriter<'w, E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores the state for an [`EventReader`].
|
/// Stores the state for an [`EventReader`].
|
||||||
|
///
|
||||||
/// Access to the [`Events<E>`] resource is required to read any incoming events.
|
/// Access to the [`Events<E>`] 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<T>` is the internal state of `EventReader<T>`, which tracks which events have been seen.
|
||||||
|
/// mut local_event_reader: Local<ManualEventReader<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(&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)]
|
#[derive(Debug)]
|
||||||
pub struct ManualEventReader<E: Event> {
|
pub struct ManualEventReader<E: Event> {
|
||||||
last_event_count: usize,
|
last_event_count: usize,
|
||||||
|
|
|
@ -239,6 +239,7 @@ Example | Description
|
||||||
[Parallel Query](../examples/ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator`
|
[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
|
[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
|
[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)
|
[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
|
[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
|
[System Closure](../examples/ecs/system_closure.rs) | Show how to use closures as systems, and how to configure `Local` variables by capturing external state
|
||||||
|
|
165
examples/ecs/send_and_receive_events.rs
Normal file
165
examples/ecs/send_and_receive_events.rs
Normal file
|
@ -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::<DebugEvent>()
|
||||||
|
.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<A>, mut b: EventReader<B>) {
|
||||||
|
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<DebugEvent>, frame_count: Res<FrameCount>) {
|
||||||
|
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<DebugEvent>) {
|
||||||
|
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<DebugEvent>, EventWriter<DebugEvent>)>,
|
||||||
|
frame_count: Res<FrameCount>,
|
||||||
|
) {
|
||||||
|
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<T>` is the internal state of `EventReader<T>`, which tracks which events have been seen.
|
||||||
|
mut local_event_reader: Local<ManualEventReader<DebugEvent>>,
|
||||||
|
// We can access the `Events` resource mutably, allowing us to both read and write its contents.
|
||||||
|
mut events: ResMut<Events<DebugEvent>>,
|
||||||
|
frame_count: Res<FrameCount>,
|
||||||
|
) {
|
||||||
|
info!(
|
||||||
|
"Sending and receiving events for frame {} with a `Local<ManualEventReader>",
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue