From 45281e62d7ec40b148840c5e3a6758d52d6efa13 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Wed, 28 Aug 2024 00:43:40 +0100 Subject: [PATCH] Commands::send_event (#14933) # Objective sending events tends to be low-frequency so ergonomics can be prioritized over efficiency. add `Commands::send_event` to send any type of event without needing a writer in hand. i don't know how we feel about these kind of ergonomic things, i add this to all my projects and find it useful. adding `mut this_particular_event_writer: EventWriter` every time i want to send something is unnecessarily cumbersome. it also simplifies the "send and receive in the same system" pattern significantly. basic example before: ```rs fn my_func( q: Query<(Entity, &State)>, mut damage_event_writer: EventWriter, mut heal_event_writer: EventWriter, ) { for (entity, state) in q.iter() { if let Some(damage) = state.get_damage() { damage_event_writer.send(DamageEvent { entity, damage }); } if let Some(heal) = state.get_heal() { heal_event_writer.send(HealEvent { entity, heal }); } } } ``` basic example after: ```rs import bevy::ecs::event::SendEventEx; fn my_func( mut commands: Commands, q: Query<(Entity, &State)>, ) { for (entity, state) in q.iter() { if let Some(damage) = state.get_damage() { commands.send_event(DamageEvent { entity, damage }); } if let Some(heal) = state.get_heal() { commands.send_event(HealEvent { entity, heal }); } } } ``` send/receive in the same system before: ```rs fn send_and_receive_param_set( mut param_set: ParamSet<(EventReader, EventWriter)>, ) { // 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); } } ``` after: ```rs use bevy::ecs::event::SendEventEx; fn send_via_commands_and_receive( mut reader: EventReader, mut commands: Commands, ) { for event in reader.read() { if event.resend_via_commands { commands.send_event(DebugEvent { times_sent: event.times_sent + 1, ..event.clone() }); } } } ``` --------- Co-authored-by: Jan Hohenheim --- crates/bevy_ecs/src/event/mod.rs | 2 ++ crates/bevy_ecs/src/event/send_event.rs | 15 +++++++++++++++ crates/bevy_ecs/src/system/commands/mod.rs | 17 ++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 crates/bevy_ecs/src/event/send_event.rs diff --git a/crates/bevy_ecs/src/event/mod.rs b/crates/bevy_ecs/src/event/mod.rs index 195d66c539..a570be5cab 100644 --- a/crates/bevy_ecs/src/event/mod.rs +++ b/crates/bevy_ecs/src/event/mod.rs @@ -7,6 +7,7 @@ mod mut_iterators; mod mutator; mod reader; mod registry; +mod send_event; mod update; mod writer; @@ -24,6 +25,7 @@ pub use mut_iterators::{EventMutIterator, EventMutIteratorWithId}; pub use mutator::EventMutator; pub use reader::EventReader; pub use registry::{EventRegistry, ShouldUpdateEvents}; +pub use send_event::SendEvent; pub use update::{ event_update_condition, event_update_system, signal_event_update_system, EventUpdates, }; diff --git a/crates/bevy_ecs/src/event/send_event.rs b/crates/bevy_ecs/src/event/send_event.rs new file mode 100644 index 0000000000..2640357bf0 --- /dev/null +++ b/crates/bevy_ecs/src/event/send_event.rs @@ -0,0 +1,15 @@ +use super::{Event, Events}; +use crate::world::{Command, World}; + +/// A command to send an arbitrary [`Event`], used by [`Commands::send_event`](crate::system::Commands::send_event). +pub struct SendEvent { + /// The event to send. + pub event: E, +} + +impl Command for SendEvent { + fn apply(self, world: &mut World) { + let mut events = world.resource_mut::>(); + events.send(self.event); + } +} diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index c0846a8c88..55468cbcad 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -8,7 +8,7 @@ use crate::{ bundle::{Bundle, InsertMode}, component::{ComponentId, ComponentInfo}, entity::{Entities, Entity}, - event::Event, + event::{Event, SendEvent}, observer::{Observer, TriggerEvent, TriggerTargets}, system::{RunSystemWithInput, SystemId}, world::{ @@ -788,6 +788,21 @@ impl<'w, 's> Commands<'w, 's> { ) -> EntityCommands { self.spawn(Observer::new(observer)) } + + /// Sends an arbitrary [`Event`]. + /// + /// This is a convenience method for sending events without requiring an [`EventWriter`]. + /// ## Performance + /// Since this is a command, exclusive world access is used, which means that it will not profit from + /// system-level parallelism on supported platforms. + /// If these events are performance-critical or very frequently + /// sent, consider using a typed [`EventWriter`] instead. + /// + /// [`EventWriter`]: crate::event::EventWriter + pub fn send_event(&mut self, event: E) -> &mut Self { + self.add(SendEvent { event }); + self + } } /// A [`Command`] which gets executed for a given [`Entity`].