use crate::{DynamicScene, Scene}; use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_ecs::{ entity::{Entity, EntityHashMap}, event::{EntityEvent, EventCursor, Events}, hierarchy::ChildOf, reflect::AppTypeRegistry, resource::Resource, world::{Mut, World}, }; use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::Reflect; use bevy_utils::prelude::DebugName; use thiserror::Error; use uuid::Uuid; use crate::{DynamicSceneRoot, SceneRoot}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::ResMut, prelude::{Changed, Component, Without}, system::{Commands, Query}, }; /// Triggered on a scene's parent entity when [`crate::SceneInstance`] becomes ready to use. /// /// See also [`On`], [`SceneSpawner::instance_is_ready`]. /// /// [`On`]: bevy_ecs::observer::On #[derive(Clone, Copy, Debug, Eq, PartialEq, EntityEvent, Reflect)] #[reflect(Debug, PartialEq, Clone)] pub struct SceneInstanceReady { /// Instance which has been spawned. pub instance_id: InstanceId, } /// Information about a scene instance. #[derive(Debug)] struct InstanceInfo { /// Mapping of entities from the scene world to the instance world. entity_map: EntityHashMap, /// The parent to attach this instance to. parent: Option, } /// Unique id identifying a scene instance. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Reflect)] #[reflect(Debug, PartialEq, Hash, Clone)] pub struct InstanceId(Uuid); impl InstanceId { fn new() -> Self { InstanceId(Uuid::new_v4()) } } /// 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_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) /// /// Deferred methods: (Scene operations will be processed when the [`scene_spawner_system`] is run) /// - [`spawn_dynamic`](Self::spawn_dynamic) /// - [`spawn_dynamic_as_child`](Self::spawn_dynamic_as_child) /// - [`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>, spawned_instances: HashMap, scene_asset_event_reader: EventCursor>, dynamic_scene_asset_event_reader: EventCursor>, scenes_to_spawn: Vec<(Handle, InstanceId, Option)>, dynamic_scenes_to_spawn: Vec<(Handle, InstanceId, Option)>, scenes_to_despawn: Vec>, dynamic_scenes_to_despawn: Vec>, instances_to_despawn: Vec, instances_ready: Vec<(InstanceId, Option)>, } /// Errors that can occur when spawning a scene. #[derive(Error, Debug)] pub enum SceneSpawnError { /// Scene contains an unregistered component type. #[error("scene contains the unregistered component `{type_path}`. consider adding `#[reflect(Component)]` to your type")] UnregisteredComponent { /// Type of the unregistered component. type_path: String, }, /// Scene contains an unregistered resource type. #[error("scene contains the unregistered resource `{type_path}`. consider adding `#[reflect(Resource)]` to your type")] UnregisteredResource { /// Type of the unregistered resource. type_path: String, }, /// Scene contains an unregistered type. #[error( "scene contains the unregistered type `{std_type_name}`. \ consider reflecting it with `#[derive(Reflect)]` \ and registering the type using `app.register_type::()`" )] UnregisteredType { /// The [type name](std::any::type_name) for the unregistered type. std_type_name: DebugName, }, /// Scene contains an unregistered type which has a `TypePath`. #[error( "scene contains the reflected type `{type_path}` but it was not found in the type registry. \ consider registering the type using `app.register_type::()``" )] UnregisteredButReflectedType { /// The unregistered type. type_path: String, }, /// Scene contains a proxy without a represented type. #[error("scene contains dynamic type `{type_path}` without a represented type. consider changing this using `set_represented_type`.")] NoRepresentedType { /// The dynamic instance type. type_path: String, }, /// Dynamic scene with the given id does not exist. #[error("scene does not exist")] NonExistentScene { /// Id of the non-existent dynamic scene. id: AssetId, }, /// Scene with the given id does not exist. #[error("scene does not exist")] NonExistentRealScene { /// Id of the non-existent scene. id: AssetId, }, } impl SceneSpawner { /// Schedule the spawn of a new instance of the provided dynamic scene. pub fn spawn_dynamic(&mut self, id: impl Into>) -> InstanceId { let instance_id = InstanceId::new(); self.dynamic_scenes_to_spawn .push((id.into(), instance_id, None)); instance_id } /// Schedule the spawn of a new instance of the provided dynamic scene as a child of `parent`. pub fn spawn_dynamic_as_child( &mut self, id: impl Into>, parent: Entity, ) -> InstanceId { let instance_id = InstanceId::new(); self.dynamic_scenes_to_spawn .push((id.into(), instance_id, Some(parent))); instance_id } /// Schedule the spawn of a new instance of the provided scene. pub fn spawn(&mut self, id: impl Into>) -> InstanceId { let instance_id = InstanceId::new(); self.scenes_to_spawn.push((id.into(), instance_id, None)); instance_id } /// Schedule the spawn of a new instance of the provided scene as a child of `parent`. pub fn spawn_as_child(&mut self, id: impl Into>, parent: Entity) -> InstanceId { let instance_id = InstanceId::new(); self.scenes_to_spawn .push((id.into(), instance_id, Some(parent))); instance_id } /// 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 /// that have been removed from the scene hierarchy. To despawn _only_ entities still in the hierarchy, /// despawn the relevant root entity directly. pub fn despawn_instance(&mut self, instance_id: InstanceId) { self.instances_to_despawn.push(instance_id); } /// This will remove all records of this instance, without despawning any entities. pub fn unregister_instance(&mut self, instance_id: InstanceId) { self.spawned_instances.remove(&instance_id); } /// 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>, ) -> Result<(), SceneSpawnError> { if let Some(instance_ids) = self.spawned_dynamic_scenes.remove(&id.into()) { for instance_id in instance_ids { self.despawn_instance_sync(world, &instance_id); } } Ok(()) } /// 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(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, world: &mut World, id: impl Into>, ) -> Result { let mut entity_map = EntityHashMap::default(); 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, 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) } fn spawn_dynamic_internal( world: &mut World, id: AssetId, entity_map: &mut EntityHashMap, ) -> Result<(), SceneSpawnError> { world.resource_scope(|world, scenes: Mut>| { let scene = scenes .get(id) .ok_or(SceneSpawnError::NonExistentScene { id })?; scene.write_to_world(world, entity_map) }) } /// Immediately spawns a new instance of the provided scene. pub fn spawn_sync( &mut self, world: &mut World, id: impl Into>, ) -> Result { let mut entity_map = EntityHashMap::default(); 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, 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) } fn spawn_sync_internal( world: &mut World, id: AssetId, entity_map: &mut EntityHashMap, ) -> Result<(), SceneSpawnError> { world.resource_scope(|world, scenes: Mut>| { let scene = scenes .get(id) .ok_or(SceneSpawnError::NonExistentRealScene { id })?; scene.write_to_world_with( world, entity_map, &world.resource::().clone(), ) }) } /// 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. 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], ) -> Result<(), SceneSpawnError> { for id in scene_ids { 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)); } } } } Ok(()) } /// 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(()) } /// Immediately despawns all scene instances scheduled for despawn. pub fn despawn_queued_instances(&mut self, world: &mut World) { let instances_to_despawn = core::mem::take(&mut self.instances_to_despawn); for instance_id in instances_to_despawn { self.despawn_instance_sync(world, &instance_id); } } /// Immediately spawns all scenes scheduled for spawn. pub fn spawn_queued_scenes(&mut self, world: &mut World) -> Result<(), SceneSpawnError> { let scenes_to_spawn = core::mem::take(&mut self.dynamic_scenes_to_spawn); for (handle, instance_id, parent) in scenes_to_spawn { let mut entity_map = EntityHashMap::default(); match Self::spawn_dynamic_internal(world, handle.id(), &mut entity_map) { Ok(_) => { 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); // 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 .push((handle, instance_id, parent)); } Err(err) => return Err(err), } } let scenes_to_spawn = core::mem::take(&mut self.scenes_to_spawn); for (scene_handle, instance_id, parent) in scenes_to_spawn { let mut entity_map = EntityHashMap::default(); match Self::spawn_sync_internal(world, scene_handle.id(), &mut entity_map) { Ok(_) => { 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_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 .push((scene_handle, instance_id, parent)); } Err(err) => return Err(err), } } Ok(()) } 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); } } } 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 { // Defer via commands otherwise SceneSpawner is not available in the observer. world.commands().trigger(SceneInstanceReady { instance_id }); } } } /// Check that a scene instance spawned previously is ready to use pub fn instance_is_ready(&self, instance_id: InstanceId) -> bool { self.spawned_instances.contains_key(&instance_id) } /// Get an iterator over the entities in an instance, once it's spawned. /// /// Before the scene is spawned, the iterator will be empty. Use [`Self::instance_is_ready`] /// to check if the instance is ready. pub fn iter_instance_entities( &'_ self, instance_id: InstanceId, ) -> impl Iterator + '_ { self.spawned_instances .get(&instance_id) .map(|instance| instance.entity_map.values()) .into_iter() .flatten() .copied() } } /// System that handles scheduled scene instance spawning and despawning through a [`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 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(|(_, _, parent)| is_parent_alive(parent)); scene_spawner .scenes_to_spawn .retain(|(_, _, parent)| is_parent_alive(parent)); 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(); for event in scene_spawner .scene_asset_event_reader .read(scene_asset_events) { if let AssetEvent::Modified { id } = event { 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); scene_spawner .spawn_queued_scenes(world) .unwrap_or_else(|err| panic!("{}", err)); scene_spawner .update_spawned_scenes(world, &updated_spawned_scenes) .unwrap(); scene_spawner .update_spawned_dynamic_scenes(world, &updated_spawned_dynamic_scenes) .unwrap(); scene_spawner.trigger_scene_ready_events(world); }); } /// [`InstanceId`] of a spawned scene. It can be used with the [`SceneSpawner`] to /// interact with the spawned scene. #[derive(Component, Deref, DerefMut)] pub struct SceneInstance(pub(crate) InstanceId); /// System that will spawn scenes from the [`SceneRoot`] and [`DynamicSceneRoot`] components. pub fn scene_spawner( mut commands: Commands, mut scene_to_spawn: Query< (Entity, &SceneRoot, Option<&mut SceneInstance>), (Changed, Without), >, mut dynamic_scene_to_spawn: Query< (Entity, &DynamicSceneRoot, Option<&mut SceneInstance>), (Changed, Without), >, mut scene_spawner: ResMut, ) { for (entity, scene, instance) in &mut scene_to_spawn { let new_instance = scene_spawner.spawn_as_child(scene.0.clone(), entity); if let Some(mut old_instance) = instance { scene_spawner.despawn_instance(**old_instance); *old_instance = SceneInstance(new_instance); } else { commands.entity(entity).insert(SceneInstance(new_instance)); } } for (entity, dynamic_scene, instance) in &mut dynamic_scene_to_spawn { let new_instance = scene_spawner.spawn_dynamic_as_child(dynamic_scene.0.clone(), entity); if let Some(mut old_instance) = instance { scene_spawner.despawn_instance(**old_instance); *old_instance = SceneInstance(new_instance); } else { commands.entity(entity).insert(SceneInstance(new_instance)); } } } #[cfg(test)] mod tests { use bevy_app::App; use bevy_asset::{AssetPlugin, AssetServer, Handle}; use bevy_ecs::{ component::Component, hierarchy::Children, observer::On, prelude::ReflectComponent, query::With, system::{Commands, Query, Res, ResMut, RunSystemOnce}, }; use bevy_reflect::Reflect; use crate::{DynamicSceneBuilder, DynamicSceneRoot, ScenePlugin}; use super::*; use crate::{DynamicScene, SceneSpawner}; use bevy_app::ScheduleRunnerPlugin; use bevy_asset::Assets; use bevy_ecs::{ entity::Entity, prelude::{AppTypeRegistry, World}, }; #[derive(Component, Reflect, Default)] #[reflect(Component)] struct ComponentA { pub x: f32, pub y: f32, } #[test] fn spawn_and_delete() { let mut app = App::new(); app.add_plugins(ScheduleRunnerPlugin::default()) .add_plugins(AssetPlugin::default()) .add_plugins(ScenePlugin) .register_type::(); app.update(); let mut scene_world = World::new(); // create a new DynamicScene manually let type_registry = app.world().resource::().clone(); scene_world.insert_resource(type_registry); scene_world.spawn(ComponentA { x: 3.0, y: 4.0 }); let scene = DynamicScene::from_world(&scene_world); let scene_handle = app .world_mut() .resource_mut::>() .add(scene); // spawn the scene as a child of `entity` using `DynamicSceneRoot` let entity = app .world_mut() .spawn(DynamicSceneRoot(scene_handle.clone())) .id(); // run the app's schedule once, so that the scene gets spawned app.update(); // make sure that the scene was added as a child of the root entity let (scene_entity, scene_component_a) = app .world_mut() .query::<(Entity, &ComponentA)>() .single(app.world()) .unwrap(); assert_eq!(scene_component_a.x, 3.0); assert_eq!(scene_component_a.y, 4.0); assert_eq!( app.world().entity(entity).get::().unwrap().len(), 1 ); // let's try to delete the scene let mut scene_spawner = app.world_mut().resource_mut::(); scene_spawner.despawn_dynamic(&scene_handle); // run the scene spawner system to despawn the scene app.update(); // the scene entity does not exist anymore assert!(app.world().get_entity(scene_entity).is_err()); // the root entity does not have any children anymore assert!(app.world().entity(entity).get::().is_none()); } #[derive(Reflect, Component, Debug, PartialEq, Eq, Clone, Copy, Default)] #[reflect(Component)] struct A(usize); #[test] fn clone_dynamic_entities() { let mut world = World::default(); // setup let atr = AppTypeRegistry::default(); atr.write().register::(); world.insert_resource(atr); world.insert_resource(Assets::::default()); // start test world.spawn(A(42)); assert_eq!(world.query::<&A>().iter(&world).len(), 1); // clone only existing entity let mut scene_spawner = SceneSpawner::default(); let entity = world .query_filtered::>() .single(&world) .unwrap(); let scene = DynamicSceneBuilder::from_world(&world) .extract_entity(entity) .build(); let scene_id = world.resource_mut::>().add(scene); let instance_id = scene_spawner .spawn_dynamic_sync(&mut world, &scene_id) .unwrap(); // verify we spawned exactly one new entity with our expected component assert_eq!(world.query::<&A>().iter(&world).len(), 2); // verify that we can get this newly-spawned entity by the instance ID let new_entity = scene_spawner .iter_instance_entities(instance_id) .next() .unwrap(); // verify this is not the original entity assert_ne!(entity, new_entity); // verify this new entity contains the same data as the original entity let [old_a, new_a] = world .query::<&A>() .get_many(&world, [entity, new_entity]) .unwrap(); assert_eq!(old_a, new_a); } #[derive(Component, Reflect, Default)] #[reflect(Component)] struct ComponentF; #[derive(Resource, Default)] struct TriggerCount(u32); fn setup() -> App { let mut app = App::new(); app.add_plugins((AssetPlugin::default(), ScenePlugin)); app.init_resource::(); app.register_type::(); app.world_mut().spawn(ComponentF); app.world_mut().spawn(ComponentF); app } fn build_scene(app: &mut App) -> Handle { app.world_mut() .run_system_once( |world: &World, type_registry: Res<'_, AppTypeRegistry>, asset_server: Res<'_, AssetServer>| { asset_server.add( Scene::from_dynamic_scene(&DynamicScene::from_world(world), &type_registry) .unwrap(), ) }, ) .expect("Failed to run scene builder system.") } fn build_dynamic_scene(app: &mut App) -> Handle { app.world_mut() .run_system_once(|world: &World, asset_server: Res<'_, AssetServer>| { asset_server.add(DynamicScene::from_world(world)) }) .expect("Failed to run dynamic scene builder system.") } fn observe_trigger(app: &mut App, scene_id: InstanceId, scene_entity: Option) { // Add observer app.world_mut().add_observer( move |trigger: On, scene_spawner: Res, mut trigger_count: ResMut| { assert_eq!( trigger.event().instance_id, scene_id, "`SceneInstanceReady` contains the wrong `InstanceId`" ); assert_eq!( trigger.target(), scene_entity.unwrap_or(Entity::PLACEHOLDER), "`SceneInstanceReady` triggered on the wrong parent entity" ); assert!( scene_spawner.instance_is_ready(trigger.event().instance_id), "`InstanceId` is not ready" ); trigger_count.0 += 1; }, ); // Check observer is triggered once. app.update(); app.world_mut() .run_system_once(|trigger_count: Res| { assert_eq!( trigger_count.0, 1, "wrong number of `SceneInstanceReady` triggers" ); }) .unwrap(); } #[test] fn observe_scene() { let mut app = setup(); // Build scene. let scene = build_scene(&mut app); // Spawn scene. let scene_id = app .world_mut() .run_system_once(move |mut scene_spawner: ResMut<'_, SceneSpawner>| { scene_spawner.spawn(scene.clone()) }) .unwrap(); // Check trigger. observe_trigger(&mut app, scene_id, None); } #[test] fn observe_dynamic_scene() { let mut app = setup(); // Build scene. let scene = build_dynamic_scene(&mut app); // Spawn scene. let scene_id = app .world_mut() .run_system_once(move |mut scene_spawner: ResMut<'_, SceneSpawner>| { scene_spawner.spawn_dynamic(scene.clone()) }) .unwrap(); // Check trigger. observe_trigger(&mut app, scene_id, None); } #[test] fn observe_scene_as_child() { let mut app = setup(); // Build scene. let scene = build_scene(&mut app); // Spawn scene as child. let (scene_id, scene_entity) = app .world_mut() .run_system_once( move |mut commands: Commands<'_, '_>, mut scene_spawner: ResMut<'_, SceneSpawner>| { let entity = commands.spawn_empty().id(); let id = scene_spawner.spawn_as_child(scene.clone(), entity); (id, entity) }, ) .unwrap(); // Check trigger. observe_trigger(&mut app, scene_id, Some(scene_entity)); } #[test] fn observe_dynamic_scene_as_child() { let mut app = setup(); // Build scene. let scene = build_dynamic_scene(&mut app); // Spawn scene as child. let (scene_id, scene_entity) = app .world_mut() .run_system_once( move |mut commands: Commands<'_, '_>, mut scene_spawner: ResMut<'_, SceneSpawner>| { let entity = commands.spawn_empty().id(); let id = scene_spawner.spawn_dynamic_as_child(scene.clone(), entity); (id, entity) }, ) .unwrap(); // Check trigger. observe_trigger(&mut app, scene_id, Some(scene_entity)); } #[test] fn despawn_scene() { let mut app = App::new(); app.add_plugins((AssetPlugin::default(), ScenePlugin)); app.register_type::(); let asset_server = app.world().resource::(); // Build scene. let scene = asset_server.add(DynamicScene::default()); let count = 10; // Checks the number of scene instances stored in `SceneSpawner`. let check = |world: &mut World, expected_count: usize| { let scene_spawner = world.resource::(); assert_eq!( scene_spawner.spawned_dynamic_scenes[&scene.id()].len(), expected_count ); assert_eq!(scene_spawner.spawned_instances.len(), expected_count); }; // Spawn scene. for _ in 0..count { app.world_mut() .spawn((ComponentF, DynamicSceneRoot(scene.clone()))); } app.update(); check(app.world_mut(), count); // Despawn scene. app.world_mut() .run_system_once( |mut commands: Commands, query: Query>| { for entity in query.iter() { commands.entity(entity).despawn(); } }, ) .unwrap(); app.update(); check(app.world_mut(), 0); } #[test] fn scene_child_order_preserved_when_archetype_order_mismatched() { let mut app = App::new(); app.add_plugins(ScheduleRunnerPlugin::default()) .add_plugins(AssetPlugin::default()) .add_plugins(ScenePlugin) .register_type::() .register_type::(); app.update(); let mut scene_world = World::new(); let root = scene_world.spawn_empty().id(); let temporary_root = scene_world.spawn_empty().id(); // Spawn entities with different parent first before parenting them to the actual root, allowing us // to decouple child order from archetype-creation-order let child1 = scene_world .spawn((ChildOf(temporary_root), ComponentA { x: 1.0, y: 1.0 })) .id(); let child2 = scene_world .spawn((ChildOf(temporary_root), ComponentA { x: 2.0, y: 2.0 })) .id(); // the "first" child is intentionally spawned with a different component to force it into a "newer" archetype, // meaning it will be iterated later in the spawn code. let child0 = scene_world .spawn((ChildOf(temporary_root), ComponentF)) .id(); scene_world .entity_mut(root) .add_children(&[child0, child1, child2]); let scene = Scene::new(scene_world); let scene_handle = app.world_mut().resource_mut::>().add(scene); let spawned = app.world_mut().spawn(SceneRoot(scene_handle.clone())).id(); app.update(); let world = app.world_mut(); let spawned_root = world.entity(spawned).get::().unwrap()[1]; let children = world.entity(spawned_root).get::().unwrap(); assert_eq!(children.len(), 3); assert!(world.entity(children[0]).get::().is_some()); assert_eq!( world.entity(children[1]).get::().unwrap().x, 1.0 ); assert_eq!( world.entity(children[2]).get::().unwrap().x, 2.0 ); } }