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 all 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
4 changes: 3 additions & 1 deletion 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},
observer::{On, TriggerTargets},
world::World,
};
Expand All @@ -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));
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
3 changes: 2 additions & 1 deletion 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 @@ -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 {}
})
}

Expand Down
8 changes: 4 additions & 4 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)]
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,
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
127 changes: 52 additions & 75 deletions crates/bevy_ecs/src/event/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<EventWrapperComponent<Self>>())
}

/// 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<EventKey> {
world
.component_id::<EventWrapperComponent<Self>>()
.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,
/// }
Expand All @@ -46,7 +77,7 @@ use core::{
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # #[derive(BroadcastEvent)]
/// # struct Speak {
/// # message: String,
/// # }
Expand All @@ -63,86 +94,36 @@ use core::{
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # #[derive(BroadcastEvent)]
/// # 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)]`"
)]
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::<EventWrapperComponent<Self>>())
}

/// 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<EventKey> {
world
.component_id::<EventWrapperComponent<Self>>()
.map(EventKey)
}
}
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.
///
/// Entity events must be thread-safe.
///
/// # 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`.
///
Expand Down Expand Up @@ -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`",
Expand Down
22 changes: 19 additions & 3 deletions crates/bevy_ecs/src/event/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
//! Event handling types.
//! Events are things that "happen" and can be processed by app logic.
//!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really excellent docs!

//! - [`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;
Expand All @@ -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;
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
Loading
Loading