Added ordering information to observer tests (#14332) (#15178)

Fixes #14332 by recording the order in which the events occur.
This commit is contained in:
Wybe Westra 2024-09-14 16:08:49 +02:00 committed by GitHub
parent b36443b6ed
commit 70808af776
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -480,6 +480,8 @@ impl World {
#[cfg(test)]
mod tests {
use std::vec;
use bevy_ptr::OwningPtr;
use crate as bevy_ecs;
@ -506,13 +508,12 @@ mod tests {
struct EventA;
#[derive(Resource, Default)]
struct R(usize);
struct Order(Vec<&'static str>);
impl R {
impl Order {
#[track_caller]
fn assert_order(&mut self, count: usize) {
assert_eq!(count, self.0);
self.0 += 1;
fn observed(&mut self, name: &'static str) {
self.0.push(name);
}
}
@ -537,61 +538,72 @@ mod tests {
#[test]
fn observer_order_spawn_despawn() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(2));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(3));
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(4, world.resource::<R>().0);
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::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(2));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(3));
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
world.observe(|_: 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!(4, world.resource::<R>().0);
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::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnReplace, S>, mut res: ResMut<R>| res.assert_order(2));
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<R>| res.assert_order(3));
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<Order>| res.observed("add"));
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<Order>| res.observed("insert"));
world.observe(|_: Trigger<OnReplace, S>, mut res: ResMut<Order>| res.observed("replace"));
world.observe(|_: 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!(4, world.resource::<R>().0);
assert_eq!(
vec!["add", "insert", "replace", "remove"],
world.resource::<Order>().0
);
}
#[test]
fn observer_order_replace() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let entity = world.spawn(A).id();
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
// and therefore does not automatically flush.
@ -600,53 +612,56 @@ mod tests {
let mut entity = world.entity_mut(entity);
entity.insert(A);
entity.flush();
assert_eq!(2, world.resource::<R>().0);
assert_eq!(vec!["replace", "insert"], world.resource::<Order>().0);
}
#[test]
fn observer_order_recursive() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(
|obs: Trigger<OnAdd, A>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(0);
|obs: Trigger<OnAdd, A>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("add_a");
commands.entity(obs.entity()).insert(B);
},
);
world.observe(
|obs: Trigger<OnRemove, A>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(2);
|obs: Trigger<OnRemove, A>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("remove_a");
commands.entity(obs.entity()).remove::<B>();
},
);
world.observe(
|obs: Trigger<OnAdd, B>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(1);
|obs: Trigger<OnAdd, B>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("add_b");
commands.entity(obs.entity()).remove::<A>();
},
);
world.observe(|_: Trigger<OnRemove, B>, mut res: ResMut<R>| {
res.assert_order(3);
world.observe(|_: 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!(4, world.resource::<R>().0);
assert_eq!(
vec!["add_a", "add_b", "remove_a", "remove_b"],
world.resource::<Order>().0
);
}
#[test]
fn observer_multiple_listeners() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_1"));
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_2"));
world.spawn(A).flush();
assert_eq!(2, world.resource::<R>().0);
assert_eq!(vec!["add_1", "add_2"], world.resource::<Order>().0);
// Our A entity plus our two observers
assert_eq!(world.entities().len(), 3);
}
@ -654,40 +669,44 @@ mod tests {
#[test]
fn observer_multiple_events() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let on_remove = world.init_component::<OnRemove>();
world.spawn(
// SAFETY: OnAdd and OnRemove are both unit types, so this is safe
unsafe {
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1)
.with_event(on_remove)
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!(2, world.resource::<R>().0);
assert_eq!(
vec!["add/remove", "add/remove"],
world.resource::<Order>().0
);
}
#[test]
fn observer_multiple_components() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.init_component::<A>();
world.init_component::<B>();
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: 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!(2, world.resource::<R>().0);
assert_eq!(vec!["add_ab", "add_ab"], world.resource::<Order>().0);
}
#[test]
fn observer_despawn() {
let mut world = World::new();
world.init_resource::<R>();
let observer = world
.observe(|_: Trigger<OnAdd, A>| panic!("Observer triggered after being despawned."))
@ -700,11 +719,11 @@ mod tests {
#[test]
fn observer_despawn_archetype_flags() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let entity = world.spawn((A, B)).flush();
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove_a"));
let observer = world
.observe(|_: Trigger<OnRemove, B>| panic!("Observer triggered after being despawned."))
@ -713,31 +732,31 @@ mod tests {
world.despawn(entity);
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["remove_a"], world.resource::<Order>().0);
}
#[test]
fn observer_multiple_matches() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<Order>| res.observed("add_ab"));
world.spawn((A, B)).flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["add_ab"], world.resource::<Order>().0);
}
#[test]
fn observer_no_target() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
assert_eq!(obs.entity(), Entity::PLACEHOLDER);
res.0 += 1;
res.observed("event_a");
});
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -745,24 +764,24 @@ mod tests {
world.flush();
world.trigger(EventA);
world.flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
}
#[test]
fn observer_entity_routing() {
let mut world = World::new();
world.init_resource::<R>();
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<R>| res.0 += 1)
.observe(|_: Trigger<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
.id();
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
assert_eq!(obs.entity(), entity);
res.0 += 1;
res.observed("a_2");
});
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -770,17 +789,17 @@ mod tests {
world.flush();
world.trigger_targets(EventA, entity);
world.flush();
assert_eq!(2, world.resource::<R>().0);
assert_eq!(vec!["a_2", "a_1"], world.resource::<Order>().0);
}
#[test]
fn observer_dynamic_component() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let component_id = world.init_component::<A>();
world.spawn(
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<R>| res.0 += 1)
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<Order>| res.observed("event_a"))
.with_component(component_id),
);
@ -793,20 +812,20 @@ mod tests {
world.trigger_targets(EventA, entity);
world.flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
}
#[test]
fn observer_dynamic_trigger() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let event_a = world.init_component::<EventA>();
world.spawn(ObserverState {
// SAFETY: we registered `event_a` above and it matches the type of TriggerA
descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) },
runner: |mut world, _trigger, _ptr, _propagate| {
world.resource_mut::<R>().0 += 1;
world.resource_mut::<Order>().observed("event_a");
},
..Default::default()
});
@ -816,22 +835,22 @@ mod tests {
unsafe { EmitDynamicTrigger::new_with_id(event_a, EventA, ()) },
);
world.flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
.id();
let child = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
.id();
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -839,22 +858,22 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(2, world.resource::<R>().0);
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::<R>();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
.id();
let child = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
.id();
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -862,22 +881,25 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, [child, child]);
world.flush();
assert_eq!(4, world.resource::<R>().0);
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::<R>();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
.id();
let child = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
.id();
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -885,24 +907,27 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, [child, parent]);
world.flush();
assert_eq!(3, world.resource::<R>().0);
assert_eq!(
vec!["child", "parent", "parent"],
world.resource::<Order>().0
);
}
#[test]
fn observer_propagating_halt() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.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<R>| {
res.0 += 1;
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child");
trigger.propagate(false);
},
)
@ -913,30 +938,30 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["child"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_join() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
.id();
let child_a = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| {
res.0 += 1;
.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<R>| {
res.0 += 1;
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child_b");
})
.id();
@ -945,17 +970,20 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, [child_a, child_b]);
world.flush();
assert_eq!(4, world.resource::<R>().0);
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::<R>();
world.init_resource::<Order>();
let entity = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("event"))
.id();
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -963,24 +991,26 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, entity);
world.flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["event"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_parallel_propagation() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let parent_a = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.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<R>| {
res.0 += 1;
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child_a");
trigger.propagate(false);
},
)
@ -988,12 +1018,14 @@ mod tests {
let parent_b = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.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<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child_b"))
.id();
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -1001,15 +1033,18 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, [child_a, child_b]);
world.flush();
assert_eq!(3, world.resource::<R>().0);
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::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("event"));
let grandparent = world.spawn_empty().id();
let parent = world.spawn(Parent(grandparent)).id();
@ -1020,18 +1055,18 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(3, world.resource::<R>().0);
assert_eq!(vec!["event", "event", "event"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_world_skipping() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<R>| {
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<Order>| {
if query.get(trigger.entity()).is_ok() {
res.0 += 1;
res.observed("event");
}
},
);
@ -1045,6 +1080,6 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(2, world.resource::<R>().0);
assert_eq!(vec!["event", "event"], world.resource::<Order>().0);
}
}