Skip to content

Commit f991d79

Browse files
authored
Refactor EntityEvent to support ContainsEntity, unlocking the use of kinded entities with observers (#21408)
This is related to #21384 # Objective The goal of this change is to enable `EntityEvent` to work with any Entity-like type which implements `ContainsEntity`. ## Solution - Change return type of `EntityEvent::event_target` from `Entity` to `&impl ContainsEntity` - Change all appropriate call sites to use `event_target().entity()` instead of `event_target()` - Refactor `EntityEvent::event_target_mut` into `EntityEvent::set_event_target` I'm not fully happy with this solution yet, but I'm opening the PR to discuss the options from here. Mainly the issue revolves around `set_event_target`. To make this work, we need the underlying type to be constructible from an entity. This may not always be safe from user's perspective (it wouldn't be with `Instance<T>` for example). Based on my understanding of the system, an event's target is only mutated in the case of event propagation, which makes sense. To work around this, I propose that we flag `EntityEvents` as **immutable** or not; or "not propagatable" or not (I'm open to other terminology! :P). This would allow us to implement `set_event_target` as `unreachable()!` and throw an error instead if the user tries to propagate such an event. We could even maybe enforce this compile time but it'll require additional complexity (mostly in form of different permutations of `trigger_*` method family). ## Testing - Added `test_derive_entity_event` to cover all permutations
1 parent 9ab3440 commit f991d79

File tree

4 files changed

+192
-10
lines changed

4 files changed

+192
-10
lines changed

crates/bevy_ecs/macros/src/event.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,21 +134,31 @@ pub fn derive_entity_event(input: TokenStream) -> TokenStream {
134134
} else {
135135
quote! {#bevy_ecs_path::event::EntityTrigger}
136136
};
137+
138+
let set_entity_event_target_impl = if propagate {
139+
quote! {
140+
impl #impl_generics #bevy_ecs_path::event::SetEntityEventTarget for #struct_name #type_generics #where_clause {
141+
fn set_event_target(&mut self, entity: #bevy_ecs_path::entity::Entity) {
142+
self.#entity_field = Into::into(entity);
143+
}
144+
}
145+
}
146+
} else {
147+
quote! {}
148+
};
149+
137150
TokenStream::from(quote! {
138151
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {
139152
type Trigger<'a> = #trigger;
140153
}
141154

142155
impl #impl_generics #bevy_ecs_path::event::EntityEvent for #struct_name #type_generics #where_clause {
143156
fn event_target(&self) -> #bevy_ecs_path::entity::Entity {
144-
self.#entity_field
145-
}
146-
147-
fn event_target_mut(&mut self) -> &mut #bevy_ecs_path::entity::Entity {
148-
&mut self.#entity_field
157+
#bevy_ecs_path::entity::ContainsEntity::entity(&self.#entity_field)
149158
}
150159
}
151160

161+
#set_entity_event_target_impl
152162
})
153163
}
154164

crates/bevy_ecs/src/event/mod.rs

Lines changed: 163 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,33 @@ pub trait Event: Send + Sync + Sized + 'static {
135135
/// struct Explode(#[event_target] Entity);
136136
/// ```
137137
///
138+
/// You may also use any type which implements [`ContainsEntity`](crate::entity::ContainsEntity) as the event target:
139+
///
140+
/// ```
141+
/// # use bevy_ecs::prelude::*;
142+
/// struct Bomb(Entity);
143+
///
144+
/// impl ContainsEntity for Bomb {
145+
/// fn entity(&self) -> Entity {
146+
/// self.0
147+
/// }
148+
/// }
149+
///
150+
/// #[derive(EntityEvent)]
151+
/// struct Explode(Bomb);
152+
/// ```
153+
///
154+
/// By default, an [`EntityEvent`] is immutable. This means the event data, including the target, does not change while the event
155+
/// is triggered. However, to support event propagation, your event must also implement the [`SetEntityEventTarget`] trait.
156+
///
157+
/// This trait is automatically implemented for you if you enable event propagation:
158+
/// ```
159+
/// # use bevy_ecs::prelude::*;
160+
/// #[derive(EntityEvent)]
161+
/// #[entity_event(propagate)]
162+
/// struct Explode(Entity);
163+
/// ```
164+
///
138165
/// ## Trigger Behavior
139166
///
140167
/// 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 {
284311
pub trait EntityEvent: Event {
285312
/// The [`Entity`] "target" of this [`EntityEvent`]. When triggered, this will run observers that watch for this specific entity.
286313
fn event_target(&self) -> Entity;
287-
/// Returns a mutable reference to the [`Entity`] "target" of this [`EntityEvent`]. When triggered, this will run observers that watch for this specific entity.
314+
}
315+
316+
/// A trait which is used to set the target of an [`EntityEvent`].
317+
///
318+
/// By default, entity events are immutable; meaning their target does not change during the lifetime of the event. However, some events
319+
/// may require mutable access to provide features such as event propagation.
320+
///
321+
/// You should never need to implement this trait manually if you use `#[derive(EntityEvent)]`. It is automatically implemented for you if you
322+
/// use `#[entity_event(propagate)]`.
323+
pub trait SetEntityEventTarget: EntityEvent {
324+
/// Sets the [`Entity`] "target" of this [`EntityEvent`]. When triggered, this will run observers that watch for this specific entity.
288325
///
289-
/// Note: In general, this should not be mutated from within an [`Observer`](crate::observer::Observer), as this will not "retarget"
326+
/// Note: In general, this should not be called from within an [`Observer`](crate::observer::Observer), as this will not "retarget"
290327
/// the event in any of Bevy's built-in [`Trigger`] implementations.
291-
fn event_target_mut(&mut self) -> &mut Entity;
328+
fn set_event_target(&mut self, entity: Entity);
292329
}
293330

294331
impl World {
@@ -931,4 +968,127 @@ mod tests {
931968
});
932969
schedule.run(&mut world);
933970
}
971+
972+
#[test]
973+
fn test_derive_entity_event() {
974+
use bevy_ecs::prelude::*;
975+
976+
struct Entitoid(Entity);
977+
978+
impl ContainsEntity for Entitoid {
979+
fn entity(&self) -> Entity {
980+
self.0
981+
}
982+
}
983+
984+
struct MutableEntitoid(Entity);
985+
986+
impl ContainsEntity for MutableEntitoid {
987+
fn entity(&self) -> Entity {
988+
self.0
989+
}
990+
}
991+
992+
impl From<Entity> for MutableEntitoid {
993+
fn from(value: Entity) -> Self {
994+
Self(value)
995+
}
996+
}
997+
998+
#[derive(EntityEvent)]
999+
struct A(Entity);
1000+
1001+
#[derive(EntityEvent)]
1002+
#[entity_event(propagate)]
1003+
struct AP(Entity);
1004+
1005+
#[derive(EntityEvent)]
1006+
struct B {
1007+
entity: Entity,
1008+
}
1009+
1010+
#[derive(EntityEvent)]
1011+
#[entity_event(propagate)]
1012+
struct BP {
1013+
entity: Entity,
1014+
}
1015+
1016+
#[derive(EntityEvent)]
1017+
struct C {
1018+
#[event_target]
1019+
target: Entity,
1020+
}
1021+
1022+
#[derive(EntityEvent)]
1023+
#[entity_event(propagate)]
1024+
struct CP {
1025+
#[event_target]
1026+
target: Entity,
1027+
}
1028+
1029+
#[derive(EntityEvent)]
1030+
struct D(Entitoid);
1031+
1032+
// SHOULD NOT COMPILE:
1033+
// #[derive(EntityEvent)]
1034+
// #[entity_event(propagate)]
1035+
// struct DP(Entitoid);
1036+
1037+
#[derive(EntityEvent)]
1038+
struct E {
1039+
entity: Entitoid,
1040+
}
1041+
1042+
// SHOULD NOT COMPILE:
1043+
// #[derive(EntityEvent)]
1044+
// #[entity_event(propagate)]
1045+
// struct EP {
1046+
// entity: Entitoid,
1047+
// }
1048+
1049+
#[derive(EntityEvent)]
1050+
struct F {
1051+
#[event_target]
1052+
target: Entitoid,
1053+
}
1054+
1055+
// SHOULD NOT COMPILE:
1056+
// #[derive(EntityEvent)]
1057+
// #[entity_event(propagate)]
1058+
// struct FP {
1059+
// #[event_target]
1060+
// target: Entitoid,
1061+
// }
1062+
1063+
#[derive(EntityEvent)]
1064+
#[entity_event(propagate)]
1065+
struct G(MutableEntitoid);
1066+
1067+
impl From<Entity> for G {
1068+
fn from(value: Entity) -> Self {
1069+
Self(value.into())
1070+
}
1071+
}
1072+
1073+
let mut world = World::new();
1074+
let entity = world.spawn_empty().id();
1075+
1076+
world.entity_mut(entity).trigger(A);
1077+
world.entity_mut(entity).trigger(AP);
1078+
world.trigger(B { entity });
1079+
world.trigger(BP { entity });
1080+
world.trigger(C { target: entity });
1081+
world.trigger(CP { target: entity });
1082+
world.trigger(D(Entitoid(entity)));
1083+
world.trigger(E {
1084+
entity: Entitoid(entity),
1085+
});
1086+
world.trigger(F {
1087+
target: Entitoid(entity),
1088+
});
1089+
world.trigger(G(MutableEntitoid(entity)));
1090+
world.entity_mut(entity).trigger(G::from);
1091+
1092+
// No asserts; test just needs to compile
1093+
}
9341094
}

crates/bevy_ecs/src/event/trigger.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::event::SetEntityEventTarget;
12
use crate::{
23
component::ComponentId,
34
entity::Entity,
@@ -267,7 +268,7 @@ impl<const AUTO_PROPAGATE: bool, E: EntityEvent, T: Traversal<E>> fmt::Debug
267268
// - `E`'s [`Event::Trigger`] is constrained to [`PropagateEntityTrigger<E>`]
268269
unsafe impl<
269270
const AUTO_PROPAGATE: bool,
270-
E: EntityEvent + for<'a> Event<Trigger<'a> = Self>,
271+
E: EntityEvent + SetEntityEventTarget + for<'a> Event<Trigger<'a> = Self>,
271272
T: Traversal<E>,
272273
> Trigger<E> for PropagateEntityTrigger<AUTO_PROPAGATE, E, T>
273274
{
@@ -309,7 +310,7 @@ unsafe impl<
309310
break;
310311
}
311312

312-
*event.event_target_mut() = current_entity;
313+
event.set_event_target(current_entity);
313314
// SAFETY:
314315
// - `observers` come from `world` and match the event type `E`, enforced by the call to `trigger`
315316
// - the passed in event pointer comes from `event`, which is an `Event`
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: "Immutable Entity Events"
3+
pull_requests: [21408]
4+
---
5+
6+
The mutable methods of `EntityEvent` (`EntityEvent::from` and `EntityEvent::event_target_mut`)
7+
have been moved to a separate trait: `SetEntityEventTarget`
8+
9+
This makes all `EntityEvents` immutable by default.
10+
11+
`SetEntityEventTarget` is implemented automatically for propagated events (e.g. `#[entity_event(propagate)]`).

0 commit comments

Comments
 (0)