Skip to content

Commit 3d0f240

Browse files
authored
bevy_ecs: flush entities after running observers and hooks in despawn (#15398)
# Objective Fixes #14467 Observers and component lifecycle hooks are allowed to perform operations that subsequently require `Entities` to be flushed, such as reserving a new entity. If this occurs during an `on_remove` hook or an `OnRemove` event trigger during an `EntityWorldMut::despawn`, a panic will occur. ## Solution Call `world.flush_entities()` after running `on_remove` hooks/observers during `despawn` ## Testing Added a new test that fails before the fix and succeeds afterward.
1 parent 1a41c73 commit 3d0f240

File tree

2 files changed

+26
-1
lines changed

2 files changed

+26
-1
lines changed

crates/bevy_ecs/src/observer/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,4 +1160,26 @@ mod tests {
11601160
world.flush();
11611161
assert_eq!(vec!["event", "event"], world.resource::<Order>().0);
11621162
}
1163+
1164+
// Regression test for https://github.com/bevyengine/bevy/issues/14467
1165+
// Fails prior to https://github.com/bevyengine/bevy/pull/15398
1166+
#[test]
1167+
fn observer_on_remove_during_despawn_spawn_empty() {
1168+
let mut world = World::new();
1169+
1170+
// Observe the removal of A - this will run during despawn
1171+
world.observe(|_: Trigger<OnRemove, A>, mut cmd: Commands| {
1172+
// Spawn a new entity - this reserves a new ID and requires a flush
1173+
// afterward before Entities::free can be called.
1174+
cmd.spawn_empty();
1175+
});
1176+
1177+
let ent = world.spawn(A).id();
1178+
1179+
// Despawn our entity, which runs the OnRemove observer and allocates a
1180+
// new Entity.
1181+
// Should not panic - if it does, then Entities was not flushed properly
1182+
// after the observer's spawn_empty.
1183+
world.despawn(ent);
1184+
}
11631185
}

crates/bevy_ecs/src/world/entity_ref.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,6 @@ impl<'w> EntityWorldMut<'w> {
12971297
/// See [`World::despawn`] for more details.
12981298
pub fn despawn(self) {
12991299
let world = self.world;
1300-
world.flush_entities();
13011300
let archetype = &world.archetypes[self.location.archetype_id];
13021301

13031302
// SAFETY: Archetype cannot be mutably aliased by DeferredWorld
@@ -1323,6 +1322,10 @@ impl<'w> EntityWorldMut<'w> {
13231322
world.removed_components.send(component_id, self.entity);
13241323
}
13251324

1325+
// Observers and on_remove hooks may reserve new entities, which
1326+
// requires a flush before Entities::free may be called.
1327+
world.flush_entities();
1328+
13261329
let location = world
13271330
.entities
13281331
.free(self.entity)

0 commit comments

Comments
 (0)