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();
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 {}
impl #impl_generics #bevy_ecs_path::event::Event 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();
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>;
}
})
}

View File

@ -11,7 +11,7 @@ use core::{
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.
///
/// Events can be triggered on a [`World`] using a method like [`trigger`](World::trigger),
@ -36,7 +36,7 @@ use core::{
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Event)]
/// #[derive(BroadcastEvent)]
/// struct Speak {
/// message: String,
/// }
@ -47,7 +47,7 @@ use core::{
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # #[derive(BroadcastEvent)]
/// # struct Speak {
/// # message: String,
/// # }
@ -64,7 +64,7 @@ use core::{
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # #[derive(BroadcastEvent)]
/// # struct Speak {
/// # message: String,
/// # }
@ -90,11 +90,14 @@ use core::{
/// [`EventReader`]: super::EventReader
/// [`EventWriter`]: super::EventWriter
#[diagnostic::on_unimplemented(
message = "`{Self}` is not an `ObserverEvent`",
label = "invalid `ObserverEvent`",
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 {
///
type Kind;
/// Generates the [`EventKey`] for this event type.
///
/// If this type has already been registered,
@ -130,7 +133,32 @@ pub trait Event: Send + Sync + 'static {
}
/// 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.
///
@ -248,7 +276,7 @@ pub trait BroadcastEvent: Event {}
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
@ -263,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.

View File

@ -11,7 +11,10 @@ mod update;
mod writer;
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};
#[expect(deprecated, reason = "`SendBatchIds` was renamed to `WriteBatchIds`.")]
pub use collections::{Events, SendBatchIds, WriteBatchIds};