From c4d4522950a6a0ec836e4d3bf000e8d04446ce4b Mon Sep 17 00:00:00 2001 From: Christian Hughes Date: Sun, 13 Oct 2024 03:49:08 -0500 Subject: [PATCH] add fully dynamic observer triggering --- crates/bevy_ecs/src/observer/mod.rs | 124 +++++++++++++++++- crates/bevy_ecs/src/observer/trigger_event.rs | 53 ++++++-- crates/bevy_ecs/src/world/deferred_world.rs | 17 ++- 3 files changed, 173 insertions(+), 21 deletions(-) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 5e87a9d01ce60..6e44c54f57b28 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -14,9 +14,10 @@ use crate::{ observer::entity_observer::ObservedBy, prelude::*, system::IntoObserverSystem, + traversal::Traversal, world::{DeferredWorld, *}, }; -use bevy_ptr::Ptr; +use bevy_ptr::{Ptr, PtrMut}; use bevy_utils::HashMap; use core::{ fmt::Debug, @@ -258,12 +259,12 @@ impl Observers { } /// This will run the observers of the given `event_type`, targeting the given `entity` and `components`. - pub(crate) fn invoke( + pub(crate) fn invoke( mut world: DeferredWorld, event_type: ComponentId, entity: Entity, components: impl Iterator, - data: &mut T, + mut data: PtrMut<'_>, propagate: &mut bool, ) { // SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld` @@ -287,7 +288,7 @@ impl Observers { event_type, entity, }, - data.into(), + data.reborrow(), propagate, ); }; @@ -410,6 +411,25 @@ impl World { TriggerEvent { event, targets: () }.trigger_ref(self); } + /// Triggers the given type-erased mutable reference as an [`Event`], which + /// will run any [`Observer`]s watching for it. + /// + /// **This method should only be used when the event type is not known at + /// compile time.** Otherwise, prefer [`World::trigger_ref`]. + /// + /// # Safety + /// + /// Caller must ensure that the passed `event_data` matches the type + /// represented by the `event_type` [`ComponentId`]. + pub unsafe fn trigger_ref_dynamic( + &mut self, + event_type: ComponentId, + event_data: PtrMut<'_>, + auto_propagate: bool, + ) { + trigger_event::(self, event_type, event_data, (), auto_propagate); + } + /// Triggers the given [`Event`] for the given `targets`, which will run any [`Observer`]s watching for it. /// /// While event types commonly implement [`Copy`], @@ -428,6 +448,26 @@ impl World { TriggerEvent { event, targets }.trigger_ref(self); } + /// Triggers the given type-erased mutable reference as an [`Event`] for + /// the given `targets`, which will run any [`Observer`]s watching for it. + /// + /// **This method should only be used when the event type is not known at + /// compile time.** Otherwise, prefer [`World::trigger_targets_ref`]. + /// + /// # Safety + /// + /// Caller must ensure that the passed `event_data` matches the type + /// represented by the `event_type` [`ComponentId`]. + pub unsafe fn trigger_targets_ref_dynamic( + &mut self, + event_type: ComponentId, + event_data: PtrMut<'_>, + targets: impl TriggerTargets, + auto_propagate: bool, + ) { + trigger_event::(self, event_type, event_data, targets, auto_propagate); + } + /// Register an observer to the cache, called when an observer is created pub(crate) fn register_observer(&mut self, observer_entity: Entity) { // SAFETY: References do not alias. @@ -551,7 +591,7 @@ impl World { mod tests { use alloc::vec; - use bevy_ptr::OwningPtr; + use bevy_ptr::{OwningPtr, PtrMut}; use crate as bevy_ecs; use crate::{ @@ -781,6 +821,80 @@ mod tests { assert_eq!(5, event.counter); } + #[test] + fn observer_trigger_ref_dynamic() { + #[derive(Resource, Default)] + struct Counter(usize); + + let mut world = World::new(); + world.init_resource::(); + let event_type = world.register_component::(); + world.add_observer( + |trigger: Trigger, mut counter: ResMut| { + counter.0 += trigger.event().counter; + }, + ); + world.flush(); + + // SAFETY: event_type was fetched via register_component + unsafe { + world.trigger_ref_dynamic::<()>( + event_type, + PtrMut::from(&mut EventWithData { counter: 636 }), + false, + ); + world.trigger_ref_dynamic::<()>( + event_type, + PtrMut::from(&mut EventWithData { counter: 714 }), + false, + ); + world.trigger_ref_dynamic::<()>( + event_type, + PtrMut::from(&mut EventWithData { counter: 146 }), + false, + ); + } + world.flush(); + + assert_eq!(636 + 714 + 146, world.resource::().0); + } + + #[test] + fn observer_trigger_targets_ref_dynamic() { + let mut world = World::new(); + world.init_resource::(); + let event_type = world.register_component::(); + + let parent = world + .spawn_empty() + .observe(|_: Trigger, mut res: ResMut| { + res.observed("parent"); + }) + .id(); + + let child = world + .spawn(Parent(parent)) + .observe(|_: Trigger, mut res: ResMut| { + res.observed("child"); + }) + .id(); + + // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut + // and therefore does not automatically flush. + world.flush(); + // SAFETY: event_type was fetched via register_component + unsafe { + world.trigger_targets_ref_dynamic::<&'static Parent>( + event_type, + PtrMut::from(&mut EventPropagating), + child, + true, + ); + } + world.flush(); + assert_eq!(vec!["child", "parent"], world.resource::().0); + } + #[test] fn observer_multiple_listeners() { let mut world = World::new(); diff --git a/crates/bevy_ecs/src/observer/trigger_event.rs b/crates/bevy_ecs/src/observer/trigger_event.rs index 5221aff070e2e..feeb31bac3c2e 100644 --- a/crates/bevy_ecs/src/observer/trigger_event.rs +++ b/crates/bevy_ecs/src/observer/trigger_event.rs @@ -1,7 +1,10 @@ +use bevy_ptr::PtrMut; + use crate::{ component::ComponentId, entity::Entity, event::Event, + traversal::Traversal, world::{Command, DeferredWorld, World}, }; @@ -17,14 +20,32 @@ pub struct TriggerEvent { impl TriggerEvent { pub(super) fn trigger(mut self, world: &mut World) { let event_type = world.register_component::(); - trigger_event(world, event_type, &mut self.event, self.targets); + // SAFETY: `event_type` was fetched based on the type of `E` + unsafe { + trigger_event::( + world, + event_type, + PtrMut::from(&mut self.event), + self.targets, + E::AUTO_PROPAGATE, + ); + } } } impl TriggerEvent<&mut E, Targets> { pub(super) fn trigger_ref(self, world: &mut World) { let event_type = world.register_component::(); - trigger_event(world, event_type, self.event, self.targets); + // SAFETY: `event_type` was fetched based on the type of `E` + unsafe { + trigger_event::( + world, + event_type, + PtrMut::from(self.event), + self.targets, + E::AUTO_PROPAGATE, + ); + } } } @@ -60,22 +81,36 @@ impl Command for EmitDynamicTrigger { fn apply(mut self, world: &mut World) { - trigger_event(world, self.event_type, &mut self.event_data, self.targets); + // SAFETY: Self::new_with_id requires the caller to uphold that the component associated with `event_type` is accessible as E + unsafe { + trigger_event::( + world, + self.event_type, + PtrMut::from(&mut self.event_data), + self.targets, + E::AUTO_PROPAGATE, + ); + } } } +/// # Safety +/// +/// Caller must ensure `event_data` matches the type represented by the `event_type` [`ComponentId`]. #[inline] -fn trigger_event( +pub(crate) unsafe fn trigger_event( world: &mut World, event_type: ComponentId, - event_data: &mut E, + mut event_data: PtrMut<'_>, targets: Targets, + auto_propagate: bool, ) { let mut world = DeferredWorld::from(world); + if targets.entities().is_empty() { // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` unsafe { - world.trigger_observers_with_data::<_, E::Traversal>( + world.trigger_observers_with_data::( event_type, Entity::PLACEHOLDER, targets.components(), @@ -87,12 +122,12 @@ fn trigger_event( for target in targets.entities() { // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` unsafe { - world.trigger_observers_with_data::<_, E::Traversal>( + world.trigger_observers_with_data::( event_type, *target, targets.components(), - event_data, - E::AUTO_PROPAGATE, + event_data.reborrow(), + auto_propagate, ); }; } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 45e7a7cf873ee..72fb08d166c0a 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -1,5 +1,7 @@ use core::ops::Deref; +use bevy_ptr::PtrMut; + use crate::{ archetype::Archetype, change_detection::MutUntyped, @@ -503,12 +505,12 @@ impl<'w> DeferredWorld<'w> { entity: Entity, components: impl Iterator, ) { - Observers::invoke::<_>( + Observers::invoke( self.reborrow(), event, entity, components, - &mut (), + PtrMut::from(&mut ()), &mut false, ); } @@ -516,25 +518,26 @@ impl<'w> DeferredWorld<'w> { /// Triggers all event observers for [`ComponentId`] in target. /// /// # Safety - /// Caller must ensure `E` is accessible as the type represented by `event` + /// + /// Caller must ensure `data` matches the type represented by the `event` [`ComponentId`]. #[inline] - pub(crate) unsafe fn trigger_observers_with_data( + pub(crate) unsafe fn trigger_observers_with_data( &mut self, event: ComponentId, mut entity: Entity, components: &[ComponentId], - data: &mut E, + mut data: PtrMut<'_>, mut propagate: bool, ) where T: Traversal, { loop { - Observers::invoke::<_>( + Observers::invoke( self.reborrow(), event, entity, components.iter().copied(), - data, + data.reborrow(), &mut propagate, ); if !propagate {