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..786cc5020fd83 100644 --- a/crates/bevy_ecs/src/event/mod.rs +++ b/crates/bevy_ecs/src/event/mod.rs @@ -284,11 +284,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 +969,132 @@ 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 { + entity: MutableEntitoid, + } + + let mut world = World::new(); + let entity = world.spawn_empty().id(); + + world.entity_mut(entity).trigger(A); + world.entity_mut(entity).trigger(AP); + + // Lame :( + world.entity_mut(entity).trigger(|entity| B { entity }); + world.entity_mut(entity).trigger(|entity| BP { entity }); + world + .entity_mut(entity) + .trigger(|entity| C { target: entity }); + world + .entity_mut(entity) + .trigger(|entity| CP { target: entity }); + world + .entity_mut(entity) + .trigger(|entity| D(Entitoid(entity))); + world.entity_mut(entity).trigger(|entity| E { + entity: Entitoid(entity), + }); + world.entity_mut(entity).trigger(|entity| F { + target: Entitoid(entity), + }); + world.entity_mut(entity).trigger(|entity| G { + entity: MutableEntitoid(entity), + }); + + // 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`