Fix #19219 by moving observer triggers out of resource_scope (#19221)

# Objective

Fixes #19219 

## Solution

Instead of calling `world.commands().trigger` and
`world.commands().trigger_targets` whenever each scene is spawned, save
the `instance_id` and optional parent entity to perform all such calls
at the end. This prevents the potential flush of the world command queue
that can happen if `add_child` is called from causing the crash.

## Testing

- Did you test these changes? If so, how?
- Verified that I can no longer reproduce the bug with the instructions
at #19219.
  - Ran `bevy_scene` tests
- Visually verified that the following examples still run as expected
`many_foxes`, `scene` . (should I test any more?)
- Are there any parts that need more testing?
- Pending to run `cargo test` at the root to test that all examples
still build; I will update the PR when that's done
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
  - Run bevy as usual
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
  - N/a (tested on Linux/wayland but it shouldn't be relevant)

---
This commit is contained in:
Manuel Brea Carreras 2025-05-30 20:33:47 +01:00 committed by François Mockers
parent bc00178b59
commit 56bdd5c3c1

View File

@ -79,6 +79,7 @@ pub struct SceneSpawner {
scenes_to_despawn: Vec<AssetId<DynamicScene>>,
instances_to_despawn: Vec<InstanceId>,
scenes_with_parent: Vec<(InstanceId, Entity)>,
instances_ready: Vec<(InstanceId, Option<Entity>)>,
}
/// Errors that can occur when spawning a scene.
@ -337,8 +338,9 @@ impl SceneSpawner {
// Scenes with parents need more setup before they are ready.
// See `set_scene_instance_parent_sync()`.
if parent.is_none() {
// Defer via commands otherwise SceneSpawner is not available in the observer.
world.commands().trigger(SceneInstanceReady { instance_id });
// We trigger `SceneInstanceReady` events after processing all scenes
// SceneSpawner may not be available in the observer.
self.instances_ready.push((instance_id, None));
}
}
Err(SceneSpawnError::NonExistentScene { .. }) => {
@ -362,8 +364,9 @@ impl SceneSpawner {
// Scenes with parents need more setup before they are ready.
// See `set_scene_instance_parent_sync()`.
if parent.is_none() {
// Defer via commands otherwise SceneSpawner is not available in the observer.
world.commands().trigger(SceneInstanceReady { instance_id });
// We trigger `SceneInstanceReady` events after processing all scenes
// SceneSpawner may not be available in the observer.
self.instances_ready.push((instance_id, None));
}
}
Err(SceneSpawnError::NonExistentRealScene { .. }) => {
@ -398,12 +401,25 @@ impl SceneSpawner {
}
}
// We trigger `SceneInstanceReady` events after processing all scenes
// SceneSpawner may not be available in the observer.
self.instances_ready.push((instance_id, Some(parent)));
} else {
self.scenes_with_parent.push((instance_id, parent));
}
}
}
fn trigger_scene_ready_events(&mut self, world: &mut World) {
for (instance_id, parent) in self.instances_ready.drain(..) {
if let Some(parent) = parent {
// Defer via commands otherwise SceneSpawner is not available in the observer.
world
.commands()
.trigger_targets(SceneInstanceReady { instance_id }, parent);
} else {
self.scenes_with_parent.push((instance_id, parent));
// Defer via commands otherwise SceneSpawner is not available in the observer.
world.commands().trigger(SceneInstanceReady { instance_id });
}
}
}
@ -477,6 +493,7 @@ pub fn scene_spawner_system(world: &mut World) {
.update_spawned_scenes(world, &updated_spawned_scenes)
.unwrap();
scene_spawner.set_scene_instance_parent_sync(world);
scene_spawner.trigger_scene_ready_events(world);
});
}