Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example demonstrating how to send and receive events in the same system #11574

Merged
merged 22 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1399,6 +1399,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"
Expand Down
7 changes: 7 additions & 0 deletions crates/bevy_ecs/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,14 @@ impl<'w, E: Event> EventWriter<'w, E> {
}

/// Stores the state for an [`EventReader`].
///
/// 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, to see how to manually track events (and why you might want to do so),
/// take a look at the [send_and_receive_events example](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/send_and_receive_events.rs).
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug)]
pub struct ManualEventReader<E: Event> {
last_event_count: usize,
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
173 changes: 173 additions & 0 deletions examples/ecs/send_and_receive_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//! 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_type)
.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_type(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 {
re_emit_from_param_set: bool,
re_emit_from_local_event_reader: bool,
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
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 {
re_emit_from_param_set: false,
re_emit_from_local_event_reader: false,
times_sent: 1,
});
events.send(DebugEvent {
re_emit_from_param_set: true,
re_emit_from_local_event_reader: false,
times_sent: 1,
});
events.send(DebugEvent {
re_emit_from_param_set: false,
re_emit_from_local_event_reader: true,
times_sent: 1,
});
events.send(DebugEvent {
re_emit_from_param_set: true,
re_emit_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 re-emit, because we can't access the writer while we're iterating over the reader.
let mut events_to_re_emit = Vec::new();

// This is p0, as the first parameter in the `ParamSet` is the reader.
for event in param_set.p0().read() {
if event.re_emit_from_param_set {
events_to_re_emit.push(event.clone());
}
}

// This is p1, as the second parameter in the `ParamSet` is the writer.
for event in events_to_re_emit {
param_set.p1().send(DebugEvent {
times_sent: event.times_sent + 1,
// This is struct update syntax! Here, we're copying all of the fields from `event`,
// except for the `times_sent` field, which we're incrementing.
..event
});
}
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
}

/// 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 re-emit, because we can't mutate events while we're iterating over the events.
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
let mut events_to_re_emit = Vec::new();

for event in local_event_reader.read(&events) {
if event.re_emit_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_re_emit` vector.
events_to_re_emit.push(event.clone());
}
}

for event in events_to_re_emit {
events.send(DebugEvent {
times_sent: event.times_sent + 1,
// This is struct update syntax! Here, we're copying all of the fields from `event`,
// except for the `times_sent` field, which we're incrementing.
..event
});
}
}
Loading