diff --git a/benches/benches/bevy_ecs/observers/simple.rs b/benches/benches/bevy_ecs/observers/simple.rs index 29ade4d2d1032..09e8b2e473023 100644 --- a/benches/benches/bevy_ecs/observers/simple.rs +++ b/benches/benches/bevy_ecs/observers/simple.rs @@ -1,7 +1,7 @@ use core::hint::black_box; use bevy_ecs::{ - event::EntityEvent, + event::{BroadcastEvent, EntityEvent}, observer::{On, TriggerTargets}, world::World, }; @@ -16,6 +16,8 @@ fn deterministic_rand() -> ChaCha8Rng { #[derive(Clone, EntityEvent)] struct EventBase; +impl BroadcastEvent for EventBase {} + pub fn observe_simple(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("observe"); group.warm_up_time(core::time::Duration::from_millis(500)); diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 5756286cf4f03..1b49497e265a8 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1320,7 +1320,7 @@ impl App { /// # /// # let mut app = App::new(); /// # - /// # #[derive(Event)] + /// # #[derive(BroadcastEvent)] /// # struct Party { /// # friends_allowed: bool, /// # }; diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index 302ab44bfe23c..16a6cbcff8eaf 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -306,7 +306,7 @@ Observers are systems that listen for a "trigger" of a specific `Event`: ```rust use bevy_ecs::prelude::*; -#[derive(Event)] +#[derive(BroadcastEvent)] struct Speak { message: String } diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index ce084474a2700..ae1427928c747 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -17,7 +17,7 @@ pub const EVENT: &str = "entity_event"; pub const AUTO_PROPAGATE: &str = "auto_propagate"; pub const TRAVERSAL: &str = "traversal"; -pub fn derive_event(input: TokenStream) -> TokenStream { +pub fn derive_broadcast_event(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); let bevy_ecs_path: Path = crate::bevy_ecs_path(); @@ -31,6 +31,7 @@ pub fn derive_event(input: TokenStream) -> TokenStream { TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {} + impl #impl_generics #bevy_ecs_path::event::BroadcastEvent for #struct_name #type_generics #where_clause {} }) } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 234a8cf8595dd..d05b62c04914c 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -547,10 +547,10 @@ pub(crate) fn bevy_ecs_path() -> syn::Path { BevyManifest::shared().get_path("bevy_ecs") } -/// Implement the `Event` trait. -#[proc_macro_derive(Event)] -pub fn derive_event(input: TokenStream) -> TokenStream { - component::derive_event(input) +/// Implement the `BroadcastEvent` trait. +#[proc_macro_derive(BroadcastEvent)] +pub fn derive_broadcast_event(input: TokenStream) -> TokenStream { + component::derive_broadcast_event(input) } /// Cheat sheet for derive syntax, diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index f682554ce9a51..4a0e57a06e0af 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -23,7 +23,7 @@ use crate::{ bundle::BundleId, component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, entity::{Entity, EntityLocation}, - event::Event, + event::BroadcastEvent, observer::Observers, storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow}, }; @@ -35,7 +35,7 @@ use core::{ }; use nonmax::NonMaxU32; -#[derive(Event)] +#[derive(BroadcastEvent)] #[expect(dead_code, reason = "Prepare for the upcoming Query as Entities")] pub(crate) struct ArchetypeCreated(pub ArchetypeId); diff --git a/crates/bevy_ecs/src/component/tick.rs b/crates/bevy_ecs/src/component/tick.rs index 09d82a13d4eb2..683915acbd41d 100644 --- a/crates/bevy_ecs/src/component/tick.rs +++ b/crates/bevy_ecs/src/component/tick.rs @@ -1,4 +1,4 @@ -use bevy_ecs_macros::Event; +use bevy_ecs_macros::BroadcastEvent; use bevy_ptr::UnsafeCellDeref; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; @@ -86,7 +86,7 @@ impl Tick { } } -/// An observer [`Event`] that can be used to maintain [`Tick`]s in custom data structures, enabling to make +/// A [`BroadcastEvent`] that can be used to maintain [`Tick`]s in custom data structures, enabling to make /// use of bevy's periodic checks that clamps ticks to a certain range, preventing overflows and thus /// keeping methods like [`Tick::is_newer_than`] reliably return `false` for ticks that got too old. /// @@ -111,7 +111,7 @@ impl Tick { /// schedule.0.check_change_ticks(*check); /// }); /// ``` -#[derive(Debug, Clone, Copy, Event)] +#[derive(Debug, Clone, Copy, BroadcastEvent)] pub struct CheckChangeTicks(pub(crate) Tick); impl CheckChangeTicks { diff --git a/crates/bevy_ecs/src/event/base.rs b/crates/bevy_ecs/src/event/base.rs index a5924d60f4a07..21874458ed1a6 100644 --- a/crates/bevy_ecs/src/event/base.rs +++ b/crates/bevy_ecs/src/event/base.rs @@ -11,31 +11,62 @@ use core::{ marker::PhantomData, }; -/// Something that "happens" and can be processed by app logic. -/// -/// Events can be triggered on a [`World`] using a method like [`trigger`](World::trigger), -/// causing any global [`Observer`] watching that event to run. This allows for push-based -/// event handling where observers are immediately notified of events as they happen. -/// -/// Additional event handling behavior can be enabled by implementing the [`EntityEvent`] -/// and [`BufferedEvent`] traits: -/// -/// - [`EntityEvent`]s support targeting specific entities, triggering any observers watching those targets. -/// They are useful for entity-specific event handlers and can even be propagated from one entity to another. -/// - [`BufferedEvent`]s support a pull-based event handling system where events are written using an [`EventWriter`] -/// and read later using an [`EventReader`]. This is an alternative to observers that allows efficient batch processing -/// of events at fixed points in a schedule. +/// Supertrait for the observer based [`BroadcastEvent`] and [`EntityEvent`]. /// /// Events must be thread-safe. +#[diagnostic::on_unimplemented( + message = "`{Self}` is not an `Event`", + label = "invalid `Event`", + note = "consider annotating `{Self}` with `#[derive(BroadcastEvent)]` or `#[derive(EntityEvent)]`" +)] +pub trait Event: Send + Sync + 'static { + /// Generates the [`EventKey`] for this event type. + /// + /// If this type has already been registered, + /// this will return the existing [`EventKey`]. + /// + /// This is used by various dynamically typed observer APIs, + /// such as [`World::trigger_targets_dynamic`]. + /// + /// # Warning + /// + /// This method should not be overridden by implementers, + /// and should always correspond to the implementation of [`event_key`](Event::event_key). + fn register_event_key(world: &mut World) -> EventKey { + EventKey(world.register_component::>()) + } + + /// Fetches the [`EventKey`] for this event type, + /// if it has already been generated. + /// + /// This is used by various dynamically typed observer APIs, + /// such as [`World::trigger_targets_dynamic`]. + /// + /// # Warning + /// + /// This method should not be overridden by implementers, + /// and should always correspond to the implementation of + /// [`register_event_key`](Event::register_event_key). + fn event_key(world: &World) -> Option { + world + .component_id::>() + .map(EventKey) + } +} + +/// An [`Event`] without an entity target. +/// +/// [`BroadcastEvent`]s can be triggered on a [`World`] with the method [`trigger`](World::trigger), +/// causing any global [`Observer`]s for that event to run. /// /// # Usage /// -/// The [`Event`] trait can be derived: +/// The [`BroadcastEvent`] trait can be derived: /// /// ``` /// # use bevy_ecs::prelude::*; /// # -/// #[derive(Event)] +/// #[derive(BroadcastEvent)] /// struct Speak { /// message: String, /// } @@ -46,7 +77,7 @@ use core::{ /// ``` /// # use bevy_ecs::prelude::*; /// # -/// # #[derive(Event)] +/// # #[derive(BroadcastEvent)] /// # struct Speak { /// # message: String, /// # } @@ -63,70 +94,20 @@ use core::{ /// ``` /// # use bevy_ecs::prelude::*; /// # -/// # #[derive(Event)] +/// # #[derive(BroadcastEvent)] /// # struct Speak { /// # message: String, /// # } /// # /// # let mut world = World::new(); /// # -/// # world.add_observer(|trigger: On| { -/// # println!("{}", trigger.message); -/// # }); -/// # -/// # world.flush(); -/// # /// world.trigger(Speak { /// message: "Hello!".to_string(), /// }); /// ``` /// -/// For events that additionally need entity targeting or buffering, consider also deriving -/// [`EntityEvent`] or [`BufferedEvent`], respectively. -/// -/// [`World`]: crate::world::World /// [`Observer`]: crate::observer::Observer -/// [`EventReader`]: super::EventReader -/// [`EventWriter`]: super::EventWriter -#[diagnostic::on_unimplemented( - message = "`{Self}` is not an `Event`", - label = "invalid `Event`", - note = "consider annotating `{Self}` with `#[derive(Event)]`" -)] -pub trait Event: Send + Sync + 'static { - /// Generates the [`EventKey`] for this event type. - /// - /// If this type has already been registered, - /// this will return the existing [`EventKey`]. - /// - /// This is used by various dynamically typed observer APIs, - /// such as [`World::trigger_targets_dynamic`]. - /// - /// # Warning - /// - /// This method should not be overridden by implementers, - /// and should always correspond to the implementation of [`event_key`](Event::event_key). - fn register_event_key(world: &mut World) -> EventKey { - EventKey(world.register_component::>()) - } - - /// Fetches the [`EventKey`] for this event type, - /// if it has already been generated. - /// - /// This is used by various dynamically typed observer APIs, - /// such as [`World::trigger_targets_dynamic`]. - /// - /// # Warning - /// - /// This method should not be overridden by implementers, - /// and should always correspond to the implementation of - /// [`register_event_key`](Event::register_event_key). - fn event_key(world: &World) -> Option { - world - .component_id::>() - .map(EventKey) - } -} +pub trait BroadcastEvent: Event {} /// An [`Event`] that can be targeted at specific entities. /// @@ -134,7 +115,7 @@ pub trait Event: Send + Sync + 'static { /// like [`trigger_targets`](World::trigger_targets), causing any [`Observer`] watching the event /// for those entities to run. /// -/// Unlike basic [`Event`]s, entity events can optionally be propagated from one entity target to another +/// Unlike [`BroadcastEvent`]s, entity events can optionally be propagated from one entity target to another /// based on the [`EntityEvent::Traversal`] type associated with the event. This enables use cases /// such as bubbling events to parent entities for UI purposes. /// @@ -142,7 +123,7 @@ pub trait Event: Send + Sync + 'static { /// /// # Usage /// -/// The [`EntityEvent`] trait can be derived. The `event` attribute can be used to further configure +/// The [`EntityEvent`] trait can be derived. The `entity_event` attribute can be used to further configure /// the propagation behavior: adding `auto_propagate` sets [`EntityEvent::AUTO_PROPAGATE`] to `true`, /// while adding `traversal = X` sets [`EntityEvent::Traversal`] to be of type `X`. /// @@ -233,12 +214,8 @@ pub trait Event: Send + Sync + 'static { /// world.trigger_targets(Damage { amount: 10.0 }, armor_piece); /// ``` /// -/// [`World`]: crate::world::World /// [`TriggerTargets`]: crate::observer::TriggerTargets /// [`Observer`]: crate::observer::Observer -/// [`Events`]: super::Events -/// [`EventReader`]: super::EventReader -/// [`EventWriter`]: super::EventWriter #[diagnostic::on_unimplemented( message = "`{Self}` is not an `EntityEvent`", label = "invalid `EntityEvent`", diff --git a/crates/bevy_ecs/src/event/mod.rs b/crates/bevy_ecs/src/event/mod.rs index 020b258557fd4..049c9f5450fdf 100644 --- a/crates/bevy_ecs/src/event/mod.rs +++ b/crates/bevy_ecs/src/event/mod.rs @@ -1,4 +1,20 @@ -//! Event handling types. +//! Events are things that "happen" and can be processed by app logic. +//! +//! - [`Event`]: A supertrait for push-based events that trigger global observers added via [`add_observer`]. +//! - [`BroadcastEvent`]: An event without an entity target. Can be used via [`trigger`]. +//! - [`EntityEvent`]: An event targeting specific entities, triggering any observers watching those targets. Can be used via [`trigger_targets`]. +//! They can trigger entity-specific observers added via [`observe`] and can be propagated from one entity to another. +//! - [`BufferedEvent`]: Support a pull-based event handling system where events are written using an [`EventWriter`] +//! and read later using an [`EventReader`]. This is an alternative to observers that allows efficient batch processing +//! of events at fixed points in a schedule. +//! +//! [`World`]: crate::world::World +//! [`add_observer`]: crate::world::World::add_observer +//! [`observe`]: crate::world::EntityWorldMut::observe +//! [`trigger`]: crate::world::World::trigger +//! [`trigger_targets`]: crate::world::World::trigger_targets +//! [`Observer`]: crate::observer::Observer + mod base; mod collections; mod event_cursor; @@ -11,8 +27,8 @@ mod update; mod writer; pub(crate) use base::EventInstance; -pub use base::{BufferedEvent, EntityEvent, Event, EventId, EventKey}; -pub use bevy_ecs_macros::{BufferedEvent, EntityEvent, Event}; +pub use base::{BroadcastEvent, BufferedEvent, EntityEvent, Event, EventId, EventKey}; +pub use bevy_ecs_macros::{BroadcastEvent, BufferedEvent, EntityEvent}; #[expect(deprecated, reason = "`SendBatchIds` was renamed to `WriteBatchIds`.")] pub use collections::{Events, SendBatchIds, WriteBatchIds}; pub use event_cursor::EventCursor; diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 8580df7a3b484..20b741eb9dffd 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -79,8 +79,8 @@ pub mod prelude { entity::{ContainsEntity, Entity, EntityMapper}, error::{BevyError, Result}, event::{ - BufferedEvent, EntityEvent, Event, EventKey, EventMutator, EventReader, EventWriter, - Events, + BroadcastEvent, BufferedEvent, EntityEvent, Event, EventKey, EventMutator, EventReader, + EventWriter, Events, }, hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children}, lifecycle::{ diff --git a/crates/bevy_ecs/src/observer/distributed_storage.rs b/crates/bevy_ecs/src/observer/distributed_storage.rs index 042661a690ddf..0e26d7966cc49 100644 --- a/crates/bevy_ecs/src/observer/distributed_storage.rs +++ b/crates/bevy_ecs/src/observer/distributed_storage.rs @@ -44,7 +44,7 @@ use crate::prelude::ReflectComponent; /// ``` /// # use bevy_ecs::prelude::*; /// # let mut world = World::default(); -/// #[derive(Event)] +/// #[derive(BroadcastEvent)] /// struct Speak { /// message: String, /// } @@ -67,7 +67,7 @@ use crate::prelude::ReflectComponent; /// ``` /// # use bevy_ecs::prelude::*; /// # let mut world = World::default(); -/// # #[derive(Event)] +/// # #[derive(BroadcastEvent)] /// # struct Speak; /// // These are functionally the same: /// world.add_observer(|trigger: On| {}); @@ -79,7 +79,7 @@ use crate::prelude::ReflectComponent; /// ``` /// # use bevy_ecs::prelude::*; /// # let mut world = World::default(); -/// # #[derive(Event)] +/// # #[derive(BroadcastEvent)] /// # struct PrintNames; /// # #[derive(Component, Debug)] /// # struct Name; @@ -97,7 +97,7 @@ use crate::prelude::ReflectComponent; /// ``` /// # use bevy_ecs::prelude::*; /// # let mut world = World::default(); -/// # #[derive(Event)] +/// # #[derive(BroadcastEvent)] /// # struct SpawnThing; /// # #[derive(Component, Debug)] /// # struct Thing; @@ -111,9 +111,9 @@ use crate::prelude::ReflectComponent; /// ``` /// # use bevy_ecs::prelude::*; /// # let mut world = World::default(); -/// # #[derive(Event)] +/// # #[derive(BroadcastEvent)] /// # struct A; -/// # #[derive(Event)] +/// # #[derive(BroadcastEvent)] /// # struct B; /// world.add_observer(|trigger: On, mut commands: Commands| { /// commands.trigger(B); @@ -211,7 +211,7 @@ pub struct Observer { } impl Observer { - /// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered + /// Creates a new [`Observer`], which defaults to a global observer. This means it will run whenever the event `E` is triggered /// for _any_ entity (or no entity). /// /// # Panics diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 574f6ca257dea..0b1d1a4e62b5e 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -145,13 +145,14 @@ pub use trigger_targets::*; use crate::{ change_detection::MaybeLocation, component::ComponentId, + event::BroadcastEvent, prelude::*, system::IntoObserverSystem, world::{DeferredWorld, *}, }; impl World { - /// Spawns a "global" [`Observer`] which will watch for the given event. + /// Spawns a global [`Observer`] which will watch for the given event. /// Returns its [`Entity`] as a [`EntityWorldMut`]. /// /// `system` can be any system whose first parameter is [`On`]. @@ -186,17 +187,21 @@ impl World { self.spawn(Observer::new(system)) } - /// Triggers the given [`Event`], which will run any [`Observer`]s watching for it. + /// Triggers the given [`BroadcastEvent`], which will run any [`Observer`]s watching for it. /// /// While event types commonly implement [`Copy`], /// those that don't will be consumed and will no longer be accessible. /// If you need to use the event after triggering it, use [`World::trigger_ref`] instead. #[track_caller] - pub fn trigger(&mut self, event: E) { + pub fn trigger(&mut self, event: E) { self.trigger_with_caller(event, MaybeLocation::caller()); } - pub(crate) fn trigger_with_caller(&mut self, mut event: E, caller: MaybeLocation) { + pub(crate) fn trigger_with_caller( + &mut self, + mut event: E, + caller: MaybeLocation, + ) { let event_key = E::register_event_key(self); // SAFETY: We just registered `event_key` with the type of `event` unsafe { @@ -204,18 +209,18 @@ impl World { } } - /// Triggers the given [`Event`] as a mutable reference, which will run any [`Observer`]s watching for it. + /// Triggers the given [`BroadcastEvent`] as a mutable reference, which will run any [`Observer`]s watching for it. /// /// Compared to [`World::trigger`], this method is most useful when it's necessary to check /// or use the event after it has been modified by observers. #[track_caller] - pub fn trigger_ref(&mut self, event: &mut E) { + pub fn trigger_ref(&mut self, event: &mut E) { let event_key = E::register_event_key(self); // SAFETY: We just registered `event_key` with the type of `event` unsafe { self.trigger_dynamic_ref_with_caller(event_key, event, MaybeLocation::caller()) }; } - unsafe fn trigger_dynamic_ref_with_caller( + unsafe fn trigger_dynamic_ref_with_caller( &mut self, event_key: EventKey, event_data: &mut E, @@ -497,6 +502,7 @@ impl World { mod tests { use alloc::{vec, vec::Vec}; + use bevy_ecs_macros::BroadcastEvent; use bevy_platform::collections::HashMap; use bevy_ptr::OwningPtr; @@ -524,8 +530,21 @@ mod tests { #[derive(EntityEvent)] struct EventA; + impl BroadcastEvent for EventA {} + + #[derive(EntityEvent)] + struct EntityEventA; + + #[derive(BroadcastEvent)] + struct BroadcastEventA; + #[derive(EntityEvent)] - struct EventWithData { + struct EntityEventWithData { + counter: usize, + } + + #[derive(BroadcastEvent)] + struct BroadcastEventWithData { counter: usize, } @@ -681,14 +700,20 @@ mod tests { fn observer_trigger_ref() { let mut world = World::new(); - world.add_observer(|mut trigger: On| trigger.event_mut().counter += 1); - world.add_observer(|mut trigger: On| trigger.event_mut().counter += 2); - world.add_observer(|mut trigger: On| trigger.event_mut().counter += 4); + world.add_observer(|mut trigger: On| { + trigger.event_mut().counter += 1; + }); + world.add_observer(|mut trigger: On| { + trigger.event_mut().counter += 2; + }); + world.add_observer(|mut trigger: On| { + trigger.event_mut().counter += 4; + }); // This flush is required for the last observer to be called when triggering the event, // due to `World::add_observer` returning `WorldEntityMut`. world.flush(); - let mut event = EventWithData { counter: 0 }; + let mut event = BroadcastEventWithData { counter: 0 }; world.trigger_ref(&mut event); assert_eq!(7, event.counter); } @@ -697,20 +722,20 @@ mod tests { fn observer_trigger_targets_ref() { let mut world = World::new(); - world.add_observer(|mut trigger: On| { + world.add_observer(|mut trigger: On| { trigger.event_mut().counter += 1; }); - world.add_observer(|mut trigger: On| { + world.add_observer(|mut trigger: On| { trigger.event_mut().counter += 2; }); - world.add_observer(|mut trigger: On| { + world.add_observer(|mut trigger: On| { trigger.event_mut().counter += 4; }); // This flush is required for the last observer to be called when triggering the event, // due to `World::add_observer` returning `WorldEntityMut`. world.flush(); - let mut event = EventWithData { counter: 0 }; + let mut event = EntityEventWithData { counter: 0 }; let component_a = world.register_component::(); world.trigger_targets_ref(&mut event, component_a); assert_eq!(5, event.counter); @@ -846,16 +871,16 @@ mod tests { let mut world = World::new(); world.init_resource::(); - let system: fn(On) = |_| { + let system: fn(On) = |_| { panic!("Trigger routed to non-targeted entity."); }; world.spawn_empty().observe(system); let entity = world .spawn_empty() - .observe(|_: On, mut res: ResMut| res.observed("a_1")) + .observe(|_: On, mut res: ResMut| res.observed("a_1")) .id(); - world.add_observer(move |obs: On, mut res: ResMut| { + world.add_observer(move |obs: On, mut res: ResMut| { assert_eq!(obs.target(), entity); res.observed("a_2"); }); @@ -863,7 +888,7 @@ mod tests { // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut // and therefore does not automatically flush. world.flush(); - world.trigger_targets(EventA, entity); + world.trigger_targets(EntityEventA, entity); world.flush(); assert_eq!(vec!["a_2", "a_1"], world.resource::().0); } @@ -881,26 +906,27 @@ mod tests { // targets (entity_1, A) let entity_1 = world .spawn_empty() - .observe(|_: On, mut res: ResMut| res.0 += 1) + .observe(|_: On, mut res: ResMut| res.0 += 1) .id(); // targets (entity_2, B) let entity_2 = world .spawn_empty() - .observe(|_: On, mut res: ResMut| res.0 += 10) + .observe(|_: On, mut res: ResMut| res.0 += 10) .id(); // targets any entity or component - world.add_observer(|_: On, mut res: ResMut| res.0 += 100); + world.add_observer(|_: On, mut res: ResMut| res.0 += 100); // targets any entity, and components A or B - world.add_observer(|_: On, mut res: ResMut| res.0 += 1000); + world.add_observer(|_: On, mut res: ResMut| res.0 += 1000); // test all tuples - world.add_observer(|_: On, mut res: ResMut| res.0 += 10000); + world + .add_observer(|_: On, mut res: ResMut| res.0 += 10000); world.add_observer( - |_: On, mut res: ResMut| { + |_: On, mut res: ResMut| { res.0 += 100000; }, ); world.add_observer( - |_: On, + |_: On, mut res: ResMut| res.0 += 1000000, ); @@ -908,21 +934,21 @@ mod tests { world.flush(); // trigger for an entity and a component - world.trigger_targets(EventA, (entity_1, component_a)); + world.trigger_targets(EntityEventA, (entity_1, component_a)); world.flush(); // only observer that doesn't trigger is the one only watching entity_2 assert_eq!(1111101, world.resource::().0); world.resource_mut::().0 = 0; // trigger for both entities, but no components: trigger once per entity target - world.trigger_targets(EventA, (entity_1, entity_2)); + world.trigger_targets(EntityEventA, (entity_1, entity_2)); world.flush(); // only the observer that doesn't require components triggers - once per entity assert_eq!(200, world.resource::().0); world.resource_mut::().0 = 0; // trigger for both components, but no entities: trigger once - world.trigger_targets(EventA, (component_a, component_b)); + world.trigger_targets(EntityEventA, (component_a, component_b)); world.flush(); // all component observers trigger, entities are not observed assert_eq!(1111100, world.resource::().0); @@ -930,14 +956,17 @@ mod tests { // trigger for both entities and both components: trigger once per entity target // we only get 2222211 because a given observer can trigger only once per entity target - world.trigger_targets(EventA, ((component_a, component_b), (entity_1, entity_2))); + world.trigger_targets( + EntityEventA, + ((component_a, component_b), (entity_1, entity_2)), + ); world.flush(); assert_eq!(2222211, world.resource::().0); world.resource_mut::().0 = 0; // trigger to test complex tuples: (A, B, (A, B)) world.trigger_targets( - EventA, + EntityEventA, (component_a, component_b, (component_a, component_b)), ); world.flush(); @@ -947,7 +976,7 @@ mod tests { // trigger to test complex tuples: (A, B, (A, B), ((A, B), (A, B))) world.trigger_targets( - EventA, + EntityEventA, ( component_a, component_b, @@ -962,7 +991,7 @@ mod tests { // trigger to test the most complex tuple: (A, B, (A, B), (B, A), (A, B, ((A, B), (B, A)))) world.trigger_targets( - EventA, + EntityEventA, ( component_a, component_b, @@ -999,7 +1028,7 @@ mod tests { }); let entity = entity.flush(); - world.trigger_targets(EventA, entity); + world.trigger_targets(EntityEventA, entity); world.flush(); assert_eq!(vec!["event_a"], world.resource::().0); } @@ -1021,7 +1050,7 @@ mod tests { world.commands().queue(move |world: &mut World| { // SAFETY: we registered `event_a` above and it matches the type of EventA - unsafe { world.trigger_targets_dynamic(event_a, EventA, ()) }; + unsafe { world.trigger_targets_dynamic(event_a, EntityEventA, ()) }; }); world.flush(); assert_eq!(vec!["event_a"], world.resource::().0); @@ -1350,10 +1379,12 @@ mod tests { let mut world = World::new(); // This fails because `ResA` is not present in the world - world.add_observer(|_: On, _: Res, mut commands: Commands| { - commands.insert_resource(ResB); - }); - world.trigger(EventA); + world.add_observer( + |_: On, _: Res, mut commands: Commands| { + commands.insert_resource(ResB); + }, + ); + world.trigger(BroadcastEventA); } #[test] @@ -1363,14 +1394,14 @@ mod tests { let mut world = World::new(); world.add_observer( - |_: On, mut params: ParamSet<(Query, Commands)>| { + |_: On, mut params: ParamSet<(Query, Commands)>| { params.p1().insert_resource(ResA); }, ); // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut // and therefore does not automatically flush. world.flush(); - world.trigger(EventA); + world.trigger(BroadcastEventA); world.flush(); assert!(world.get_resource::().is_some()); @@ -1379,7 +1410,7 @@ mod tests { #[test] #[track_caller] fn observer_caller_location_event() { - #[derive(Event)] + #[derive(BroadcastEvent)] struct EventA; let caller = MaybeLocation::caller(); @@ -1419,7 +1450,7 @@ mod tests { let b_id = world.register_component::(); world.add_observer( - |trigger: On, mut counter: ResMut| { + |trigger: On, mut counter: ResMut| { for &component in trigger.components() { *counter.0.entry(component).or_default() += 1; } @@ -1427,11 +1458,11 @@ mod tests { ); world.flush(); - world.trigger_targets(EventA, [a_id, b_id]); - world.trigger_targets(EventA, a_id); - world.trigger_targets(EventA, b_id); - world.trigger_targets(EventA, [a_id, b_id]); - world.trigger_targets(EventA, a_id); + world.trigger_targets(EntityEventA, [a_id, b_id]); + world.trigger_targets(EntityEventA, a_id); + world.trigger_targets(EntityEventA, b_id); + world.trigger_targets(EntityEventA, [a_id, b_id]); + world.trigger_targets(EntityEventA, a_id); world.flush(); let counter = world.resource::(); diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index f25e742eed300..249f4b43a9723 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -93,11 +93,11 @@ mod tests { use super::*; use crate::{ error::{ignore, DefaultErrorHandler}, - event::Event, + event::BroadcastEvent, observer::On, }; - #[derive(Event)] + #[derive(BroadcastEvent)] struct TriggerEvent; #[test] diff --git a/crates/bevy_ecs/src/observer/system_param.rs b/crates/bevy_ecs/src/observer/system_param.rs index 6e165ccd6df67..c9c958150366d 100644 --- a/crates/bevy_ecs/src/observer/system_param.rs +++ b/crates/bevy_ecs/src/observer/system_param.rs @@ -106,21 +106,21 @@ impl<'w, E, B: Bundle> On<'w, E, B> { } impl<'w, E: EntityEvent, B: Bundle> On<'w, E, B> { - /// Returns the [`Entity`] that was targeted by the `event` that triggered this observer. + /// Returns the [`Entity`] that was targeted by the [`EntityEvent`] that triggered this observer. /// /// Note that if event propagation is enabled, this may not be the same as the original target of the event, /// which can be accessed via [`On::original_target`]. /// - /// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`]. + /// If the event is also a [`BroadcastEvent`] sent with [`trigger`](World::trigger), this will return [`Entity::PLACEHOLDER`]. pub fn target(&self) -> Entity { self.trigger.current_target.unwrap_or(Entity::PLACEHOLDER) } - /// Returns the original [`Entity`] that the `event` was targeted at when it was first triggered. + /// Returns the original [`Entity`] that the [`EntityEvent`] was targeted at when it was first triggered. /// /// If event propagation is not enabled, this will always return the same value as [`On::target`]. /// - /// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`]. + /// If the event is also a [`BroadcastEvent`] sent with [`trigger`](World::trigger), this will return [`Entity::PLACEHOLDER`]. pub fn original_target(&self) -> Entity { self.trigger.original_target.unwrap_or(Entity::PLACEHOLDER) } @@ -185,11 +185,13 @@ pub struct ObserverTrigger { pub event_key: EventKey, /// The [`ComponentId`]s the trigger targeted. pub components: SmallVec<[ComponentId; 2]>, - /// The entity that the entity-event targeted, if any. + /// For [`EntityEvent`]s used with `trigger_targets` this is the entity that the event targeted. + /// Can only be `None` for [`BroadcastEvent`]s used with `trigger`. /// /// Note that if event propagation is enabled, this may not be the same as [`ObserverTrigger::original_target`]. pub current_target: Option, - /// The entity that the entity-event was originally targeted at, if any. + /// For [`EntityEvent`]s used with `trigger_targets` this is the entity that the event was originally targeted at. + /// Can only be `None` for [`BroadcastEvent`]s used with `trigger`. /// /// If event propagation is enabled, this will be the first entity that the event was targeted at, /// even if the event was propagated to other entities. diff --git a/crates/bevy_ecs/src/system/commands/command.rs b/crates/bevy_ecs/src/system/commands/command.rs index 83dad342a803c..47898d51c3502 100644 --- a/crates/bevy_ecs/src/system/commands/command.rs +++ b/crates/bevy_ecs/src/system/commands/command.rs @@ -9,7 +9,7 @@ use crate::{ change_detection::MaybeLocation, entity::Entity, error::Result, - event::{BufferedEvent, EntityEvent, Event, Events}, + event::{BroadcastEvent, BufferedEvent, EntityEvent, Events}, observer::TriggerTargets, resource::Resource, schedule::ScheduleLabel, @@ -208,9 +208,9 @@ pub fn run_schedule(label: impl ScheduleLabel) -> impl Command { } } -/// A [`Command`] that sends a global [`Event`] without any targets. +/// A [`Command`] that sends a [`BroadcastEvent`]. #[track_caller] -pub fn trigger(event: impl Event) -> impl Command { +pub fn trigger(event: impl BroadcastEvent) -> impl Command { let caller = MaybeLocation::caller(); move |world: &mut World| { world.trigger_with_caller(event, caller); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index f7e19423797d3..1efbd78bd1a5a 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -20,7 +20,7 @@ use crate::{ component::{Component, ComponentId, Mutable}, entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError, OptIn, OptOut}, error::{warn, BevyError, CommandWithEntity, ErrorContext, HandleError}, - event::{BufferedEvent, EntityEvent, Event}, + event::{BroadcastEvent, BufferedEvent, EntityEvent, Event}, observer::{Observer, TriggerTargets}, resource::Resource, schedule::ScheduleLabel, @@ -1083,11 +1083,11 @@ impl<'w, 's> Commands<'w, 's> { self.queue(command::run_system_cached_with(system, input).handle_error_with(warn)); } - /// Sends a global [`Event`] without any targets. + /// Sends a [`BroadcastEvent`]. /// - /// This will run any [`Observer`] of the given [`Event`] that isn't scoped to specific targets. + /// This will run any [`Observer`] of the given [`BroadcastEvent`] that isn't scoped to specific targets. #[track_caller] - pub fn trigger(&mut self, event: impl Event) { + pub fn trigger(&mut self, event: impl BroadcastEvent) { self.queue(command::trigger(event)); } diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index 862ebf71c7eb4..af5652128d68c 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -53,13 +53,13 @@ where #[cfg(test)] mod tests { use crate::{ - event::Event, + event::BroadcastEvent, observer::On, system::{In, IntoSystem}, world::World, }; - #[derive(Event)] + #[derive(BroadcastEvent)] struct TriggerEvent; #[test] diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index bc87cd4feae50..fb5c0a4b6d753 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -526,6 +526,7 @@ impl core::fmt::Debug for RegisteredSystemError { mod tests { use core::cell::Cell; + use bevy_ecs_macros::BroadcastEvent; use bevy_utils::default; use crate::{prelude::*, system::SystemId}; @@ -898,7 +899,7 @@ mod tests { #[test] fn system_with_input_mut() { - #[derive(Event)] + #[derive(BroadcastEvent)] struct MyEvent { cancelled: bool, } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 3b146d2c0589c..fbae80d0b971e 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -7,7 +7,7 @@ use crate::{ change_detection::{MaybeLocation, MutUntyped}, component::{ComponentId, Mutable}, entity::Entity, - event::{BufferedEvent, EntityEvent, Event, EventId, EventKey, Events, WriteBatchIds}, + event::{BroadcastEvent, BufferedEvent, EntityEvent, EventId, EventKey, Events, WriteBatchIds}, lifecycle::{HookContext, INSERT, REPLACE}, observer::{Observers, TriggerTargets}, prelude::{Component, QueryState}, @@ -860,12 +860,12 @@ impl<'w> DeferredWorld<'w> { } } - /// Sends a global [`Event`] without any targets. + /// Sends a [`BroadcastEvent`]. /// - /// This will run any [`Observer`] of the given [`Event`] that isn't scoped to specific targets. + /// This will run any [`Observer`] of the given [`BroadcastEvent`] that isn't scoped to specific targets. /// /// [`Observer`]: crate::observer::Observer - pub fn trigger(&mut self, trigger: impl Event) { + pub fn trigger(&mut self, trigger: impl BroadcastEvent) { self.commands().trigger(trigger); } diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 13713fe64ce7f..c415e8e1cbeee 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -2,7 +2,7 @@ use crate::{DynamicScene, Scene}; use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_ecs::{ entity::{Entity, EntityHashMap}, - event::{EntityEvent, EventCursor, Events}, + event::{BroadcastEvent, EntityEvent, EventCursor, Events}, hierarchy::ChildOf, reflect::AppTypeRegistry, resource::Resource, @@ -22,11 +22,13 @@ use bevy_ecs::{ system::{Commands, Query}, }; -/// Triggered on a scene's parent entity when [`crate::SceneInstance`] becomes ready to use. +/// This [`Event`] is triggered when the [`SceneInstance`] becomes ready to use. +/// If the scene has a parent the event will be triggered on that entity, otherwise the event has no target. /// /// See also [`On`], [`SceneSpawner::instance_is_ready`]. /// /// [`On`]: bevy_ecs::observer::On +/// [`Event`]: bevy_ecs::event::Event #[derive(Clone, Copy, Debug, Eq, PartialEq, EntityEvent, Reflect)] #[reflect(Debug, PartialEq, Clone)] pub struct SceneInstanceReady { @@ -34,6 +36,8 @@ pub struct SceneInstanceReady { pub instance_id: InstanceId, } +impl BroadcastEvent for SceneInstanceReady {} + /// Information about a scene instance. #[derive(Debug)] pub struct InstanceInfo { diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index 925537c39ef2d..e151ad04a007e 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -60,7 +60,7 @@ impl Mine { } } -#[derive(Event)] +#[derive(BroadcastEvent)] struct ExplodeMines { pos: Vec2, radius: f32, diff --git a/examples/usage/context_menu.rs b/examples/usage/context_menu.rs index a50e5cdabbae7..14db9cc9c2d1c 100644 --- a/examples/usage/context_menu.rs +++ b/examples/usage/context_menu.rs @@ -8,13 +8,13 @@ use bevy::{ use std::fmt::Debug; /// event opening a new context menu at position `pos` -#[derive(Event)] +#[derive(BroadcastEvent)] struct OpenContextMenu { pos: Vec2, } /// event will be sent to close currently open context menus -#[derive(Event)] +#[derive(BroadcastEvent)] struct CloseContextMenus; /// marker component identifying root of a context menu diff --git a/release-content/release-notes/event_split.md b/release-content/release-notes/event_split.md index b650c29e73303..0796406bce0dc 100644 --- a/release-content/release-notes/event_split.md +++ b/release-content/release-notes/event_split.md @@ -1,7 +1,7 @@ --- title: Event Split -authors: ["@Jondolf"] -pull_requests: [19647, 20101] +authors: ["@Jondolf", "@tim-blackbird"] +pull_requests: [19647, 20101, 20104, 20151] --- In past releases, all event types were defined by simply deriving the `Event` trait: @@ -23,21 +23,24 @@ The first two are observer APIs, while the third is a fully separate "buffered" All three patterns are fundamentally different in both the interface and usage. Despite the same event type being used everywhere, APIs are typically built to support only one of them. -This has led to a lot of confusion and frustration for users. A common footgun was using a "buffered event" with an observer, -or an observer event with `EventReader`, leaving the user wondering why the event is not being detected. +This has led to a lot of confusion and frustration for users. Common footguns include: -**Bevy 0.17** aims to solve this ambiguity by splitting the event traits into `Event`, `EntityEvent`, and `BufferedEvent`. +- Using a "buffered event" with an observer, or an observer event with `EventReader`, leaving the user wondering why the event is not being detected. +- `On`(formerly `Trigger`) has a `target` getter which would cause confusion for events only meant to be used with `trigger` where it returns `Entity::PLACEHOLDER`. -- `Event`: A shared trait for observer events. -- `EntityEvent`: An `Event` that additionally supports targeting specific entities and propagating the event from one entity to another. -- `BufferedEvent`: An event that supports usage with `EventReader` and `EventWriter` for pull-based event handling. +**Bevy 0.17** aims to solve this ambiguity by splitting the different kinds of events into multiple traits: + +- `Event`: A supertrait for observer events. + - `BroadcastEvent`: An observer event without an entity target. + - `EntityEvent`: An observer event that targets specific entities and can propagate the event from one entity to another across relationships. +- `BufferedEvent`: An event used with `EventReader` and `EventWriter` for pull-based event handling. ## Using Events -A basic `Event` can be defined like before, by deriving the `Event` trait. +Events without an entity target can be defined, by deriving the `BroadcastEvent` trait. ```rust -#[derive(Event)] +#[derive(BroadcastEvent)] struct Speak { message: String, } @@ -57,8 +60,8 @@ commands.trigger(Speak { }); ``` -To allow an event to be targeted at entities and even propagated further, you can instead derive `EntityEvent`. -It supports optionally specifying some options for propagation using the `event` attribute: +To make an event target entities and even be propagated further, you can instead derive `EntityEvent`. +It supports optionally specifying some options for propagation using the `entity_event` attribute: ```rust // When the `Damage` event is triggered on an entity, bubble the event up to ancestors. @@ -69,8 +72,7 @@ struct Damage { } ``` -Every `EntityEvent` is also an `Event`, so you can still use `trigger` to trigger them globally. -However, entity events also support targeted observer APIs such as `trigger_targets` and `observe`: +`EntityEvent`s can be used with targeted observer APIs such as `trigger_targets` and `observe`: ```rust // Spawn an enemy entity. @@ -116,6 +118,6 @@ fn read_messages(mut reader: EventReader) { In summary: -- Need a basic event you can trigger and observe? Derive `Event`! +- Need an event you can trigger and observe? Derive `BroadcastEvent`! - Need the observer event to be targeted at an entity? Derive `EntityEvent`! - Need the event to be buffered and support the `EventReader`/`EventWriter` API? Derive `BufferedEvent`!