mod entity_observer;
mod runner;
mod trigger_event;
pub use runner::*;
pub use trigger_event::*;
use crate::{
archetype::ArchetypeFlags,
component::ComponentId,
entity::EntityHashMap,
observer::entity_observer::ObservedBy,
prelude::*,
system::IntoObserverSystem,
world::{DeferredWorld, *},
};
use bevy_ptr::Ptr;
use bevy_utils::HashMap;
use core::{
fmt::Debug,
marker::PhantomData,
ops::{Deref, DerefMut},
};
use smallvec::SmallVec;
pub struct Trigger<'w, E, B: Bundle = ()> {
event: &'w mut E,
propagate: &'w mut bool,
trigger: ObserverTrigger,
_marker: PhantomData<B>,
}
impl<'w, E, B: Bundle> Trigger<'w, E, B> {
pub fn new(event: &'w mut E, propagate: &'w mut bool, trigger: ObserverTrigger) -> Self {
Self {
event,
propagate,
trigger,
_marker: PhantomData,
}
}
pub fn event_type(&self) -> ComponentId {
self.trigger.event_type
}
pub fn event(&self) -> &E {
self.event
}
pub fn event_mut(&mut self) -> &mut E {
self.event
}
pub fn event_ptr(&self) -> Ptr {
Ptr::from(&self.event)
}
pub fn entity(&self) -> Entity {
self.trigger.entity
}
pub fn components(&self) -> &[ComponentId] {
&self.trigger.components
}
pub fn observer(&self) -> Entity {
self.trigger.observer
}
pub fn propagate(&mut self, should_propagate: bool) {
*self.propagate = should_propagate;
}
pub fn get_propagate(&self) -> bool {
*self.propagate
}
}
impl<'w, E: Debug, B: Bundle> Debug for Trigger<'w, E, B> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Trigger")
.field("event", &self.event)
.field("propagate", &self.propagate)
.field("trigger", &self.trigger)
.field("_marker", &self._marker)
.finish()
}
}
impl<'w, E, B: Bundle> Deref for Trigger<'w, E, B> {
type Target = E;
fn deref(&self) -> &Self::Target {
self.event
}
}
impl<'w, E, B: Bundle> DerefMut for Trigger<'w, E, B> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.event
}
}
#[derive(Default, Clone)]
pub struct ObserverDescriptor {
events: Vec<ComponentId>,
components: Vec<ComponentId>,
entities: Vec<Entity>,
}
impl ObserverDescriptor {
pub unsafe fn with_events(mut self, events: Vec<ComponentId>) -> Self {
self.events = events;
self
}
pub fn with_components(mut self, components: Vec<ComponentId>) -> Self {
self.components = components;
self
}
pub fn with_entities(mut self, entities: Vec<Entity>) -> Self {
self.entities = entities;
self
}
pub(crate) fn merge(&mut self, descriptor: &ObserverDescriptor) {
self.events.extend(descriptor.events.iter().copied());
self.components
.extend(descriptor.components.iter().copied());
self.entities.extend(descriptor.entities.iter().copied());
}
}
#[derive(Debug)]
pub struct ObserverTrigger {
pub observer: Entity,
pub event_type: ComponentId,
components: SmallVec<[ComponentId; 2]>,
pub entity: Entity,
}
impl ObserverTrigger {
pub fn components(&self) -> &[ComponentId] {
&self.components
}
}
type ObserverMap = EntityHashMap<ObserverRunner>;
#[derive(Default, Debug)]
pub struct CachedComponentObservers {
map: ObserverMap,
entity_map: EntityHashMap<ObserverMap>,
}
#[derive(Default, Debug)]
pub struct CachedObservers {
map: ObserverMap,
component_observers: HashMap<ComponentId, CachedComponentObservers>,
entity_observers: EntityHashMap<ObserverMap>,
}
#[derive(Default, Debug)]
pub struct Observers {
on_add: CachedObservers,
on_insert: CachedObservers,
on_replace: CachedObservers,
on_remove: CachedObservers,
cache: HashMap<ComponentId, CachedObservers>,
}
impl Observers {
pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers {
match event_type {
ON_ADD => &mut self.on_add,
ON_INSERT => &mut self.on_insert,
ON_REPLACE => &mut self.on_replace,
ON_REMOVE => &mut self.on_remove,
_ => self.cache.entry(event_type).or_default(),
}
}
pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
match event_type {
ON_ADD => Some(&self.on_add),
ON_INSERT => Some(&self.on_insert),
ON_REPLACE => Some(&self.on_replace),
ON_REMOVE => Some(&self.on_remove),
_ => self.cache.get(&event_type),
}
}
pub(crate) fn invoke<T>(
mut world: DeferredWorld,
event_type: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId> + Clone,
data: &mut T,
propagate: &mut bool,
) {
let (mut world, observers) = unsafe {
let world = world.as_unsafe_world_cell();
world.increment_trigger_id();
let observers = world.observers();
let Some(observers) = observers.try_get_observers(event_type) else {
return;
};
(world.into_deferred(), observers)
};
let trigger_for_components = components.clone();
let mut trigger_observer = |(&observer, runner): (&Entity, &ObserverRunner)| {
(runner)(
world.reborrow(),
ObserverTrigger {
observer,
event_type,
components: components.clone().collect(),
entity,
},
data.into(),
propagate,
);
};
observers.map.iter().for_each(&mut trigger_observer);
if entity != Entity::PLACEHOLDER {
if let Some(map) = observers.entity_observers.get(&entity) {
map.iter().for_each(&mut trigger_observer);
}
}
trigger_for_components.for_each(|id| {
if let Some(component_observers) = observers.component_observers.get(&id) {
component_observers
.map
.iter()
.for_each(&mut trigger_observer);
if entity != Entity::PLACEHOLDER {
if let Some(map) = component_observers.entity_map.get(&entity) {
map.iter().for_each(&mut trigger_observer);
}
}
}
});
}
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
match event_type {
ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
ON_REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER),
ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER),
_ => None,
}
}
pub(crate) fn update_archetype_flags(
&self,
component_id: ComponentId,
flags: &mut ArchetypeFlags,
) {
if self.on_add.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_ADD_OBSERVER);
}
if self
.on_insert
.component_observers
.contains_key(&component_id)
{
flags.insert(ArchetypeFlags::ON_INSERT_OBSERVER);
}
if self
.on_replace
.component_observers
.contains_key(&component_id)
{
flags.insert(ArchetypeFlags::ON_REPLACE_OBSERVER);
}
if self
.on_remove
.component_observers
.contains_key(&component_id)
{
flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER);
}
}
}
impl World {
pub fn add_observer<E: Event, B: Bundle, M>(
&mut self,
system: impl IntoObserverSystem<E, B, M>,
) -> EntityWorldMut {
self.spawn(Observer::new(system))
}
pub fn trigger(&mut self, event: impl Event) {
TriggerEvent { event, targets: () }.trigger(self);
}
pub fn trigger_ref(&mut self, event: &mut impl Event) {
TriggerEvent { event, targets: () }.trigger_ref(self);
}
pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) {
TriggerEvent { event, targets }.trigger(self);
}
pub fn trigger_targets_ref(&mut self, event: &mut impl Event, targets: impl TriggerTargets) {
TriggerEvent { event, targets }.trigger_ref(self);
}
pub(crate) fn register_observer(&mut self, observer_entity: Entity) {
let (observer_state, archetypes, observers) = unsafe {
let observer_state: *const ObserverState =
self.get::<ObserverState>(observer_entity).unwrap();
for watched_entity in &(*observer_state).descriptor.entities {
let mut entity_mut = self.entity_mut(*watched_entity);
let mut observed_by = entity_mut.entry::<ObservedBy>().or_default();
observed_by.0.push(observer_entity);
}
(&*observer_state, &mut self.archetypes, &mut self.observers)
};
let descriptor = &observer_state.descriptor;
for &event_type in &descriptor.events {
let cache = observers.get_observers(event_type);
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
cache.map.insert(observer_entity, observer_state.runner);
} else if descriptor.components.is_empty() {
for &watched_entity in &observer_state.descriptor.entities {
let map = cache.entity_observers.entry(watched_entity).or_default();
map.insert(observer_entity, observer_state.runner);
}
} else {
for &component in &descriptor.components {
let observers =
cache
.component_observers
.entry(component)
.or_insert_with(|| {
if let Some(flag) = Observers::is_archetype_cached(event_type) {
archetypes.update_flags(component, flag, true);
}
CachedComponentObservers::default()
});
if descriptor.entities.is_empty() {
observers.map.insert(observer_entity, observer_state.runner);
} else {
for &watched_entity in &descriptor.entities {
let map = observers.entity_map.entry(watched_entity).or_default();
map.insert(observer_entity, observer_state.runner);
}
}
}
}
}
}
pub(crate) fn unregister_observer(&mut self, entity: Entity, descriptor: ObserverDescriptor) {
let archetypes = &mut self.archetypes;
let observers = &mut self.observers;
for &event_type in &descriptor.events {
let cache = observers.get_observers(event_type);
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
cache.map.remove(&entity);
} else if descriptor.components.is_empty() {
for watched_entity in &descriptor.entities {
let Some(observers) = cache.entity_observers.get_mut(watched_entity) else {
continue;
};
observers.remove(&entity);
if observers.is_empty() {
cache.entity_observers.remove(watched_entity);
}
}
} else {
for component in &descriptor.components {
let Some(observers) = cache.component_observers.get_mut(component) else {
continue;
};
if descriptor.entities.is_empty() {
observers.map.remove(&entity);
} else {
for watched_entity in &descriptor.entities {
let Some(map) = observers.entity_map.get_mut(watched_entity) else {
continue;
};
map.remove(&entity);
if map.is_empty() {
observers.entity_map.remove(watched_entity);
}
}
}
if observers.map.is_empty() && observers.entity_map.is_empty() {
cache.component_observers.remove(component);
if let Some(flag) = Observers::is_archetype_cached(event_type) {
if let Some(by_component) = archetypes.by_component.get(component) {
for archetype in by_component.keys() {
let archetype = &mut archetypes.archetypes[archetype.index()];
if archetype.contains(*component) {
let no_longer_observed = archetype
.components()
.all(|id| !cache.component_observers.contains_key(&id));
if no_longer_observed {
archetype.flags.set(flag, false);
}
}
}
}
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use bevy_ptr::OwningPtr;
use bevy_utils::HashMap;
use crate as bevy_ecs;
use crate::component::ComponentId;
use crate::{
observer::{EmitDynamicTrigger, Observer, ObserverDescriptor, ObserverState, OnReplace},
prelude::*,
traversal::Traversal,
};
#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
#[derive(Component)]
struct C;
#[derive(Component)]
#[component(storage = "SparseSet")]
struct S;
#[derive(Event)]
struct EventA;
#[derive(Event)]
struct EventWithData {
counter: usize,
}
#[derive(Resource, Default)]
struct Order(Vec<&'static str>);
impl Order {
#[track_caller]
fn observed(&mut self, name: &'static str) {
self.0.push(name);
}
}
#[derive(Component)]
struct Parent(Entity);
impl Traversal for &'_ Parent {
fn traverse(item: Self::Item<'_>) -> Option<Entity> {
Some(item.0)
}
}
#[derive(Component)]
struct EventPropagating;
impl Event for EventPropagating {
type Traversal = &'static Parent;
const AUTO_PROPAGATE: bool = true;
}
#[test]
fn observer_order_spawn_despawn() {
let mut world = World::new();
world.init_resource::<Order>();
world.add_observer(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
world
.add_observer(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
world.add_observer(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| {
res.observed("replace");
});
world
.add_observer(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(
vec!["add", "insert", "replace", "remove"],
world.resource::<Order>().0
);
}
#[test]
fn observer_order_insert_remove() {
let mut world = World::new();
world.init_resource::<Order>();
world.add_observer(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
world
.add_observer(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
world.add_observer(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| {
res.observed("replace");
});
world
.add_observer(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
let mut entity = world.spawn_empty();
entity.insert(A);
entity.remove::<A>();
entity.flush();
assert_eq!(
vec!["add", "insert", "replace", "remove"],
world.resource::<Order>().0
);
}
#[test]
fn observer_order_insert_remove_sparse() {
let mut world = World::new();
world.init_resource::<Order>();
world.add_observer(|_: Trigger<OnAdd, S>, mut res: ResMut<Order>| res.observed("add"));
world
.add_observer(|_: Trigger<OnInsert, S>, mut res: ResMut<Order>| res.observed("insert"));
world.add_observer(|_: Trigger<OnReplace, S>, mut res: ResMut<Order>| {
res.observed("replace");
});
world
.add_observer(|_: Trigger<OnRemove, S>, mut res: ResMut<Order>| res.observed("remove"));
let mut entity = world.spawn_empty();
entity.insert(S);
entity.remove::<S>();
entity.flush();
assert_eq!(
vec!["add", "insert", "replace", "remove"],
world.resource::<Order>().0
);
}
#[test]
fn observer_order_replace() {
let mut world = World::new();
world.init_resource::<Order>();
let entity = world.spawn(A).id();
world.add_observer(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
world
.add_observer(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
world.add_observer(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| {
res.observed("replace");
});
world
.add_observer(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
world.flush();
let mut entity = world.entity_mut(entity);
entity.insert(A);
entity.flush();
assert_eq!(vec!["replace", "insert"], world.resource::<Order>().0);
}
#[test]
fn observer_order_recursive() {
let mut world = World::new();
world.init_resource::<Order>();
world.add_observer(
|obs: Trigger<OnAdd, A>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("add_a");
commands.entity(obs.entity()).insert(B);
},
);
world.add_observer(
|obs: Trigger<OnRemove, A>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("remove_a");
commands.entity(obs.entity()).remove::<B>();
},
);
world.add_observer(
|obs: Trigger<OnAdd, B>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("add_b");
commands.entity(obs.entity()).remove::<A>();
},
);
world.add_observer(|_: Trigger<OnRemove, B>, mut res: ResMut<Order>| {
res.observed("remove_b");
});
let entity = world.spawn(A).flush();
let entity = world.get_entity(entity).unwrap();
assert!(!entity.contains::<A>());
assert!(!entity.contains::<B>());
assert_eq!(
vec!["add_a", "add_b", "remove_a", "remove_b"],
world.resource::<Order>().0
);
}
#[test]
fn observer_trigger_ref() {
let mut world = World::new();
world.add_observer(|mut trigger: Trigger<EventWithData>| trigger.event_mut().counter += 1);
world.add_observer(|mut trigger: Trigger<EventWithData>| trigger.event_mut().counter += 2);
world.add_observer(|mut trigger: Trigger<EventWithData>| trigger.event_mut().counter += 4);
world.flush();
let mut event = EventWithData { counter: 0 };
world.trigger_ref(&mut event);
assert_eq!(7, event.counter);
}
#[test]
fn observer_trigger_targets_ref() {
let mut world = World::new();
world.add_observer(|mut trigger: Trigger<EventWithData, A>| {
trigger.event_mut().counter += 1;
});
world.add_observer(|mut trigger: Trigger<EventWithData, B>| {
trigger.event_mut().counter += 2;
});
world.add_observer(|mut trigger: Trigger<EventWithData, A>| {
trigger.event_mut().counter += 4;
});
world.flush();
let mut event = EventWithData { counter: 0 };
let component_a = world.register_component::<A>();
world.trigger_targets_ref(&mut event, component_a);
assert_eq!(5, event.counter);
}
#[test]
fn observer_multiple_listeners() {
let mut world = World::new();
world.init_resource::<Order>();
world.add_observer(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_1"));
world.add_observer(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_2"));
world.spawn(A).flush();
assert_eq!(vec!["add_1", "add_2"], world.resource::<Order>().0);
assert_eq!(world.entities().len(), 3);
}
#[test]
fn observer_multiple_events() {
let mut world = World::new();
world.init_resource::<Order>();
let on_remove = world.register_component::<OnRemove>();
world.spawn(
unsafe {
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| {
res.observed("add/remove");
})
.with_event(on_remove)
},
);
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(
vec!["add/remove", "add/remove"],
world.resource::<Order>().0
);
}
#[test]
fn observer_multiple_components() {
let mut world = World::new();
world.init_resource::<Order>();
world.register_component::<A>();
world.register_component::<B>();
world.add_observer(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<Order>| {
res.observed("add_ab");
});
let entity = world.spawn(A).id();
world.entity_mut(entity).insert(B);
world.flush();
assert_eq!(vec!["add_ab", "add_ab"], world.resource::<Order>().0);
}
#[test]
fn observer_despawn() {
let mut world = World::new();
let observer = world
.add_observer(|_: Trigger<OnAdd, A>| {
panic!("Observer triggered after being despawned.")
})
.id();
world.despawn(observer);
world.spawn(A).flush();
}
#[test]
fn observer_despawn_archetype_flags() {
let mut world = World::new();
world.init_resource::<Order>();
let entity = world.spawn((A, B)).flush();
world.add_observer(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| {
res.observed("remove_a");
});
let observer = world
.add_observer(|_: Trigger<OnRemove, B>| {
panic!("Observer triggered after being despawned.")
})
.flush();
world.despawn(observer);
world.despawn(entity);
assert_eq!(vec!["remove_a"], world.resource::<Order>().0);
}
#[test]
fn observer_multiple_matches() {
let mut world = World::new();
world.init_resource::<Order>();
world.add_observer(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<Order>| {
res.observed("add_ab");
});
world.spawn((A, B)).flush();
assert_eq!(vec!["add_ab"], world.resource::<Order>().0);
}
#[test]
fn observer_no_target() {
let mut world = World::new();
world.init_resource::<Order>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
world.add_observer(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
assert_eq!(obs.entity(), Entity::PLACEHOLDER);
res.observed("event_a");
});
world.flush();
world.trigger(EventA);
world.flush();
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
}
#[test]
fn observer_entity_routing() {
let mut world = World::new();
world.init_resource::<Order>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
let entity = world
.spawn_empty()
.observe(|_: Trigger<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
.id();
world.add_observer(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
assert_eq!(obs.entity(), entity);
res.observed("a_2");
});
world.flush();
world.trigger_targets(EventA, entity);
world.flush();
assert_eq!(vec!["a_2", "a_1"], world.resource::<Order>().0);
}
#[test]
fn observer_dynamic_component() {
let mut world = World::new();
world.init_resource::<Order>();
let component_id = world.register_component::<A>();
world.spawn(
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<Order>| res.observed("event_a"))
.with_component(component_id),
);
let mut entity = world.spawn_empty();
OwningPtr::make(A, |ptr| {
unsafe { entity.insert_by_id(component_id, ptr) };
});
let entity = entity.flush();
world.trigger_targets(EventA, entity);
world.flush();
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
}
#[test]
fn observer_dynamic_trigger() {
let mut world = World::new();
world.init_resource::<Order>();
let event_a = world.register_component::<EventA>();
world.spawn(ObserverState {
descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) },
runner: |mut world, _trigger, _ptr, _propagate| {
world.resource_mut::<Order>().observed("event_a");
},
..Default::default()
});
world.commands().queue(
unsafe { EmitDynamicTrigger::new_with_id(event_a, EventA, ()) },
);
world.flush();
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating() {
let mut world = World::new();
world.init_resource::<Order>();
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();
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(vec!["child", "parent"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_redundant_dispatch_same_entity() {
let mut world = World::new();
world.init_resource::<Order>();
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();
world.flush();
world.trigger_targets(EventPropagating, [child, child]);
world.flush();
assert_eq!(
vec!["child", "parent", "child", "parent"],
world.resource::<Order>().0
);
}
#[test]
fn observer_propagating_redundant_dispatch_parent_child() {
let mut world = World::new();
world.init_resource::<Order>();
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();
world.flush();
world.trigger_targets(EventPropagating, [child, parent]);
world.flush();
assert_eq!(
vec!["child", "parent", "parent"],
world.resource::<Order>().0
);
}
#[test]
fn observer_propagating_halt() {
let mut world = World::new();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("parent");
})
.id();
let child = world
.spawn(Parent(parent))
.observe(
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child");
trigger.propagate(false);
},
)
.id();
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(vec!["child"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_join() {
let mut world = World::new();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("parent");
})
.id();
let child_a = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child_a");
})
.id();
let child_b = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child_b");
})
.id();
world.flush();
world.trigger_targets(EventPropagating, [child_a, child_b]);
world.flush();
assert_eq!(
vec!["child_a", "parent", "child_b", "parent"],
world.resource::<Order>().0
);
}
#[test]
fn observer_propagating_no_next() {
let mut world = World::new();
world.init_resource::<Order>();
let entity = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("event");
})
.id();
world.flush();
world.trigger_targets(EventPropagating, entity);
world.flush();
assert_eq!(vec!["event"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_parallel_propagation() {
let mut world = World::new();
world.init_resource::<Order>();
let parent_a = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("parent_a");
})
.id();
let child_a = world
.spawn(Parent(parent_a))
.observe(
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child_a");
trigger.propagate(false);
},
)
.id();
let parent_b = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("parent_b");
})
.id();
let child_b = world
.spawn(Parent(parent_b))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child_b");
})
.id();
world.flush();
world.trigger_targets(EventPropagating, [child_a, child_b]);
world.flush();
assert_eq!(
vec!["child_a", "child_b", "parent_b"],
world.resource::<Order>().0
);
}
#[test]
fn observer_propagating_world() {
let mut world = World::new();
world.init_resource::<Order>();
world.add_observer(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("event");
});
let grandparent = world.spawn_empty().id();
let parent = world.spawn(Parent(grandparent)).id();
let child = world.spawn(Parent(parent)).id();
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(vec!["event", "event", "event"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_world_skipping() {
let mut world = World::new();
world.init_resource::<Order>();
world.add_observer(
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<Order>| {
if query.get(trigger.entity()).is_ok() {
res.observed("event");
}
},
);
let grandparent = world.spawn(A).id();
let parent = world.spawn(Parent(grandparent)).id();
let child = world.spawn((A, Parent(parent))).id();
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(vec!["event", "event"], world.resource::<Order>().0);
}
#[test]
fn observer_on_remove_during_despawn_spawn_empty() {
let mut world = World::new();
world.add_observer(|_: Trigger<OnRemove, A>, mut cmd: Commands| {
cmd.spawn_empty();
});
let ent = world.spawn(A).id();
world.despawn(ent);
}
#[test]
fn observer_invalid_params() {
#[derive(Resource)]
struct ResA;
#[derive(Resource)]
struct ResB;
let mut world = World::new();
world.add_observer(|_: Trigger<EventA>, _: Res<ResA>, mut commands: Commands| {
commands.insert_resource(ResB);
});
world.trigger(EventA);
assert!(world.get_resource::<ResB>().is_none());
}
#[test]
fn observer_apply_deferred_from_param_set() {
#[derive(Resource)]
struct ResA;
let mut world = World::new();
world.add_observer(
|_: Trigger<EventA>, mut params: ParamSet<(Query<Entity>, Commands)>| {
params.p1().insert_resource(ResA);
},
);
world.flush();
world.trigger(EventA);
world.flush();
assert!(world.get_resource::<ResA>().is_some());
}
#[test]
fn observer_triggered_components() {
#[derive(Resource, Default)]
struct Counter(HashMap<ComponentId, usize>);
let mut world = World::new();
world.init_resource::<Counter>();
let a_id = world.register_component::<A>();
let b_id = world.register_component::<B>();
world.add_observer(
|trigger: Trigger<EventA, (A, B)>, mut counter: ResMut<Counter>| {
for &component in trigger.components() {
*counter.0.entry(component).or_default() += 1;
}
},
);
world.flush();
world.trigger_targets(EventA, [a_id, b_id]);
world.trigger_targets(EventA, a_id);
world.trigger_targets(EventA, b_id);
world.trigger_targets(EventA, [a_id, b_id]);
world.trigger_targets(EventA, a_id);
world.flush();
let counter = world.resource::<Counter>();
assert_eq!(4, *counter.0.get(&a_id).unwrap());
assert_eq!(3, *counter.0.get(&b_id).unwrap());
}
}