diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 9b0845f80f..9e6fe16d1a 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -91,12 +91,19 @@ impl Plugin for ScenePlugin { app.world_mut() .register_component_hooks::() .on_remove(|mut world, context| { + let Some(handle) = world.get::(context.entity) else { + return; + }; + let id = handle.id(); if let Some(&SceneInstance(scene_instance)) = world.get::(context.entity) { let Some(mut scene_spawner) = world.get_resource_mut::() else { return; }; + if let Some(instance_ids) = scene_spawner.spawned_scenes.get_mut(&id) { + instance_ids.remove(&scene_instance); + } scene_spawner.unregister_instance(scene_instance); } }); @@ -107,3 +114,299 @@ impl Plugin for ScenePlugin { impl Plugin for ScenePlugin { fn build(&self, _: &mut App) {} } + +#[cfg(test)] +mod tests { + use bevy_app::App; + use bevy_asset::{AssetPlugin, Assets}; + use bevy_ecs::{ + component::Component, + hierarchy::{ChildOf, Children}, + reflect::{AppTypeRegistry, ReflectComponent}, + world::World, + }; + use bevy_reflect::Reflect; + + use crate::{ + DynamicScene, DynamicSceneBuilder, DynamicSceneRoot, Scene, ScenePlugin, SceneRoot, + }; + + #[derive(Component, Reflect, PartialEq, Debug)] + #[reflect(Component)] + struct Circle { + radius: f32, + } + + #[derive(Component, Reflect, PartialEq, Debug)] + #[reflect(Component)] + struct Rectangle { + width: f32, + height: f32, + } + + #[derive(Component, Reflect, PartialEq, Debug)] + #[reflect(Component)] + struct Triangle { + base: f32, + height: f32, + } + + #[derive(Component, Reflect)] + #[reflect(Component)] + struct FinishLine; + + #[test] + fn scene_spawns_and_respawns_after_change() { + let mut app = App::new(); + + app.add_plugins((AssetPlugin::default(), ScenePlugin)) + .register_type::() + .register_type::() + .register_type::() + .register_type::(); + + let scene_handle = app + .world_mut() + .resource_mut::>() + .reserve_handle(); + + let scene_entity = app.world_mut().spawn(SceneRoot(scene_handle.clone())).id(); + app.update(); + + assert!(app.world().entity(scene_entity).get::().is_none()); + + let mut scene_1 = Scene { + world: World::new(), + }; + let root = scene_1.world.spawn_empty().id(); + scene_1.world.spawn(( + Rectangle { + width: 10.0, + height: 5.0, + }, + FinishLine, + ChildOf(root), + )); + scene_1.world.spawn((Circle { radius: 7.0 }, ChildOf(root))); + + app.world_mut() + .resource_mut::>() + .insert(&scene_handle, scene_1); + + app.update(); + + let child_root = app + .world() + .entity(scene_entity) + .get::() + .and_then(|children| children.first().cloned()) + .expect("There should be exactly one child on the scene root"); + let children = app + .world() + .entity(child_root) + .get::() + .expect("The child of the scene root should itself have 2 children"); + assert_eq!(children.len(), 2); + + let finish_line = app.world().entity(children[0]); + assert_eq!(finish_line.archetype().component_count(), 3); + let (rectangle, _, child_of) = + finish_line.components::<(&Rectangle, &FinishLine, &ChildOf)>(); + assert_eq!( + rectangle, + &Rectangle { + width: 10.0, + height: 5.0, + } + ); + assert_eq!(child_of.0, child_root); + + let circle = app.world().entity(children[1]); + assert_eq!(circle.archetype().component_count(), 2); + let (circle, child_of) = circle.components::<(&Circle, &ChildOf)>(); + assert_eq!(circle, &Circle { radius: 7.0 }); + assert_eq!(child_of.0, child_root); + + // Now that we know our scene contains exactly what we expect, we will change the scene + // asset and ensure it contains the new scene results. + + let mut scene_2 = Scene { + world: World::new(), + }; + let root = scene_2.world.spawn_empty().id(); + scene_2.world.spawn(( + Triangle { + base: 1.0, + height: 2.0, + }, + ChildOf(root), + )); + + app.world_mut() + .resource_mut::>() + .insert(&scene_handle, scene_2); + + app.update(); + app.update(); + + let child_root = app + .world() + .entity(scene_entity) + .get::() + .and_then(|children| children.first().cloned()) + .expect("There should be exactly one child on the scene root"); + let children = app + .world() + .entity(child_root) + .get::() + .expect("The child of the scene root should itself have 2 children"); + assert_eq!(children.len(), 1); + + let triangle = app.world().entity(children[0]); + assert_eq!(triangle.archetype().component_count(), 2); + let (triangle, child_of) = triangle.components::<(&Triangle, &ChildOf)>(); + assert_eq!( + triangle, + &Triangle { + base: 1.0, + height: 2.0, + } + ); + assert_eq!(child_of.0, child_root); + } + + #[test] + fn dynamic_scene_spawns_and_respawns_after_change() { + let mut app = App::new(); + + app.add_plugins((AssetPlugin::default(), ScenePlugin)) + .register_type::() + .register_type::() + .register_type::() + .register_type::(); + + let scene_handle = app + .world_mut() + .resource_mut::>() + .reserve_handle(); + + let scene_entity = app + .world_mut() + .spawn(DynamicSceneRoot(scene_handle.clone())) + .id(); + app.update(); + + assert!(app.world().entity(scene_entity).get::().is_none()); + + let create_dynamic_scene = |mut scene: Scene, world: &World| { + scene + .world + .insert_resource(world.resource::().clone()); + DynamicSceneBuilder::from_world(&scene.world) + .extract_entities(scene.world.iter_entities().map(|entity| entity.id())) + .build() + }; + + let mut scene_1 = Scene { + world: World::new(), + }; + let root = scene_1.world.spawn_empty().id(); + scene_1.world.spawn(( + Rectangle { + width: 10.0, + height: 5.0, + }, + FinishLine, + ChildOf(root), + )); + scene_1.world.spawn((Circle { radius: 7.0 }, ChildOf(root))); + + let scene_1 = create_dynamic_scene(scene_1, app.world()); + app.world_mut() + .resource_mut::>() + .insert(&scene_handle, scene_1); + + app.update(); + + let child_root = app + .world() + .entity(scene_entity) + .get::() + .and_then(|children| children.first().cloned()) + .expect("There should be exactly one child on the scene root"); + let children = app + .world() + .entity(child_root) + .get::() + .expect("The child of the scene root should itself have 2 children"); + assert_eq!(children.len(), 2); + + let finish_line = app.world().entity(children[0]); + assert_eq!(finish_line.archetype().component_count(), 3); + let (rectangle, _, child_of) = + finish_line.components::<(&Rectangle, &FinishLine, &ChildOf)>(); + assert_eq!( + rectangle, + &Rectangle { + width: 10.0, + height: 5.0, + } + ); + assert_eq!(child_of.0, child_root); + + let circle = app.world().entity(children[1]); + assert_eq!(circle.archetype().component_count(), 2); + let (circle, child_of) = circle.components::<(&Circle, &ChildOf)>(); + assert_eq!(circle, &Circle { radius: 7.0 }); + assert_eq!(child_of.0, child_root); + + // Now that we know our scene contains exactly what we expect, we will change the scene + // asset and ensure it contains the new scene results. + + let mut scene_2 = Scene { + world: World::new(), + }; + let root = scene_2.world.spawn_empty().id(); + scene_2.world.spawn(( + Triangle { + base: 1.0, + height: 2.0, + }, + ChildOf(root), + )); + + let scene_2 = create_dynamic_scene(scene_2, app.world()); + + app.world_mut() + .resource_mut::>() + .insert(&scene_handle, scene_2); + + app.update(); + app.update(); + + let child_root = app + .world() + .entity(scene_entity) + .get::() + .and_then(|children| children.first().cloned()) + .expect("There should be exactly one child on the scene root"); + let children = app + .world() + .entity(child_root) + .get::() + .expect("The child of the scene root should itself have 2 children"); + assert_eq!(children.len(), 1); + + let triangle = app.world().entity(children[0]); + assert_eq!(triangle.archetype().component_count(), 2); + let (triangle, child_of) = triangle.components::<(&Triangle, &ChildOf)>(); + assert_eq!( + triangle, + &Triangle { + base: 1.0, + height: 2.0, + } + ); + assert_eq!(child_of.0, child_root); + } +} diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 13713fe64c..5243df357a 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -36,9 +36,11 @@ pub struct SceneInstanceReady { /// Information about a scene instance. #[derive(Debug)] -pub struct InstanceInfo { +struct InstanceInfo { /// Mapping of entities from the scene world to the instance world. - pub entity_map: EntityHashMap, + entity_map: EntityHashMap, + /// The parent to attach this instance to. + parent: Option, } /// Unique id identifying a scene instance. @@ -55,11 +57,13 @@ impl InstanceId { /// Handles spawning and despawning scenes in the world, either synchronously or batched through the [`scene_spawner_system`]. /// /// Synchronous methods: (Scene operations will take effect immediately) -/// - [`spawn_dynamic_sync`](Self::spawn_dynamic_sync) /// - [`spawn_sync`](Self::spawn_sync) +/// - [`spawn_dynamic_sync`](Self::spawn_dynamic_sync) /// - [`despawn_sync`](Self::despawn_sync) +/// - [`despawn_dynamic_sync`](Self::despawn_dynamic_sync) /// - [`despawn_instance_sync`](Self::despawn_instance_sync) /// - [`update_spawned_scenes`](Self::update_spawned_scenes) +/// - [`update_spawned_dynamic_scenes`](Self::update_spawned_dynamic_scenes) /// - [`spawn_queued_scenes`](Self::spawn_queued_scenes) /// - [`despawn_queued_scenes`](Self::despawn_queued_scenes) /// - [`despawn_queued_instances`](Self::despawn_queued_instances) @@ -70,17 +74,20 @@ impl InstanceId { /// - [`spawn`](Self::spawn) /// - [`spawn_as_child`](Self::spawn_as_child) /// - [`despawn`](Self::despawn) +/// - [`despawn_dynamic`](Self::despawn_dynamic) /// - [`despawn_instance`](Self::despawn_instance) #[derive(Default, Resource)] pub struct SceneSpawner { + pub(crate) spawned_scenes: HashMap, HashSet>, pub(crate) spawned_dynamic_scenes: HashMap, HashSet>, - pub(crate) spawned_instances: HashMap, - scene_asset_event_reader: EventCursor>, - dynamic_scenes_to_spawn: Vec<(Handle, InstanceId, Option)>, + spawned_instances: HashMap, + scene_asset_event_reader: EventCursor>, + dynamic_scene_asset_event_reader: EventCursor>, scenes_to_spawn: Vec<(Handle, InstanceId, Option)>, - scenes_to_despawn: Vec>, + dynamic_scenes_to_spawn: Vec<(Handle, InstanceId, Option)>, + scenes_to_despawn: Vec>, + dynamic_scenes_to_despawn: Vec>, instances_to_despawn: Vec, - scenes_with_parent: Vec<(InstanceId, Entity)>, instances_ready: Vec<(InstanceId, Option)>, } @@ -156,7 +163,6 @@ impl SceneSpawner { let instance_id = InstanceId::new(); self.dynamic_scenes_to_spawn .push((id.into(), instance_id, Some(parent))); - self.scenes_with_parent.push((instance_id, parent)); instance_id } @@ -172,15 +178,19 @@ impl SceneSpawner { let instance_id = InstanceId::new(); self.scenes_to_spawn .push((id.into(), instance_id, Some(parent))); - self.scenes_with_parent.push((instance_id, parent)); instance_id } - /// Schedule the despawn of all instances of the provided dynamic scene. - pub fn despawn(&mut self, id: impl Into>) { + /// Schedule the despawn of all instances of the provided scene. + pub fn despawn(&mut self, id: impl Into>) { self.scenes_to_despawn.push(id.into()); } + /// Schedule the despawn of all instances of the provided dynamic scene. + pub fn despawn_dynamic(&mut self, id: impl Into>) { + self.dynamic_scenes_to_despawn.push(id.into()); + } + /// Schedule the despawn of a scene instance, removing all its entities from the world. /// /// Note: this will despawn _all_ entities associated with this instance, including those @@ -195,8 +205,22 @@ impl SceneSpawner { self.spawned_instances.remove(&instance_id); } - /// Immediately despawns all instances of a dynamic scene. + /// Immediately despawns all instances of a scene. pub fn despawn_sync( + &mut self, + world: &mut World, + id: impl Into>, + ) -> Result<(), SceneSpawnError> { + if let Some(instance_ids) = self.spawned_scenes.remove(&id.into()) { + for instance_id in instance_ids { + self.despawn_instance_sync(world, &instance_id); + } + } + Ok(()) + } + + /// Immediately despawns all instances of a dynamic scene. + pub fn despawn_dynamic_sync( &mut self, world: &mut World, id: impl Into>, @@ -211,15 +235,21 @@ impl SceneSpawner { /// Immediately despawns a scene instance, removing all its entities from the world. pub fn despawn_instance_sync(&mut self, world: &mut World, instance_id: &InstanceId) { - if let Some(instance) = self.spawned_instances.remove(instance_id) { - for &entity in instance.entity_map.values() { - if let Ok(entity_mut) = world.get_entity_mut(entity) { - entity_mut.despawn(); - }; - } + if let Some(mut instance) = self.spawned_instances.remove(instance_id) { + Self::despawn_instance_internal(world, &mut instance); } } + fn despawn_instance_internal(world: &mut World, instance: &mut InstanceInfo) { + for &entity in instance.entity_map.values() { + if let Ok(entity_mut) = world.get_entity_mut(entity) { + entity_mut.despawn(); + }; + } + // Just make sure if we reuse `InstanceInfo` for something, we don't reuse the despawned entities. + instance.entity_map.clear(); + } + /// Immediately spawns a new instance of the provided dynamic scene. pub fn spawn_dynamic_sync( &mut self, @@ -230,10 +260,18 @@ impl SceneSpawner { let id = id.into(); Self::spawn_dynamic_internal(world, id, &mut entity_map)?; let instance_id = InstanceId::new(); - self.spawned_instances - .insert(instance_id, InstanceInfo { entity_map }); + self.spawned_instances.insert( + instance_id, + InstanceInfo { + entity_map, + parent: None, + }, + ); let spawned = self.spawned_dynamic_scenes.entry(id).or_default(); spawned.insert(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)); Ok(instance_id) } @@ -261,8 +299,18 @@ impl SceneSpawner { let id = id.into(); Self::spawn_sync_internal(world, id, &mut entity_map)?; let instance_id = InstanceId::new(); - self.spawned_instances - .insert(instance_id, InstanceInfo { entity_map }); + self.spawned_instances.insert( + instance_id, + InstanceInfo { + entity_map, + parent: None, + }, + ); + let spawned = self.spawned_scenes.entry(id).or_default(); + spawned.insert(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)); Ok(instance_id) } @@ -286,8 +334,39 @@ impl SceneSpawner { /// Iterate through all instances of the provided scenes and update those immediately. /// - /// Useful for updating already spawned scene instances after their corresponding scene has been modified. + /// Useful for updating already spawned scene instances after their corresponding scene has been + /// modified. pub fn update_spawned_scenes( + &mut self, + world: &mut World, + scene_ids: &[AssetId], + ) -> Result<(), SceneSpawnError> { + for id in scene_ids { + if let Some(spawned_instances) = self.spawned_scenes.get(id) { + for instance_id in spawned_instances { + if let Some(instance_info) = self.spawned_instances.get_mut(instance_id) { + // Despawn the scene before respawning it. This is a very heavy operation, + // but otherwise, entities may be left behind, or be left in an otherwise + // invalid state (e.g., invalid relationships). + Self::despawn_instance_internal(world, instance_info); + Self::spawn_sync_internal(world, *id, &mut instance_info.entity_map)?; + Self::set_scene_instance_parent_sync(world, instance_info); + // We trigger `SceneInstanceReady` events after processing all scenes + // SceneSpawner may not be available in the observer. + self.instances_ready + .push((*instance_id, instance_info.parent)); + } + } + } + } + Ok(()) + } + + /// Iterate through all instances of the provided dynamic scenes and update those immediately. + /// + /// Useful for updating already spawned scene instances after their corresponding dynamic scene + /// has been modified. + pub fn update_spawned_dynamic_scenes( &mut self, world: &mut World, scene_ids: &[AssetId], @@ -296,7 +375,16 @@ impl SceneSpawner { if let Some(spawned_instances) = self.spawned_dynamic_scenes.get(id) { for instance_id in spawned_instances { if let Some(instance_info) = self.spawned_instances.get_mut(instance_id) { + // Despawn the scene before respawning it. This is a very heavy operation, + // but otherwise, entities may be left behind, or be left in an otherwise + // invalid state (e.g., invalid relationships). + Self::despawn_instance_internal(world, instance_info); Self::spawn_dynamic_internal(world, *id, &mut instance_info.entity_map)?; + Self::set_scene_instance_parent_sync(world, instance_info); + // We trigger `SceneInstanceReady` events after processing all scenes + // SceneSpawner may not be available in the observer. + self.instances_ready + .push((*instance_id, instance_info.parent)); } } } @@ -307,10 +395,13 @@ impl SceneSpawner { /// Immediately despawns all scenes scheduled for despawn by despawning their instances. pub fn despawn_queued_scenes(&mut self, world: &mut World) -> Result<(), SceneSpawnError> { let scenes_to_despawn = core::mem::take(&mut self.scenes_to_despawn); - for scene_handle in scenes_to_despawn { self.despawn_sync(world, scene_handle)?; } + let scenes_to_despawn = core::mem::take(&mut self.dynamic_scenes_to_despawn); + for scene_handle in scenes_to_despawn { + self.despawn_dynamic_sync(world, scene_handle)?; + } Ok(()) } @@ -332,18 +423,15 @@ impl SceneSpawner { match Self::spawn_dynamic_internal(world, handle.id(), &mut entity_map) { Ok(_) => { - self.spawned_instances - .insert(instance_id, InstanceInfo { entity_map }); + let instance_info = InstanceInfo { entity_map, parent }; + Self::set_scene_instance_parent_sync(world, &instance_info); + + self.spawned_instances.insert(instance_id, instance_info); let spawned = self.spawned_dynamic_scenes.entry(handle.id()).or_default(); spawned.insert(instance_id); - - // Scenes with parents need more setup before they are ready. - // See `set_scene_instance_parent_sync()`. - if parent.is_none() { - // We trigger `SceneInstanceReady` events after processing all scenes - // SceneSpawner may not be available in the observer. - self.instances_ready.push((instance_id, None)); - } + // We trigger `SceneInstanceReady` events after processing all scenes + // SceneSpawner may not be available in the observer. + self.instances_ready.push((instance_id, parent)); } Err(SceneSpawnError::NonExistentScene { .. }) => { self.dynamic_scenes_to_spawn @@ -360,16 +448,16 @@ impl SceneSpawner { match Self::spawn_sync_internal(world, scene_handle.id(), &mut entity_map) { Ok(_) => { - self.spawned_instances - .insert(instance_id, InstanceInfo { entity_map }); + let instance_info = InstanceInfo { entity_map, parent }; + Self::set_scene_instance_parent_sync(world, &instance_info); - // Scenes with parents need more setup before they are ready. - // See `set_scene_instance_parent_sync()`. - if parent.is_none() { - // We trigger `SceneInstanceReady` events after processing all scenes - // SceneSpawner may not be available in the observer. - self.instances_ready.push((instance_id, None)); - } + self.spawned_instances.insert(instance_id, instance_info); + let spawned = self.spawned_scenes.entry(scene_handle.id()).or_default(); + spawned.insert(instance_id); + + // We trigger `SceneInstanceReady` events after processing all scenes + // SceneSpawner may not be available in the observer. + self.instances_ready.push((instance_id, parent)); } Err(SceneSpawnError::NonExistentRealScene { .. }) => { self.scenes_to_spawn @@ -382,32 +470,23 @@ impl SceneSpawner { Ok(()) } - pub(crate) fn set_scene_instance_parent_sync(&mut self, world: &mut World) { - let scenes_with_parent = core::mem::take(&mut self.scenes_with_parent); - - for (instance_id, parent) in scenes_with_parent { - if let Some(instance) = self.spawned_instances.get(&instance_id) { - for &entity in instance.entity_map.values() { - // Add the `ChildOf` component to the scene root, and update the `Children` component of - // the scene parent - if !world - .get_entity(entity) - .ok() - // This will filter only the scene root entity, as all other from the - // scene have a parent - // Entities that wouldn't exist anymore are also skipped - // this case shouldn't happen anyway - .is_none_or(|entity| entity.contains::()) - { - world.entity_mut(parent).add_child(entity); - } - } - - // 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 set_scene_instance_parent_sync(world: &mut World, instance: &InstanceInfo) { + let Some(parent) = instance.parent else { + return; + }; + for &entity in instance.entity_map.values() { + // Add the `ChildOf` component to the scene root, and update the `Children` component of + // the scene parent + if !world + .get_entity(entity) + .ok() + // This will filter only the scene root entity, as all other from the + // scene have a parent + // Entities that wouldn't exist anymore are also skipped + // this case shouldn't happen anyway + .is_none_or(|entity| entity.contains::()) + { + world.entity_mut(parent).add_child(entity); } } } @@ -452,39 +531,44 @@ impl SceneSpawner { pub fn scene_spawner_system(world: &mut World) { world.resource_scope(|world, mut scene_spawner: Mut| { // remove any loading instances where parent is deleted - let mut dead_instances = >::default(); - scene_spawner - .scenes_with_parent - .retain(|(instance, parent)| { - let retain = world.get_entity(*parent).is_ok(); - - if !retain { - dead_instances.insert(*instance); - } - - retain - }); + let is_parent_alive = |parent: &Option| { + parent + .map(|parent| world.get_entity(parent).is_ok()) + .unwrap_or(true) // If we don't have a parent, then consider the parent alive. + }; scene_spawner .dynamic_scenes_to_spawn - .retain(|(_, instance, _)| !dead_instances.contains(instance)); + .retain(|(_, _, parent)| is_parent_alive(parent)); scene_spawner .scenes_to_spawn - .retain(|(_, instance, _)| !dead_instances.contains(instance)); + .retain(|(_, _, parent)| is_parent_alive(parent)); - let scene_asset_events = world.resource::>>(); + let scene_asset_events = world.resource::>>(); + let dynamic_scene_asset_events = world.resource::>>(); + let scene_spawner = &mut *scene_spawner; let mut updated_spawned_scenes = Vec::new(); - let scene_spawner = &mut *scene_spawner; for event in scene_spawner .scene_asset_event_reader .read(scene_asset_events) { if let AssetEvent::Modified { id } = event { - if scene_spawner.spawned_dynamic_scenes.contains_key(id) { + if scene_spawner.spawned_scenes.contains_key(id) { updated_spawned_scenes.push(*id); } } } + let mut updated_spawned_dynamic_scenes = Vec::new(); + for event in scene_spawner + .dynamic_scene_asset_event_reader + .read(dynamic_scene_asset_events) + { + if let AssetEvent::Modified { id } = event { + if scene_spawner.spawned_dynamic_scenes.contains_key(id) { + updated_spawned_dynamic_scenes.push(*id); + } + } + } scene_spawner.despawn_queued_scenes(world).unwrap(); scene_spawner.despawn_queued_instances(world); @@ -494,7 +578,9 @@ pub fn scene_spawner_system(world: &mut World) { scene_spawner .update_spawned_scenes(world, &updated_spawned_scenes) .unwrap(); - scene_spawner.set_scene_instance_parent_sync(world); + scene_spawner + .update_spawned_dynamic_scenes(world, &updated_spawned_dynamic_scenes) + .unwrap(); scene_spawner.trigger_scene_ready_events(world); }); } @@ -615,7 +701,7 @@ mod tests { // let's try to delete the scene let mut scene_spawner = app.world_mut().resource_mut::(); - scene_spawner.despawn(&scene_handle); + scene_spawner.despawn_dynamic(&scene_handle); // run the scene spawner system to despawn the scene app.update(); diff --git a/release-content/migration-guides/scene_spawner_api.md b/release-content/migration-guides/scene_spawner_api.md new file mode 100644 index 0000000000..5303682488 --- /dev/null +++ b/release-content/migration-guides/scene_spawner_api.md @@ -0,0 +1,12 @@ +--- +title: `SceneSpawner` methods have been renamed and replaced. +pull_requests: [18358] +--- + +Some methods on `SceneSpawner` have been renamed: + - `despawn` -> `despawn_dynamic` + - `despawn_sync` -> `despawn_dynamic_sync` + - `update_spawned_scenes` -> `update_spawned_dynamic_scenes` + +In their place, we've added `despawn`, `despawn_sync`, and `update_spawned_scenes` which all act on +`Scene`s (as opposed to `DynamicScene`s).