This commit is contained in:
Tim 2025-07-18 00:01:01 +02:00 committed by GitHub
commit 18abe7763a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 234 additions and 198 deletions

View File

@ -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));

View File

@ -1320,7 +1320,7 @@ impl App {
/// #
/// # let mut app = App::new();
/// #
/// # #[derive(Event)]
/// # #[derive(BroadcastEvent)]
/// # struct Party {
/// # friends_allowed: bool,
/// # };

View File

@ -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
}

View File

@ -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 {}
})
}

View File

@ -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,

View File

@ -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);

View File

@ -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 {

View File

@ -11,87 +11,13 @@ 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.
///
/// # Usage
///
/// The [`Event`] trait can be derived:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Event)]
/// struct Speak {
/// message: String,
/// }
/// ```
///
/// An [`Observer`] can then be added to listen for this event type:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # struct Speak {
/// # message: String,
/// # }
/// #
/// # let mut world = World::new();
/// #
/// world.add_observer(|trigger: On<Speak>| {
/// println!("{}", trigger.message);
/// });
/// ```
///
/// The event can be triggered on the [`World`] using the [`trigger`](World::trigger) method:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # struct Speak {
/// # message: String,
/// # }
/// #
/// # let mut world = World::new();
/// #
/// # world.add_observer(|trigger: On<Speak>| {
/// # 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)]`"
note = "consider annotating `{Self}` with `#[derive(BroadcastEvent)]` or `#[derive(EntityEvent)]`"
)]
pub trait Event: Send + Sync + 'static {
/// Generates the [`EventKey`] for this event type.
@ -128,13 +54,68 @@ pub trait Event: Send + Sync + 'static {
}
}
/// 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 [`BroadcastEvent`] trait can be derived:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(BroadcastEvent)]
/// struct Speak {
/// message: String,
/// }
/// ```
///
/// An [`Observer`] can then be added to listen for this event type:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(BroadcastEvent)]
/// # struct Speak {
/// # message: String,
/// # }
/// #
/// # let mut world = World::new();
/// #
/// world.add_observer(|trigger: On<Speak>| {
/// println!("{}", trigger.message);
/// });
/// ```
///
/// The event can be triggered on the [`World`] using the [`trigger`](World::trigger) method:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(BroadcastEvent)]
/// # struct Speak {
/// # message: String,
/// # }
/// #
/// # let mut world = World::new();
/// #
/// world.trigger(Speak {
/// message: "Hello!".to_string(),
/// });
/// ```
///
/// [`Observer`]: crate::observer::Observer
pub trait BroadcastEvent: Event {}
/// An [`Event`] that can be targeted at specific entities.
///
/// Entity events can be triggered on a [`World`] with specific entity targets using a method
/// 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<E>`]: super::Events
/// [`EventReader`]: super::EventReader
/// [`EventWriter`]: super::EventWriter
#[diagnostic::on_unimplemented(
message = "`{Self}` is not an `EntityEvent`",
label = "invalid `EntityEvent`",

View File

@ -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;

View File

@ -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::{

View File

@ -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<Speak>| {});
@ -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<A>, 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

View File

@ -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<E: Event>(&mut self, event: E) {
pub fn trigger<E: BroadcastEvent>(&mut self, event: E) {
self.trigger_with_caller(event, MaybeLocation::caller());
}
pub(crate) fn trigger_with_caller<E: Event>(&mut self, mut event: E, caller: MaybeLocation) {
pub(crate) fn trigger_with_caller<E: BroadcastEvent>(
&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<E: Event>(&mut self, event: &mut E) {
pub fn trigger_ref<E: BroadcastEvent>(&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<E: Event>(
unsafe fn trigger_dynamic_ref_with_caller<E: BroadcastEvent>(
&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 EventWithData {
struct EntityEventA;
#[derive(BroadcastEvent)]
struct BroadcastEventA;
#[derive(EntityEvent)]
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<EventWithData>| trigger.event_mut().counter += 1);
world.add_observer(|mut trigger: On<EventWithData>| trigger.event_mut().counter += 2);
world.add_observer(|mut trigger: On<EventWithData>| trigger.event_mut().counter += 4);
world.add_observer(|mut trigger: On<BroadcastEventWithData>| {
trigger.event_mut().counter += 1;
});
world.add_observer(|mut trigger: On<BroadcastEventWithData>| {
trigger.event_mut().counter += 2;
});
world.add_observer(|mut trigger: On<BroadcastEventWithData>| {
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<EventWithData, A>| {
world.add_observer(|mut trigger: On<EntityEventWithData, A>| {
trigger.event_mut().counter += 1;
});
world.add_observer(|mut trigger: On<EventWithData, B>| {
world.add_observer(|mut trigger: On<EntityEventWithData, B>| {
trigger.event_mut().counter += 2;
});
world.add_observer(|mut trigger: On<EventWithData, A>| {
world.add_observer(|mut trigger: On<EntityEventWithData, A>| {
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::<A>();
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::<Order>();
let system: fn(On<EventA>) = |_| {
let system: fn(On<EntityEventA>) = |_| {
panic!("Trigger routed to non-targeted entity.");
};
world.spawn_empty().observe(system);
let entity = world
.spawn_empty()
.observe(|_: On<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
.observe(|_: On<EntityEventA>, mut res: ResMut<Order>| res.observed("a_1"))
.id();
world.add_observer(move |obs: On<EventA>, mut res: ResMut<Order>| {
world.add_observer(move |obs: On<EntityEventA>, mut res: ResMut<Order>| {
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::<Order>().0);
}
@ -881,26 +906,27 @@ mod tests {
// targets (entity_1, A)
let entity_1 = world
.spawn_empty()
.observe(|_: On<EventA, A>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: On<EntityEventA, A>, mut res: ResMut<R>| res.0 += 1)
.id();
// targets (entity_2, B)
let entity_2 = world
.spawn_empty()
.observe(|_: On<EventA, B>, mut res: ResMut<R>| res.0 += 10)
.observe(|_: On<EntityEventA, B>, mut res: ResMut<R>| res.0 += 10)
.id();
// targets any entity or component
world.add_observer(|_: On<EventA>, mut res: ResMut<R>| res.0 += 100);
world.add_observer(|_: On<EntityEventA>, mut res: ResMut<R>| res.0 += 100);
// targets any entity, and components A or B
world.add_observer(|_: On<EventA, (A, B)>, mut res: ResMut<R>| res.0 += 1000);
world.add_observer(|_: On<EntityEventA, (A, B)>, mut res: ResMut<R>| res.0 += 1000);
// test all tuples
world.add_observer(|_: On<EventA, (A, B, (A, B))>, mut res: ResMut<R>| res.0 += 10000);
world
.add_observer(|_: On<EntityEventA, (A, B, (A, B))>, mut res: ResMut<R>| res.0 += 10000);
world.add_observer(
|_: On<EventA, (A, B, (A, B), ((A, B), (A, B)))>, mut res: ResMut<R>| {
|_: On<EntityEventA, (A, B, (A, B), ((A, B), (A, B)))>, mut res: ResMut<R>| {
res.0 += 100000;
},
);
world.add_observer(
|_: On<EventA, (A, B, (A, B), (B, A), (A, B, ((A, B), (B, A))))>,
|_: On<EntityEventA, (A, B, (A, B), (B, A), (A, B, ((A, B), (B, A))))>,
mut res: ResMut<R>| 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::<R>().0);
world.resource_mut::<R>().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::<R>().0);
world.resource_mut::<R>().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::<R>().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::<R>().0);
world.resource_mut::<R>().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::<Order>().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::<Order>().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<EventA>, _: Res<ResA>, mut commands: Commands| {
commands.insert_resource(ResB);
});
world.trigger(EventA);
world.add_observer(
|_: On<BroadcastEventA>, _: Res<ResA>, 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<EventA>, mut params: ParamSet<(Query<Entity>, Commands)>| {
|_: On<BroadcastEventA>, mut params: ParamSet<(Query<Entity>, 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::<ResA>().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::<B>();
world.add_observer(
|trigger: On<EventA, (A, B)>, mut counter: ResMut<Counter>| {
|trigger: On<EntityEventA, (A, B)>, mut counter: ResMut<Counter>| {
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::<Counter>();

View File

@ -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]

View File

@ -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<Entity>,
/// 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.

View File

@ -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<Result> {
}
}
/// 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);

View File

@ -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));
}

View File

@ -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]

View File

@ -526,6 +526,7 @@ impl<I: SystemInput, O> core::fmt::Debug for RegisteredSystemError<I, O> {
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,
}

View File

@ -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);
}

View File

@ -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 {

View File

@ -60,7 +60,7 @@ impl Mine {
}
}
#[derive(Event)]
#[derive(BroadcastEvent)]
struct ExplodeMines {
pos: Vec2,
radius: f32,

View File

@ -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

View File

@ -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<Message>) {
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`!