Skip to content

Commit

Permalink
add fully dynamic observer triggering
Browse files Browse the repository at this point in the history
  • Loading branch information
ItsDoot committed Oct 13, 2024
1 parent 813c759 commit c4d4522
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 21 deletions.
124 changes: 119 additions & 5 deletions crates/bevy_ecs/src/observer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<T>(
pub(crate) fn invoke(
mut world: DeferredWorld,
event_type: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
data: &mut T,
mut data: PtrMut<'_>,
propagate: &mut bool,
) {
// SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld`
Expand All @@ -287,7 +288,7 @@ impl Observers {
event_type,
entity,
},
data.into(),
data.reborrow(),
propagate,
);
};
Expand Down Expand Up @@ -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<T: Traversal>(
&mut self,
event_type: ComponentId,
event_data: PtrMut<'_>,
auto_propagate: bool,
) {
trigger_event::<T, _>(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`],
Expand All @@ -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<T: Traversal>(
&mut self,
event_type: ComponentId,
event_data: PtrMut<'_>,
targets: impl TriggerTargets,
auto_propagate: bool,
) {
trigger_event::<T, _>(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.
Expand Down Expand Up @@ -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::{
Expand Down Expand Up @@ -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::<Counter>();
let event_type = world.register_component::<EventWithData>();
world.add_observer(
|trigger: Trigger<EventWithData>, mut counter: ResMut<Counter>| {
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::<Counter>().0);
}

#[test]
fn observer_trigger_targets_ref_dynamic() {
let mut world = World::new();
world.init_resource::<Order>();
let event_type = world.register_component::<EventPropagating>();

let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("parent");
})
.id();

let child = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
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::<Order>().0);
}

#[test]
fn observer_multiple_listeners() {
let mut world = World::new();
Expand Down
53 changes: 44 additions & 9 deletions crates/bevy_ecs/src/observer/trigger_event.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use bevy_ptr::PtrMut;

use crate::{
component::ComponentId,
entity::Entity,
event::Event,
traversal::Traversal,
world::{Command, DeferredWorld, World},
};

Expand All @@ -17,14 +20,32 @@ pub struct TriggerEvent<E, Targets: TriggerTargets = ()> {
impl<E: Event, Targets: TriggerTargets> TriggerEvent<E, Targets> {
pub(super) fn trigger(mut self, world: &mut World) {
let event_type = world.register_component::<E>();
trigger_event(world, event_type, &mut self.event, self.targets);
// SAFETY: `event_type` was fetched based on the type of `E`
unsafe {
trigger_event::<E::Traversal, _>(
world,
event_type,
PtrMut::from(&mut self.event),
self.targets,
E::AUTO_PROPAGATE,
);
}
}
}

impl<E: Event, Targets: TriggerTargets> TriggerEvent<&mut E, Targets> {
pub(super) fn trigger_ref(self, world: &mut World) {
let event_type = world.register_component::<E>();
trigger_event(world, event_type, self.event, self.targets);
// SAFETY: `event_type` was fetched based on the type of `E`
unsafe {
trigger_event::<E::Traversal, _>(
world,
event_type,
PtrMut::from(self.event),
self.targets,
E::AUTO_PROPAGATE,
);
}
}
}

Expand Down Expand Up @@ -60,22 +81,36 @@ impl<E: Event, Targets: TriggerTargets + Send + Sync + 'static> Command
for EmitDynamicTrigger<E, Targets>
{
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::<E::Traversal, _>(
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<E: Event, Targets: TriggerTargets>(
pub(crate) unsafe fn trigger_event<T: Traversal, Targets: TriggerTargets>(
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::<T>(
event_type,
Entity::PLACEHOLDER,
targets.components(),
Expand All @@ -87,12 +122,12 @@ fn trigger_event<E: Event, Targets: TriggerTargets>(
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::<T>(
event_type,
*target,
targets.components(),
event_data,
E::AUTO_PROPAGATE,
event_data.reborrow(),
auto_propagate,
);
};
}
Expand Down
17 changes: 10 additions & 7 deletions crates/bevy_ecs/src/world/deferred_world.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use core::ops::Deref;

use bevy_ptr::PtrMut;

use crate::{
archetype::Archetype,
change_detection::MutUntyped,
Expand Down Expand Up @@ -503,38 +505,39 @@ impl<'w> DeferredWorld<'w> {
entity: Entity,
components: impl Iterator<Item = ComponentId>,
) {
Observers::invoke::<_>(
Observers::invoke(
self.reborrow(),
event,
entity,
components,
&mut (),
PtrMut::from(&mut ()),
&mut false,
);
}

/// 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<E, T>(
pub(crate) unsafe fn trigger_observers_with_data<T>(
&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 {
Expand Down

0 comments on commit c4d4522

Please sign in to comment.