Skip to content

Split observer events #20151

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions benches/benches/bevy_ecs/observers/simple.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::hint::black_box;

use bevy_ecs::{
event::EntityEvent,
event::{BroadcastEvent, EntityEvent, Event},
observer::{On, TriggerTargets},
world::World,
};
Expand All @@ -13,6 +13,9 @@ fn deterministic_rand() -> ChaCha8Rng {
ChaCha8Rng::seed_from_u64(42)
}

#[derive(Clone, BroadcastEvent)]
struct BroadcastEventBase;

#[derive(Clone, EntityEvent)]
struct EventBase;

Expand All @@ -23,10 +26,10 @@ pub fn observe_simple(criterion: &mut Criterion) {

group.bench_function("trigger_simple", |bencher| {
let mut world = World::new();
world.add_observer(empty_listener_base);
world.add_observer(empty_listener::<BroadcastEventBase>);
bencher.iter(|| {
for _ in 0..10000 {
world.trigger(EventBase);
world.trigger(BroadcastEventBase);
}
});
});
Expand All @@ -35,7 +38,12 @@ pub fn observe_simple(criterion: &mut Criterion) {
let mut world = World::new();
let mut entities = vec![];
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());
bencher.iter(|| {
Expand All @@ -46,7 +54,7 @@ pub fn observe_simple(criterion: &mut Criterion) {
group.finish();
}

fn empty_listener_base(trigger: On<EventBase>) {
fn empty_listener<E: Event>(trigger: On<E>) {
black_box(trigger);
}

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1320,7 +1320,7 @@ impl App {
/// #
/// # let mut app = App::new();
/// #
/// # #[derive(Event)]
/// # #[derive(BroadcastEvent)]
/// # struct Party {
/// # friends_allowed: bool,
/// # };
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
12 changes: 6 additions & 6 deletions crates/bevy_ecs/macros/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -30,7 +30,9 @@ pub fn derive_event(input: TokenStream) -> TokenStream {
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();

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 {
type Kind = #bevy_ecs_path::event::BroadcastEventKind;
}
})
}

Expand Down Expand Up @@ -73,10 +75,8 @@ pub fn derive_entity_event(input: TokenStream) -> TokenStream {
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();

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::EntityEvent for #struct_name #type_generics #where_clause {
type Traversal = #traversal;
const AUTO_PROPAGATE: bool = #auto_propagate;
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {
type Kind = #bevy_ecs_path::event::EntityEventKind<Self, #traversal, #auto_propagate>;
}
})
}
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
/// Implement the `BroadcastEvent` trait.
#[proc_macro_derive(BroadcastEvent)]
pub fn derive_event(input: TokenStream) -> TokenStream {
component::derive_event(input)
component::derive_broadcast_event(input)
}

/// Cheat sheet for derive syntax,
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand All @@ -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);

Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/component/tick.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.
///
Expand All @@ -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 {
Expand Down
73 changes: 68 additions & 5 deletions crates/bevy_ecs/src/event/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use core::{
marker::PhantomData,
};

// TODO: These docs need to be moved around and adjusted
/// Something that "happens" and can be processed by app logic.
///
/// Events can be triggered on a [`World`] using a method like [`trigger`](World::trigger),
Expand All @@ -35,7 +36,7 @@ use core::{
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Event)]
/// #[derive(BroadcastEvent)]
/// struct Speak {
/// message: String,
/// }
Expand All @@ -46,7 +47,7 @@ use core::{
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # #[derive(BroadcastEvent)]
/// # struct Speak {
/// # message: String,
/// # }
Expand All @@ -63,7 +64,7 @@ use core::{
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # #[derive(BroadcastEvent)]
/// # struct Speak {
/// # message: String,
/// # }
Expand Down Expand Up @@ -91,9 +92,12 @@ use core::{
#[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 {
#[doc(hidden)]
type Kind;

/// Generates the [`EventKey`] for this event type.
///
/// If this type has already been registered,
Expand Down Expand Up @@ -128,6 +132,34 @@ pub trait Event: Send + Sync + 'static {
}
}

/// A global [`Event`] without an entity target.
#[diagnostic::on_unimplemented(
message = "`{Self}` is not an `BroadcastEvent`",
label = "invalid `BroadcastEvent`",
note = "consider annotating `{Self}` with `#[derive(BroadcastEvent)]`"
)]
pub trait BroadcastEvent: Event + sealed_a::SealedA {}

#[doc(hidden)]
pub struct BroadcastEventKind;

#[diagnostic::do_not_recommend]
impl<T> BroadcastEvent for T where T: Event<Kind = BroadcastEventKind> {}

pub(crate) mod sealed_a {
use super::*;

/// Seal for the [`BroadcastEvent`] trait.
#[diagnostic::on_unimplemented(
message = "manual implementations of `BroadcastEvent` are disallowed",
note = "consider annotating `{Self}` with `#[derive(BroadcastEvent)]` instead"
)]
pub trait SealedA {}

#[diagnostic::do_not_recommend]
impl<T> SealedA for T where T: Event<Kind = BroadcastEventKind> {}
}

/// An [`Event`] that can be targeted at specific entities.
///
/// Entity events can be triggered on a [`World`] with specific entity targets using a method
Expand Down Expand Up @@ -244,7 +276,7 @@ pub trait Event: Send + Sync + 'static {
label = "invalid `EntityEvent`",
note = "consider annotating `{Self}` with `#[derive(EntityEvent)]`"
)]
pub trait EntityEvent: Event {
pub trait EntityEvent: Event + sealed_b::SealedB {
/// The component that describes which [`Entity`] to propagate this event to next, when [propagation] is enabled.
///
/// [`Entity`]: crate::entity::Entity
Expand All @@ -259,6 +291,37 @@ pub trait EntityEvent: Event {
const AUTO_PROPAGATE: bool = false;
}

#[doc(hidden)]
pub struct EntityEventKind<T: ?Sized, R: Traversal<T>, const A: bool> {
_t: PhantomData<T>,
_r: PhantomData<R>,
}

// Blanket impl for EntityEvent
#[diagnostic::do_not_recommend]
impl<T, R: Traversal<T>, const A: bool> EntityEvent for T
where
T: Event<Kind = EntityEventKind<T, R, A>>,
{
type Traversal = R;
const AUTO_PROPAGATE: bool = A;
}

pub(crate) mod sealed_b {
use super::*;

/// Seal for the [`EntityEvent`] trait.
#[diagnostic::on_unimplemented(
message = "manual implementations of `EntityEvent` are disallowed",
note = "consider annotating `{Self}` with `#[derive(EntityEvent)]` instead"
)]
pub trait SealedB {}

#[diagnostic::do_not_recommend]
impl<T, R: Traversal<T>, const A: bool> SealedB for T where T: Event<Kind = EntityEventKind<T, R, A>>
{}
}

/// A buffered event for pull-based event handling.
///
/// Buffered events can be written with [`EventWriter`] and read using the [`EventReader`] system parameter.
Expand Down
7 changes: 5 additions & 2 deletions crates/bevy_ecs/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ 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, BroadcastEventKind, BufferedEvent, EntityEvent, EntityEventKind, 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;
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
12 changes: 6 additions & 6 deletions crates/bevy_ecs/src/observer/distributed_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
/// }
Expand All @@ -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>| {});
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand Down
Loading
Loading