Make BroadcastEvent and EntityEvent mutually exclusive

This commit is contained in:
Tim Blackbird 2025-07-15 20:18:01 +02:00
parent 9154556d97
commit bd812cd9e0
3 changed files with 76 additions and 15 deletions

View File

@ -30,8 +30,9 @@ pub fn derive_broadcast_event(input: TokenStream) -> TokenStream {
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
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 {} type Kind = #bevy_ecs_path::event::BroadcastEventKind;
}
}) })
} }
@ -74,10 +75,8 @@ pub fn derive_entity_event(input: TokenStream) -> TokenStream {
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
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::EntityEvent for #struct_name #type_generics #where_clause { type Kind = #bevy_ecs_path::event::EntityEventKind<Self, #traversal, #auto_propagate>;
type Traversal = #traversal;
const AUTO_PROPAGATE: bool = #auto_propagate;
} }
}) })
} }

View File

@ -11,7 +11,7 @@ use core::{
marker::PhantomData, marker::PhantomData,
}; };
// TODO: Adjust these docs // TODO: These docs need to be moved around and adjusted
/// 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),
@ -36,7 +36,7 @@ use core::{
/// ``` /// ```
/// # use bevy_ecs::prelude::*; /// # use bevy_ecs::prelude::*;
/// # /// #
/// #[derive(Event)] /// #[derive(BroadcastEvent)]
/// struct Speak { /// struct Speak {
/// message: String, /// message: String,
/// } /// }
@ -47,7 +47,7 @@ use core::{
/// ``` /// ```
/// # use bevy_ecs::prelude::*; /// # use bevy_ecs::prelude::*;
/// # /// #
/// # #[derive(Event)] /// # #[derive(BroadcastEvent)]
/// # struct Speak { /// # struct Speak {
/// # message: String, /// # message: String,
/// # } /// # }
@ -64,7 +64,7 @@ use core::{
/// ``` /// ```
/// # use bevy_ecs::prelude::*; /// # use bevy_ecs::prelude::*;
/// # /// #
/// # #[derive(Event)] /// # #[derive(BroadcastEvent)]
/// # struct Speak { /// # struct Speak {
/// # message: String, /// # message: String,
/// # } /// # }
@ -90,11 +90,14 @@ 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 `ObserverEvent`", message = "`{Self}` is not an `Event`",
label = "invalid `ObserverEvent`", label = "invalid `Event`",
note = "consider annotating `{Self}` with `#[derive(BroadcastEvent)]` or `#[derive(EntityEvent)]`" note = "consider annotating `{Self}` with `#[derive(BroadcastEvent)]` or `#[derive(EntityEvent)]`"
)] )]
pub trait Event: Send + Sync + 'static { pub trait Event: Send + Sync + 'static {
///
type Kind;
/// Generates the [`EventKey`] for this event type. /// Generates the [`EventKey`] for this event type.
/// ///
/// If this type has already been registered, /// If this type has already been registered,
@ -130,7 +133,32 @@ pub trait Event: Send + Sync + 'static {
} }
/// A global [`Event`] without an entity target. /// A global [`Event`] without an entity target.
pub trait BroadcastEvent: Event {} #[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. /// An [`Event`] that can be targeted at specific entities.
/// ///
@ -248,7 +276,7 @@ pub trait BroadcastEvent: Event {}
label = "invalid `EntityEvent`", label = "invalid `EntityEvent`",
note = "consider annotating `{Self}` with `#[derive(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. /// The component that describes which [`Entity`] to propagate this event to next, when [propagation] is enabled.
/// ///
/// [`Entity`]: crate::entity::Entity /// [`Entity`]: crate::entity::Entity
@ -263,6 +291,37 @@ pub trait EntityEvent: Event {
const AUTO_PROPAGATE: bool = false; 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. /// A buffered event for pull-based event handling.
/// ///
/// Buffered events can be written with [`EventWriter`] and read using the [`EventReader`] system parameter. /// Buffered events can be written with [`EventWriter`] and read using the [`EventReader`] system parameter.

View File

@ -11,7 +11,10 @@ mod update;
mod writer; mod writer;
pub(crate) use base::EventInstance; pub(crate) use base::EventInstance;
pub use base::{BroadcastEvent, BufferedEvent, EntityEvent, Event, EventId, EventKey}; pub use base::{
BroadcastEvent, BroadcastEventKind, BufferedEvent, EntityEvent, EntityEventKind, Event,
EventId, EventKey,
};
pub use bevy_ecs_macros::{BroadcastEvent, BufferedEvent, EntityEvent}; 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};