Split new BroadcastEvent trait from Event

This commit is contained in:
Tim Blackbird 2025-07-15 15:28:33 +02:00
parent 57086d4416
commit 4d82820e97
18 changed files with 130 additions and 112 deletions

View File

@ -1,7 +1,7 @@
use core::hint::black_box; use core::hint::black_box;
use bevy_ecs::{ use bevy_ecs::{
event::EntityEvent, event::{BroadcastEvent, EntityEvent, Event},
observer::{On, TriggerTargets}, observer::{On, TriggerTargets},
world::World, world::World,
}; };
@ -13,6 +13,9 @@ fn deterministic_rand() -> ChaCha8Rng {
ChaCha8Rng::seed_from_u64(42) ChaCha8Rng::seed_from_u64(42)
} }
#[derive(Clone, BroadcastEvent)]
struct BroadcastEventBase;
#[derive(Clone, EntityEvent)] #[derive(Clone, EntityEvent)]
struct EventBase; struct EventBase;
@ -23,10 +26,10 @@ pub fn observe_simple(criterion: &mut Criterion) {
group.bench_function("trigger_simple", |bencher| { group.bench_function("trigger_simple", |bencher| {
let mut world = World::new(); let mut world = World::new();
world.add_observer(empty_listener_base); world.add_observer(empty_listener::<BroadcastEventBase>);
bencher.iter(|| { bencher.iter(|| {
for _ in 0..10000 { for _ in 0..10000 {
world.trigger(EventBase); world.trigger(BroadcastEventBase);
} }
}); });
}); });
@ -35,7 +38,12 @@ pub fn observe_simple(criterion: &mut Criterion) {
let mut world = World::new(); let mut world = World::new();
let mut entities = vec![]; let mut entities = vec![];
for _ in 0..10000 { for _ in 0..10000 {
entities.push(world.spawn_empty().observe(empty_listener_base).id()); entities.push(
world
.spawn_empty()
.observe(empty_listener::<EventBase>)
.id(),
);
} }
entities.shuffle(&mut deterministic_rand()); entities.shuffle(&mut deterministic_rand());
bencher.iter(|| { bencher.iter(|| {
@ -46,7 +54,7 @@ pub fn observe_simple(criterion: &mut Criterion) {
group.finish(); group.finish();
} }
fn empty_listener_base(trigger: On<EventBase>) { fn empty_listener<E: Event>(trigger: On<E>) {
black_box(trigger); black_box(trigger);
} }

View File

@ -306,7 +306,7 @@ Observers are systems that listen for a "trigger" of a specific `Event`:
```rust ```rust
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
#[derive(Event)] #[derive(BroadcastEvent)]
struct Speak { struct Speak {
message: String message: String
} }

View File

@ -17,7 +17,7 @@ pub const EVENT: &str = "entity_event";
pub const AUTO_PROPAGATE: &str = "auto_propagate"; pub const AUTO_PROPAGATE: &str = "auto_propagate";
pub const TRAVERSAL: &str = "traversal"; 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 mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path(); let bevy_ecs_path: Path = crate::bevy_ecs_path();
@ -31,6 +31,7 @@ pub fn derive_event(input: TokenStream) -> TokenStream {
TokenStream::from(quote! { 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::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") BevyManifest::shared().get_path("bevy_ecs")
} }
/// Implement the `Event` trait. /// Implement the `BroadcastEvent` trait.
#[proc_macro_derive(Event)] #[proc_macro_derive(BroadcastEvent)]
pub fn derive_event(input: TokenStream) -> TokenStream { pub fn derive_event(input: TokenStream) -> TokenStream {
component::derive_event(input) component::derive_broadcast_event(input)
} }
/// Cheat sheet for derive syntax, /// Cheat sheet for derive syntax,

View File

@ -23,7 +23,7 @@ use crate::{
bundle::BundleId, bundle::BundleId,
component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, component::{ComponentId, Components, RequiredComponentConstructor, StorageType},
entity::{Entity, EntityLocation}, entity::{Entity, EntityLocation},
event::Event, event::BroadcastEvent,
observer::Observers, observer::Observers,
storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow}, storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow},
}; };
@ -35,7 +35,7 @@ use core::{
}; };
use nonmax::NonMaxU32; use nonmax::NonMaxU32;
#[derive(Event)] #[derive(BroadcastEvent)]
#[expect(dead_code, reason = "Prepare for the upcoming Query as Entities")] #[expect(dead_code, reason = "Prepare for the upcoming Query as Entities")]
pub(crate) struct ArchetypeCreated(pub ArchetypeId); 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; use bevy_ptr::UnsafeCellDeref;
#[cfg(feature = "bevy_reflect")] #[cfg(feature = "bevy_reflect")]
use bevy_reflect::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 /// 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. /// 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); /// schedule.0.check_change_ticks(*check);
/// }); /// });
/// ``` /// ```
#[derive(Debug, Clone, Copy, Event)] #[derive(Debug, Clone, Copy, BroadcastEvent)]
pub struct CheckChangeTicks(pub(crate) Tick); pub struct CheckChangeTicks(pub(crate) Tick);
impl CheckChangeTicks { impl CheckChangeTicks {

View File

@ -11,6 +11,7 @@ use core::{
marker::PhantomData, marker::PhantomData,
}; };
// TODO: Adjust these docs
/// Something that "happens" and can be processed by app logic. /// Something that "happens" and can be processed by app logic.
/// ///
/// Events can be triggered on a [`World`] using a method like [`trigger`](World::trigger), /// Events can be triggered on a [`World`] using a method like [`trigger`](World::trigger),
@ -89,9 +90,9 @@ use core::{
/// [`EventReader`]: super::EventReader /// [`EventReader`]: super::EventReader
/// [`EventWriter`]: super::EventWriter /// [`EventWriter`]: super::EventWriter
#[diagnostic::on_unimplemented( #[diagnostic::on_unimplemented(
message = "`{Self}` is not an `Event`", message = "`{Self}` is not an `ObserverEvent`",
label = "invalid `Event`", label = "invalid `ObserverEvent`",
note = "consider annotating `{Self}` with `#[derive(Event)]`" note = "consider annotating `{Self}` with `#[derive(BroadcastEvent)]` or `#[derive(EntityEvent)]`"
)] )]
pub trait Event: Send + Sync + 'static { pub trait Event: Send + Sync + 'static {
/// Generates the [`EventKey`] for this event type. /// Generates the [`EventKey`] for this event type.
@ -128,6 +129,9 @@ pub trait Event: Send + Sync + 'static {
} }
} }
/// A global [`Event`] without an entity target.
pub trait BroadcastEvent: Event {}
/// An [`Event`] that can be targeted at specific entities. /// An [`Event`] that can be targeted at specific entities.
/// ///
/// Entity events can be triggered on a [`World`] with specific entity targets using a method /// Entity events can be triggered on a [`World`] with specific entity targets using a method

View File

@ -11,8 +11,8 @@ mod update;
mod writer; mod writer;
pub(crate) use base::EventInstance; pub(crate) use base::EventInstance;
pub use base::{BufferedEvent, EntityEvent, Event, EventId, EventKey}; pub use base::{BroadcastEvent, BufferedEvent, EntityEvent, Event, EventId, EventKey};
pub use bevy_ecs_macros::{BufferedEvent, EntityEvent, Event}; pub use bevy_ecs_macros::{BroadcastEvent, BufferedEvent, EntityEvent};
#[expect(deprecated, reason = "`SendBatchIds` was renamed to `WriteBatchIds`.")] #[expect(deprecated, reason = "`SendBatchIds` was renamed to `WriteBatchIds`.")]
pub use collections::{Events, SendBatchIds, WriteBatchIds}; pub use collections::{Events, SendBatchIds, WriteBatchIds};
pub use event_cursor::EventCursor; pub use event_cursor::EventCursor;

View File

@ -79,8 +79,8 @@ pub mod prelude {
entity::{ContainsEntity, Entity, EntityMapper}, entity::{ContainsEntity, Entity, EntityMapper},
error::{BevyError, Result}, error::{BevyError, Result},
event::{ event::{
BufferedEvent, EntityEvent, Event, EventKey, EventMutator, EventReader, EventWriter, BroadcastEvent, BufferedEvent, EntityEvent, Event, EventKey, EventMutator, EventReader,
Events, EventWriter, Events,
}, },
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children}, hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
lifecycle::{ lifecycle::{

View File

@ -145,6 +145,7 @@ pub use trigger_targets::*;
use crate::{ use crate::{
change_detection::MaybeLocation, change_detection::MaybeLocation,
component::ComponentId, component::ComponentId,
event::BroadcastEvent,
prelude::*, prelude::*,
system::IntoObserverSystem, system::IntoObserverSystem,
world::{DeferredWorld, *}, world::{DeferredWorld, *},
@ -186,17 +187,21 @@ impl World {
self.spawn(Observer::new(system)) 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`], /// While event types commonly implement [`Copy`],
/// those that don't will be consumed and will no longer be accessible. /// 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. /// If you need to use the event after triggering it, use [`World::trigger_ref`] instead.
#[track_caller] #[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()); 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); let event_key = E::register_event_key(self);
// SAFETY: We just registered `event_key` with the type of `event` // SAFETY: We just registered `event_key` with the type of `event`
unsafe { 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 /// 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. /// or use the event after it has been modified by observers.
#[track_caller] #[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); let event_key = E::register_event_key(self);
// SAFETY: We just registered `event_key` with the type of `event` // SAFETY: We just registered `event_key` with the type of `event`
unsafe { self.trigger_dynamic_ref_with_caller(event_key, event, MaybeLocation::caller()) }; 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, &mut self,
event_key: EventKey, event_key: EventKey,
event_data: &mut E, event_data: &mut E,
@ -497,6 +502,7 @@ impl World {
mod tests { mod tests {
use alloc::{vec, vec::Vec}; use alloc::{vec, vec::Vec};
use bevy_ecs_macros::BroadcastEvent;
use bevy_platform::collections::HashMap; use bevy_platform::collections::HashMap;
use bevy_ptr::OwningPtr; use bevy_ptr::OwningPtr;
@ -522,10 +528,18 @@ mod tests {
struct S; struct S;
#[derive(EntityEvent)] #[derive(EntityEvent)]
struct EventA; struct EntityEventA;
#[derive(BroadcastEvent)]
struct BroadcastEventA;
#[derive(EntityEvent)] #[derive(EntityEvent)]
struct EventWithData { struct EntityEventWithData {
counter: usize,
}
#[derive(BroadcastEvent)]
struct BroadcastEventWithData {
counter: usize, counter: usize,
} }
@ -681,14 +695,20 @@ mod tests {
fn observer_trigger_ref() { fn observer_trigger_ref() {
let mut world = World::new(); let mut world = World::new();
world.add_observer(|mut trigger: On<EventWithData>| trigger.event_mut().counter += 1); world.add_observer(|mut trigger: On<BroadcastEventWithData>| {
world.add_observer(|mut trigger: On<EventWithData>| trigger.event_mut().counter += 2); trigger.event_mut().counter += 1;
world.add_observer(|mut trigger: On<EventWithData>| trigger.event_mut().counter += 4); });
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, // This flush is required for the last observer to be called when triggering the event,
// due to `World::add_observer` returning `WorldEntityMut`. // due to `World::add_observer` returning `WorldEntityMut`.
world.flush(); world.flush();
let mut event = EventWithData { counter: 0 }; let mut event = BroadcastEventWithData { counter: 0 };
world.trigger_ref(&mut event); world.trigger_ref(&mut event);
assert_eq!(7, event.counter); assert_eq!(7, event.counter);
} }
@ -697,20 +717,20 @@ mod tests {
fn observer_trigger_targets_ref() { fn observer_trigger_targets_ref() {
let mut world = World::new(); 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; 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; 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; trigger.event_mut().counter += 4;
}); });
// This flush is required for the last observer to be called when triggering the event, // This flush is required for the last observer to be called when triggering the event,
// due to `World::add_observer` returning `WorldEntityMut`. // due to `World::add_observer` returning `WorldEntityMut`.
world.flush(); world.flush();
let mut event = EventWithData { counter: 0 }; let mut event = EntityEventWithData { counter: 0 };
let component_a = world.register_component::<A>(); let component_a = world.register_component::<A>();
world.trigger_targets_ref(&mut event, component_a); world.trigger_targets_ref(&mut event, component_a);
assert_eq!(5, event.counter); assert_eq!(5, event.counter);
@ -819,43 +839,21 @@ mod tests {
assert_eq!(vec!["add_ab"], world.resource::<Order>().0); assert_eq!(vec!["add_ab"], world.resource::<Order>().0);
} }
#[test]
fn observer_no_target() {
let mut world = World::new();
world.init_resource::<Order>();
let system: fn(On<EventA>) = |_| {
panic!("Trigger routed to non-targeted entity.");
};
world.spawn_empty().observe(system);
world.add_observer(move |obs: On<EventA>, mut res: ResMut<Order>| {
assert_eq!(obs.target(), Entity::PLACEHOLDER);
res.observed("event_a");
});
// 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.flush();
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
}
#[test] #[test]
fn observer_entity_routing() { fn observer_entity_routing() {
let mut world = World::new(); let mut world = World::new();
world.init_resource::<Order>(); world.init_resource::<Order>();
let system: fn(On<EventA>) = |_| { let system: fn(On<EntityEventA>) = |_| {
panic!("Trigger routed to non-targeted entity."); panic!("Trigger routed to non-targeted entity.");
}; };
world.spawn_empty().observe(system); world.spawn_empty().observe(system);
let entity = world let entity = world
.spawn_empty() .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(); .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); assert_eq!(obs.target(), entity);
res.observed("a_2"); res.observed("a_2");
}); });
@ -863,7 +861,7 @@ mod tests {
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
// and therefore does not automatically flush. // and therefore does not automatically flush.
world.flush(); world.flush();
world.trigger_targets(EventA, entity); world.trigger_targets(EntityEventA, entity);
world.flush(); world.flush();
assert_eq!(vec!["a_2", "a_1"], world.resource::<Order>().0); assert_eq!(vec!["a_2", "a_1"], world.resource::<Order>().0);
} }
@ -881,26 +879,27 @@ mod tests {
// targets (entity_1, A) // targets (entity_1, A)
let entity_1 = world let entity_1 = world
.spawn_empty() .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(); .id();
// targets (entity_2, B) // targets (entity_2, B)
let entity_2 = world let entity_2 = world
.spawn_empty() .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(); .id();
// targets any entity or component // 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 // 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 // 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( 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; res.0 += 100000;
}, },
); );
world.add_observer( 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, mut res: ResMut<R>| res.0 += 1000000,
); );
@ -908,21 +907,21 @@ mod tests {
world.flush(); world.flush();
// trigger for an entity and a component // 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(); world.flush();
// only observer that doesn't trigger is the one only watching entity_2 // only observer that doesn't trigger is the one only watching entity_2
assert_eq!(1111101, world.resource::<R>().0); assert_eq!(1111101, world.resource::<R>().0);
world.resource_mut::<R>().0 = 0; world.resource_mut::<R>().0 = 0;
// trigger for both entities, but no components: trigger once per entity target // 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(); world.flush();
// only the observer that doesn't require components triggers - once per entity // only the observer that doesn't require components triggers - once per entity
assert_eq!(200, world.resource::<R>().0); assert_eq!(200, world.resource::<R>().0);
world.resource_mut::<R>().0 = 0; world.resource_mut::<R>().0 = 0;
// trigger for both components, but no entities: trigger once // 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(); world.flush();
// all component observers trigger, entities are not observed // all component observers trigger, entities are not observed
assert_eq!(1111100, world.resource::<R>().0); assert_eq!(1111100, world.resource::<R>().0);
@ -930,14 +929,17 @@ mod tests {
// trigger for both entities and both components: trigger once per entity target // 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 // 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(); world.flush();
assert_eq!(2222211, world.resource::<R>().0); assert_eq!(2222211, world.resource::<R>().0);
world.resource_mut::<R>().0 = 0; world.resource_mut::<R>().0 = 0;
// trigger to test complex tuples: (A, B, (A, B)) // trigger to test complex tuples: (A, B, (A, B))
world.trigger_targets( world.trigger_targets(
EventA, EntityEventA,
(component_a, component_b, (component_a, component_b)), (component_a, component_b, (component_a, component_b)),
); );
world.flush(); world.flush();
@ -947,7 +949,7 @@ mod tests {
// trigger to test complex tuples: (A, B, (A, B), ((A, B), (A, B))) // trigger to test complex tuples: (A, B, (A, B), ((A, B), (A, B)))
world.trigger_targets( world.trigger_targets(
EventA, EntityEventA,
( (
component_a, component_a,
component_b, component_b,
@ -962,7 +964,7 @@ mod tests {
// trigger to test the most complex tuple: (A, B, (A, B), (B, A), (A, B, ((A, B), (B, A)))) // trigger to test the most complex tuple: (A, B, (A, B), (B, A), (A, B, ((A, B), (B, A))))
world.trigger_targets( world.trigger_targets(
EventA, EntityEventA,
( (
component_a, component_a,
component_b, component_b,
@ -999,7 +1001,7 @@ mod tests {
}); });
let entity = entity.flush(); let entity = entity.flush();
world.trigger_targets(EventA, entity); world.trigger_targets(EntityEventA, entity);
world.flush(); world.flush();
assert_eq!(vec!["event_a"], world.resource::<Order>().0); assert_eq!(vec!["event_a"], world.resource::<Order>().0);
} }
@ -1021,7 +1023,7 @@ mod tests {
world.commands().queue(move |world: &mut World| { world.commands().queue(move |world: &mut World| {
// SAFETY: we registered `event_a` above and it matches the type of EventA // 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(); world.flush();
assert_eq!(vec!["event_a"], world.resource::<Order>().0); assert_eq!(vec!["event_a"], world.resource::<Order>().0);
@ -1350,10 +1352,12 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
// This fails because `ResA` is not present in the world // This fails because `ResA` is not present in the world
world.add_observer(|_: On<EventA>, _: Res<ResA>, mut commands: Commands| { world.add_observer(
commands.insert_resource(ResB); |_: On<BroadcastEventA>, _: Res<ResA>, mut commands: Commands| {
}); commands.insert_resource(ResB);
world.trigger(EventA); },
);
world.trigger(BroadcastEventA);
} }
#[test] #[test]
@ -1363,14 +1367,14 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
world.add_observer( world.add_observer(
|_: On<EventA>, mut params: ParamSet<(Query<Entity>, Commands)>| { |_: On<BroadcastEventA>, mut params: ParamSet<(Query<Entity>, Commands)>| {
params.p1().insert_resource(ResA); params.p1().insert_resource(ResA);
}, },
); );
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
// and therefore does not automatically flush. // and therefore does not automatically flush.
world.flush(); world.flush();
world.trigger(EventA); world.trigger(BroadcastEventA);
world.flush(); world.flush();
assert!(world.get_resource::<ResA>().is_some()); assert!(world.get_resource::<ResA>().is_some());
@ -1379,7 +1383,7 @@ mod tests {
#[test] #[test]
#[track_caller] #[track_caller]
fn observer_caller_location_event() { fn observer_caller_location_event() {
#[derive(Event)] #[derive(BroadcastEvent)]
struct EventA; struct EventA;
let caller = MaybeLocation::caller(); let caller = MaybeLocation::caller();
@ -1419,7 +1423,7 @@ mod tests {
let b_id = world.register_component::<B>(); let b_id = world.register_component::<B>();
world.add_observer( 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() { for &component in trigger.components() {
*counter.0.entry(component).or_default() += 1; *counter.0.entry(component).or_default() += 1;
} }
@ -1427,11 +1431,11 @@ mod tests {
); );
world.flush(); world.flush();
world.trigger_targets(EventA, [a_id, b_id]); world.trigger_targets(EntityEventA, [a_id, b_id]);
world.trigger_targets(EventA, a_id); world.trigger_targets(EntityEventA, a_id);
world.trigger_targets(EventA, b_id); world.trigger_targets(EntityEventA, b_id);
world.trigger_targets(EventA, [a_id, b_id]); world.trigger_targets(EntityEventA, [a_id, b_id]);
world.trigger_targets(EventA, a_id); world.trigger_targets(EntityEventA, a_id);
world.flush(); world.flush();
let counter = world.resource::<Counter>(); let counter = world.resource::<Counter>();

View File

@ -93,11 +93,11 @@ mod tests {
use super::*; use super::*;
use crate::{ use crate::{
error::{ignore, DefaultErrorHandler}, error::{ignore, DefaultErrorHandler},
event::Event, event::BroadcastEvent,
observer::On, observer::On,
}; };
#[derive(Event)] #[derive(BroadcastEvent)]
struct TriggerEvent; struct TriggerEvent;
#[test] #[test]

View File

@ -9,7 +9,7 @@ use crate::{
change_detection::MaybeLocation, change_detection::MaybeLocation,
entity::Entity, entity::Entity,
error::Result, error::Result,
event::{BufferedEvent, EntityEvent, Event, Events}, event::{BroadcastEvent, BufferedEvent, EntityEvent, Events},
observer::TriggerTargets, observer::TriggerTargets,
resource::Resource, resource::Resource,
schedule::ScheduleLabel, 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] #[track_caller]
pub fn trigger(event: impl Event) -> impl Command { pub fn trigger(event: impl BroadcastEvent) -> impl Command {
let caller = MaybeLocation::caller(); let caller = MaybeLocation::caller();
move |world: &mut World| { move |world: &mut World| {
world.trigger_with_caller(event, caller); world.trigger_with_caller(event, caller);

View File

@ -20,7 +20,7 @@ use crate::{
component::{Component, ComponentId, Mutable}, component::{Component, ComponentId, Mutable},
entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError, OptIn, OptOut}, entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError, OptIn, OptOut},
error::{warn, BevyError, CommandWithEntity, ErrorContext, HandleError}, error::{warn, BevyError, CommandWithEntity, ErrorContext, HandleError},
event::{BufferedEvent, EntityEvent, Event}, event::{BroadcastEvent, BufferedEvent, EntityEvent, Event},
observer::{Observer, TriggerTargets}, observer::{Observer, TriggerTargets},
resource::Resource, resource::Resource,
schedule::ScheduleLabel, 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)); 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] #[track_caller]
pub fn trigger(&mut self, event: impl Event) { pub fn trigger(&mut self, event: impl BroadcastEvent) {
self.queue(command::trigger(event)); self.queue(command::trigger(event));
} }

View File

@ -53,13 +53,13 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
event::Event, event::BroadcastEvent,
observer::On, observer::On,
system::{In, IntoSystem}, system::{In, IntoSystem},
world::World, world::World,
}; };
#[derive(Event)] #[derive(BroadcastEvent)]
struct TriggerEvent; struct TriggerEvent;
#[test] #[test]

View File

@ -526,6 +526,7 @@ impl<I: SystemInput, O> core::fmt::Debug for RegisteredSystemError<I, O> {
mod tests { mod tests {
use core::cell::Cell; use core::cell::Cell;
use bevy_ecs_macros::BroadcastEvent;
use bevy_utils::default; use bevy_utils::default;
use crate::{prelude::*, system::SystemId}; use crate::{prelude::*, system::SystemId};
@ -898,7 +899,7 @@ mod tests {
#[test] #[test]
fn system_with_input_mut() { fn system_with_input_mut() {
#[derive(Event)] #[derive(BroadcastEvent)]
struct MyEvent { struct MyEvent {
cancelled: bool, cancelled: bool,
} }

View File

@ -7,7 +7,7 @@ use crate::{
change_detection::{MaybeLocation, MutUntyped}, change_detection::{MaybeLocation, MutUntyped},
component::{ComponentId, Mutable}, component::{ComponentId, Mutable},
entity::Entity, entity::Entity,
event::{BufferedEvent, EntityEvent, Event, EventId, EventKey, Events, WriteBatchIds}, event::{BroadcastEvent, BufferedEvent, EntityEvent, EventId, EventKey, Events, WriteBatchIds},
lifecycle::{HookContext, INSERT, REPLACE}, lifecycle::{HookContext, INSERT, REPLACE},
observer::{Observers, TriggerTargets}, observer::{Observers, TriggerTargets},
prelude::{Component, QueryState}, 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 /// [`Observer`]: crate::observer::Observer
pub fn trigger(&mut self, trigger: impl Event) { pub fn trigger(&mut self, trigger: impl BroadcastEvent) {
self.commands().trigger(trigger); self.commands().trigger(trigger);
} }

View File

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

View File

@ -8,13 +8,13 @@ use bevy::{
use std::fmt::Debug; use std::fmt::Debug;
/// event opening a new context menu at position `pos` /// event opening a new context menu at position `pos`
#[derive(Event)] #[derive(BroadcastEvent)]
struct OpenContextMenu { struct OpenContextMenu {
pos: Vec2, pos: Vec2,
} }
/// event will be sent to close currently open context menus /// event will be sent to close currently open context menus
#[derive(Event)] #[derive(BroadcastEvent)]
struct CloseContextMenus; struct CloseContextMenus;
/// marker component identifying root of a context menu /// marker component identifying root of a context menu