Change SceneInstanceReady to trigger an observer. (#13859)
				
					
				
			# Objective The `SceneInstanceReady` event would be more ergonomic (and potentially efficient) if it could be delivered to listeners attached to the scene entities becoming ready rather than into a World-global queue. This is an evolution of @Shatur's work in #9313. ## Solution The scene spawner is changed to trigger observers on the scene entity when it is ready rather than enqueue an event with `EventWriter`. This addresses the two outstanding feature requests mentioned on #2218, that i) the events should be "scoped" in some way and ii) that the `InstanceId` should be included in the event. ## Testing Modified the `scene_spawner::tests::event` test to use the new mechanism. --- ## Changelog - Changed `SceneInstanceReady` to trigger an entity observer rather than be written to an event queue. - Changed `SceneInstanceReady` to carry the `InstanceId` of the scene. ## Migration Guide If you have a system which read `SceneInstanceReady` events: > ```fn ready_system(ready_events: EventReader<'_, '_, SceneInstanceReady>) {``` It must be rewritten as an observer: > ```commands.observe(|trigger: Trigger<SceneInstanceReady>| {``` Or, if you were expecting the event in relation to a specific entity or entities, as an entity observer: > ```commands.entity(entity).observe(|trigger: Trigger<SceneInstanceReady>| {```
This commit is contained in:
		
							parent
							
								
									ba09f35474
								
							
						
					
					
						commit
						3d1c9ca87f
					
				| @ -56,7 +56,6 @@ impl Plugin for ScenePlugin { | ||||
|         app.init_asset::<DynamicScene>() | ||||
|             .init_asset::<Scene>() | ||||
|             .init_asset_loader::<SceneLoader>() | ||||
|             .add_event::<SceneInstanceReady>() | ||||
|             .init_resource::<SceneSpawner>() | ||||
|             .add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain()); | ||||
| 
 | ||||
|  | ||||
| @ -13,15 +13,15 @@ use bevy_utils::{tracing::error, HashMap, HashSet}; | ||||
| use thiserror::Error; | ||||
| use uuid::Uuid; | ||||
| 
 | ||||
| /// Emitted when [`crate::SceneInstance`] becomes ready to use.
 | ||||
| /// Triggered on a scene's parent entity when [`crate::SceneInstance`] becomes ready to use.
 | ||||
| ///
 | ||||
| /// See also [`SceneSpawner::instance_is_ready`].
 | ||||
| /// See also [`Trigger`], [`SceneSpawner::instance_is_ready`].
 | ||||
| ///
 | ||||
| /// [`Trigger`]: bevy_ecs::observer::Trigger
 | ||||
| #[derive(Clone, Copy, Debug, Eq, PartialEq, Event)] | ||||
| pub struct SceneInstanceReady { | ||||
|     /// ID of the spawned instance.
 | ||||
|     pub id: InstanceId, | ||||
|     /// Entity to which the scene was spawned as a child.
 | ||||
|     pub parent: Option<Entity>, | ||||
|     /// Instance which has been spawned.
 | ||||
|     pub instance_id: InstanceId, | ||||
| } | ||||
| 
 | ||||
| /// Information about a scene instance.
 | ||||
| @ -323,10 +323,8 @@ impl SceneSpawner { | ||||
|                     // Scenes with parents need more setup before they are ready.
 | ||||
|                     // See `set_scene_instance_parent_sync()`.
 | ||||
|                     if parent.is_none() { | ||||
|                         world.send_event(SceneInstanceReady { | ||||
|                             id: instance_id, | ||||
|                             parent: None, | ||||
|                         }); | ||||
|                         // Defer via commands otherwise SceneSpawner is not available in the observer.
 | ||||
|                         world.commands().trigger(SceneInstanceReady { instance_id }); | ||||
|                     } | ||||
|                 } | ||||
|                 Err(SceneSpawnError::NonExistentScene { .. }) => { | ||||
| @ -350,10 +348,8 @@ impl SceneSpawner { | ||||
|                     // Scenes with parents need more setup before they are ready.
 | ||||
|                     // See `set_scene_instance_parent_sync()`.
 | ||||
|                     if parent.is_none() { | ||||
|                         world.send_event(SceneInstanceReady { | ||||
|                             id: instance_id, | ||||
|                             parent: None, | ||||
|                         }); | ||||
|                         // Defer via commands otherwise SceneSpawner is not available in the observer.
 | ||||
|                         world.commands().trigger(SceneInstanceReady { instance_id }); | ||||
|                     } | ||||
|                 } | ||||
|                 Err(SceneSpawnError::NonExistentRealScene { .. }) => { | ||||
| @ -392,10 +388,10 @@ impl SceneSpawner { | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 world.send_event(SceneInstanceReady { | ||||
|                     id: instance_id, | ||||
|                     parent: Some(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)); | ||||
|             } | ||||
| @ -479,7 +475,7 @@ mod tests { | ||||
|     use bevy_app::App; | ||||
|     use bevy_asset::Handle; | ||||
|     use bevy_asset::{AssetPlugin, AssetServer}; | ||||
|     use bevy_ecs::event::EventReader; | ||||
|     use bevy_ecs::observer::Trigger; | ||||
|     use bevy_ecs::prelude::ReflectComponent; | ||||
|     use bevy_ecs::query::With; | ||||
|     use bevy_ecs::system::{Commands, Res, ResMut, RunSystemOnce}; | ||||
| @ -545,9 +541,13 @@ mod tests { | ||||
|     #[reflect(Component)] | ||||
|     struct ComponentA; | ||||
| 
 | ||||
|     #[derive(Resource, Default)] | ||||
|     struct TriggerCount(u32); | ||||
| 
 | ||||
|     fn setup() -> App { | ||||
|         let mut app = App::new(); | ||||
|         app.add_plugins((AssetPlugin::default(), ScenePlugin)); | ||||
|         app.init_resource::<TriggerCount>(); | ||||
| 
 | ||||
|         app.register_type::<ComponentA>(); | ||||
|         app.world_mut().spawn(ComponentA); | ||||
| @ -569,8 +569,50 @@ mod tests { | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     fn build_dynamic_scene(app: &mut App) -> Handle<DynamicScene> { | ||||
|         app.world_mut() | ||||
|             .run_system_once(|world: &World, asset_server: Res<'_, AssetServer>| { | ||||
|                 asset_server.add(DynamicScene::from_world(world)) | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     fn observe_trigger(app: &mut App, scene_id: InstanceId, scene_entity: Entity) { | ||||
|         // Add observer
 | ||||
|         app.world_mut().observe( | ||||
|             move |trigger: Trigger<SceneInstanceReady>, | ||||
|                   scene_spawner: Res<SceneSpawner>, | ||||
|                   mut trigger_count: ResMut<TriggerCount>| { | ||||
|                 assert_eq!( | ||||
|                     trigger.event().instance_id, | ||||
|                     scene_id, | ||||
|                     "`SceneInstanceReady` contains the wrong `InstanceId`" | ||||
|                 ); | ||||
|                 assert_eq!( | ||||
|                     trigger.entity(), | ||||
|                     scene_entity, | ||||
|                     "`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<TriggerCount>| { | ||||
|                 assert_eq!( | ||||
|                     trigger_count.0, 1, | ||||
|                     "wrong number of `SceneInstanceReady` triggers" | ||||
|                 ); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn event_scene() { | ||||
|     fn observe_scene() { | ||||
|         let mut app = setup(); | ||||
| 
 | ||||
|         // Build scene.
 | ||||
| @ -583,25 +625,30 @@ mod tests { | ||||
|                     scene_spawner.spawn(scene.clone()) | ||||
|                 }); | ||||
| 
 | ||||
|         // Check for event arrival.
 | ||||
|         app.update(); | ||||
|         app.world_mut().run_system_once( | ||||
|             move |mut ev_scene: EventReader<'_, '_, SceneInstanceReady>| { | ||||
|                 let mut events = ev_scene.read(); | ||||
| 
 | ||||
|                 let event = events.next().expect("found no `SceneInstanceReady` event"); | ||||
|                 assert_eq!( | ||||
|                     event.id, scene_id, | ||||
|                     "`SceneInstanceReady` contains the wrong `InstanceId`" | ||||
|                 ); | ||||
| 
 | ||||
|                 assert!(events.next().is_none(), "found more than one event"); | ||||
|             }, | ||||
|         ); | ||||
|         // Check trigger.
 | ||||
|         observe_trigger(&mut app, scene_id, Entity::PLACEHOLDER); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn event_scene_as_child() { | ||||
|     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()) | ||||
|                 }); | ||||
| 
 | ||||
|         // Check trigger.
 | ||||
|         observe_trigger(&mut app, scene_id, Entity::PLACEHOLDER); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn observe_scene_as_child() { | ||||
|         let mut app = setup(); | ||||
| 
 | ||||
|         // Build scene.
 | ||||
| @ -616,68 +663,12 @@ mod tests { | ||||
|             }, | ||||
|         ); | ||||
| 
 | ||||
|         // Check for event arrival.
 | ||||
|         app.update(); | ||||
|         app.world_mut().run_system_once( | ||||
|             move |mut ev_scene: EventReader<'_, '_, SceneInstanceReady>| { | ||||
|                 let mut events = ev_scene.read(); | ||||
| 
 | ||||
|                 let event = events.next().expect("found no `SceneInstanceReady` event"); | ||||
|                 assert_eq!( | ||||
|                     event.id, scene_id, | ||||
|                     "`SceneInstanceReady` contains the wrong `InstanceId`" | ||||
|                 ); | ||||
|                 assert_eq!( | ||||
|                     event.parent, | ||||
|                     Some(scene_entity), | ||||
|                     "`SceneInstanceReady` contains the wrong parent entity" | ||||
|                 ); | ||||
| 
 | ||||
|                 assert!(events.next().is_none(), "found more than one event"); | ||||
|             }, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     fn build_dynamic_scene(app: &mut App) -> Handle<DynamicScene> { | ||||
|         app.world_mut() | ||||
|             .run_system_once(|world: &World, asset_server: Res<'_, AssetServer>| { | ||||
|                 asset_server.add(DynamicScene::from_world(world)) | ||||
|             }) | ||||
|         // Check trigger.
 | ||||
|         observe_trigger(&mut app, scene_id, scene_entity); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn event_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()) | ||||
|                 }); | ||||
| 
 | ||||
|         // Check for event arrival.
 | ||||
|         app.update(); | ||||
|         app.world_mut().run_system_once( | ||||
|             move |mut ev_scene: EventReader<'_, '_, SceneInstanceReady>| { | ||||
|                 let mut events = ev_scene.read(); | ||||
| 
 | ||||
|                 let event = events.next().expect("found no `SceneInstanceReady` event"); | ||||
|                 assert_eq!( | ||||
|                     event.id, scene_id, | ||||
|                     "`SceneInstanceReady` contains the wrong `InstanceId`" | ||||
|                 ); | ||||
| 
 | ||||
|                 assert!(events.next().is_none(), "found more than one event"); | ||||
|             }, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn event_dynamic_scene_as_child() { | ||||
|     fn observe_dynamic_scene_as_child() { | ||||
|         let mut app = setup(); | ||||
| 
 | ||||
|         // Build scene.
 | ||||
| @ -692,26 +683,8 @@ mod tests { | ||||
|             }, | ||||
|         ); | ||||
| 
 | ||||
|         // Check for event arrival.
 | ||||
|         app.update(); | ||||
|         app.world_mut().run_system_once( | ||||
|             move |mut ev_scene: EventReader<'_, '_, SceneInstanceReady>| { | ||||
|                 let mut events = ev_scene.read(); | ||||
| 
 | ||||
|                 let event = events.next().expect("found no `SceneInstanceReady` event"); | ||||
|                 assert_eq!( | ||||
|                     event.id, scene_id, | ||||
|                     "`SceneInstanceReady` contains the wrong `InstanceId`" | ||||
|                 ); | ||||
|                 assert_eq!( | ||||
|                     event.parent, | ||||
|                     Some(scene_entity), | ||||
|                     "`SceneInstanceReady` contains the wrong parent entity" | ||||
|                 ); | ||||
| 
 | ||||
|                 assert!(events.next().is_none(), "found more than one event"); | ||||
|             }, | ||||
|         ); | ||||
|         // Check trigger.
 | ||||
|         observe_trigger(&mut app, scene_id, scene_entity); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Robin KAY
						Robin KAY