diff --git a/crates/bevy_ecs/macros/src/event.rs b/crates/bevy_ecs/macros/src/event.rs index 5137dfe6f408b..90aaff50de0ba 100644 --- a/crates/bevy_ecs/macros/src/event.rs +++ b/crates/bevy_ecs/macros/src/event.rs @@ -142,6 +142,19 @@ pub fn derive_entity_event(input: TokenStream) -> TokenStream { } else { quote! {#bevy_ecs_path::event::EntityTrigger} }; + + let set_entity_event_target_impl = if propagate { + quote! { + impl #impl_generics #bevy_ecs_path::event::SetEntityEventTarget for #struct_name #type_generics #where_clause { + fn set_event_target(&mut self, entity: #bevy_ecs_path::entity::Entity) { + self.#entity_field = Into::into(entity); + } + } + } + } else { + quote! {} + }; + TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause { type Trigger<'a> = #trigger; @@ -149,14 +162,11 @@ pub fn derive_entity_event(input: TokenStream) -> TokenStream { impl #impl_generics #bevy_ecs_path::event::EntityEvent for #struct_name #type_generics #where_clause { fn event_target(&self) -> #bevy_ecs_path::entity::Entity { - self.#entity_field - } - - fn event_target_mut(&mut self) -> &mut #bevy_ecs_path::entity::Entity { - &mut self.#entity_field + #bevy_ecs_path::entity::ContainsEntity::entity(&self.#entity_field) } } + #set_entity_event_target_impl }) } diff --git a/crates/bevy_ecs/src/event/mod.rs b/crates/bevy_ecs/src/event/mod.rs index b7fac6696bd5b..ffbab7f4227b4 100644 --- a/crates/bevy_ecs/src/event/mod.rs +++ b/crates/bevy_ecs/src/event/mod.rs @@ -135,6 +135,33 @@ pub trait Event: Send + Sync + Sized + 'static { /// struct Explode(#[event_target] Entity); /// ``` /// +/// You may also use any type which implements [`ContainsEntity`](crate::entity::ContainsEntity) as the event target: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// struct Bomb(Entity); +/// +/// impl ContainsEntity for Bomb { +/// fn entity(&self) -> Entity { +/// self.0 +/// } +/// } +/// +/// #[derive(EntityEvent)] +/// struct Explode(Bomb); +/// ``` +/// +/// By default, an [`EntityEvent`] is immutable. This means the event data, including the target, does not change while the event +/// is triggered. However, to support event propagation, your event must also implement the [`SetEntityEventTarget`] trait. +/// +/// This trait is automatically implemented for you if you enable event propagation: +/// ``` +/// # use bevy_ecs::prelude::*; +/// #[derive(EntityEvent)] +/// #[entity_event(propagate)] +/// struct Explode(Entity); +/// ``` +/// /// ## Trigger Behavior /// /// When derived, [`EntityEvent`] defaults to setting [`Event::Trigger`] to [`EntityTrigger`], which will run all normal "untargeted" @@ -284,11 +311,21 @@ pub trait Event: Send + Sync + Sized + 'static { pub trait EntityEvent: Event { /// The [`Entity`] "target" of this [`EntityEvent`]. When triggered, this will run observers that watch for this specific entity. fn event_target(&self) -> Entity; - /// Returns a mutable reference to the [`Entity`] "target" of this [`EntityEvent`]. When triggered, this will run observers that watch for this specific entity. +} + +/// A trait which is used to set the target of an [`EntityEvent`]. +/// +/// By default, entity events are immutable; meaning their target does not change during the lifetime of the event. However, some events +/// may require mutable access to provide features such as event propagation. +/// +/// You should never need to implement this trait manually if you use `#[derive(EntityEvent)]`. It is automatically implemented for you if you +/// use `#[entity_event(propagate)]`. +pub trait SetEntityEventTarget: EntityEvent { + /// Sets the [`Entity`] "target" of this [`EntityEvent`]. When triggered, this will run observers that watch for this specific entity. /// - /// Note: In general, this should not be mutated from within an [`Observer`](crate::observer::Observer), as this will not "retarget" + /// Note: In general, this should not be called from within an [`Observer`](crate::observer::Observer), as this will not "retarget" /// the event in any of Bevy's built-in [`Trigger`] implementations. - fn event_target_mut(&mut self) -> &mut Entity; + fn set_event_target(&mut self, entity: Entity); } impl World { @@ -959,4 +996,127 @@ mod tests { }); schedule.run(&mut world); } + + #[test] + fn test_derive_entity_event() { + use bevy_ecs::prelude::*; + + struct Entitoid(Entity); + + impl ContainsEntity for Entitoid { + fn entity(&self) -> Entity { + self.0 + } + } + + struct MutableEntitoid(Entity); + + impl ContainsEntity for MutableEntitoid { + fn entity(&self) -> Entity { + self.0 + } + } + + impl From for MutableEntitoid { + fn from(value: Entity) -> Self { + Self(value) + } + } + + #[derive(EntityEvent)] + struct A(Entity); + + #[derive(EntityEvent)] + #[entity_event(propagate)] + struct AP(Entity); + + #[derive(EntityEvent)] + struct B { + entity: Entity, + } + + #[derive(EntityEvent)] + #[entity_event(propagate)] + struct BP { + entity: Entity, + } + + #[derive(EntityEvent)] + struct C { + #[event_target] + target: Entity, + } + + #[derive(EntityEvent)] + #[entity_event(propagate)] + struct CP { + #[event_target] + target: Entity, + } + + #[derive(EntityEvent)] + struct D(Entitoid); + + // SHOULD NOT COMPILE: + // #[derive(EntityEvent)] + // #[entity_event(propagate)] + // struct DP(Entitoid); + + #[derive(EntityEvent)] + struct E { + entity: Entitoid, + } + + // SHOULD NOT COMPILE: + // #[derive(EntityEvent)] + // #[entity_event(propagate)] + // struct EP { + // entity: Entitoid, + // } + + #[derive(EntityEvent)] + struct F { + #[event_target] + target: Entitoid, + } + + // SHOULD NOT COMPILE: + // #[derive(EntityEvent)] + // #[entity_event(propagate)] + // struct FP { + // #[event_target] + // target: Entitoid, + // } + + #[derive(EntityEvent)] + #[entity_event(propagate)] + struct G(MutableEntitoid); + + impl From for G { + fn from(value: Entity) -> Self { + Self(value.into()) + } + } + + let mut world = World::new(); + let entity = world.spawn_empty().id(); + + world.entity_mut(entity).trigger(A); + world.entity_mut(entity).trigger(AP); + world.trigger(B { entity }); + world.trigger(BP { entity }); + world.trigger(C { target: entity }); + world.trigger(CP { target: entity }); + world.trigger(D(Entitoid(entity))); + world.trigger(E { + entity: Entitoid(entity), + }); + world.trigger(F { + target: Entitoid(entity), + }); + world.trigger(G(MutableEntitoid(entity))); + world.entity_mut(entity).trigger(G::from); + + // No asserts; test just needs to compile + } } diff --git a/crates/bevy_ecs/src/event/trigger.rs b/crates/bevy_ecs/src/event/trigger.rs index 9492380b63159..69aac4a513793 100644 --- a/crates/bevy_ecs/src/event/trigger.rs +++ b/crates/bevy_ecs/src/event/trigger.rs @@ -1,3 +1,4 @@ +use crate::event::SetEntityEventTarget; use crate::{ component::ComponentId, entity::Entity, @@ -267,7 +268,7 @@ impl> fmt::Debug // - `E`'s [`Event::Trigger`] is constrained to [`PropagateEntityTrigger`] unsafe impl< const AUTO_PROPAGATE: bool, - E: EntityEvent + for<'a> Event = Self>, + E: EntityEvent + SetEntityEventTarget + for<'a> Event = Self>, T: Traversal, > Trigger for PropagateEntityTrigger { @@ -309,7 +310,7 @@ unsafe impl< break; } - *event.event_target_mut() = current_entity; + event.set_event_target(current_entity); // SAFETY: // - `observers` come from `world` and match the event type `E`, enforced by the call to `trigger` // - the passed in event pointer comes from `event`, which is an `Event` diff --git a/release-content/migration-guides/immutable-entity-events.md b/release-content/migration-guides/immutable-entity-events.md new file mode 100644 index 0000000000000..3d1e8b162149f --- /dev/null +++ b/release-content/migration-guides/immutable-entity-events.md @@ -0,0 +1,11 @@ +--- +title: "Immutable Entity Events" +pull_requests: [21408] +--- + +The mutable methods of `EntityEvent` (`EntityEvent::from` and `EntityEvent::event_target_mut`) +have been moved to a separate trait: `SetEntityEventTarget` + +This makes all `EntityEvents` immutable by default. + +`SetEntityEventTarget` is implemented automatically for propagated events (e.g. `#[entity_event(propagate)]`).