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>() |         app.init_asset::<DynamicScene>() | ||||||
|             .init_asset::<Scene>() |             .init_asset::<Scene>() | ||||||
|             .init_asset_loader::<SceneLoader>() |             .init_asset_loader::<SceneLoader>() | ||||||
|             .add_event::<SceneInstanceReady>() |  | ||||||
|             .init_resource::<SceneSpawner>() |             .init_resource::<SceneSpawner>() | ||||||
|             .add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain()); |             .add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain()); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -13,15 +13,15 @@ use bevy_utils::{tracing::error, HashMap, HashSet}; | |||||||
| use thiserror::Error; | use thiserror::Error; | ||||||
| use uuid::Uuid; | 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)] | #[derive(Clone, Copy, Debug, Eq, PartialEq, Event)] | ||||||
| pub struct SceneInstanceReady { | pub struct SceneInstanceReady { | ||||||
|     /// ID of the spawned instance.
 |     /// Instance which has been spawned.
 | ||||||
|     pub id: InstanceId, |     pub instance_id: InstanceId, | ||||||
|     /// Entity to which the scene was spawned as a child.
 |  | ||||||
|     pub parent: Option<Entity>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Information about a scene instance.
 | /// Information about a scene instance.
 | ||||||
| @ -323,10 +323,8 @@ impl SceneSpawner { | |||||||
|                     // Scenes with parents need more setup before they are ready.
 |                     // Scenes with parents need more setup before they are ready.
 | ||||||
|                     // See `set_scene_instance_parent_sync()`.
 |                     // See `set_scene_instance_parent_sync()`.
 | ||||||
|                     if parent.is_none() { |                     if parent.is_none() { | ||||||
|                         world.send_event(SceneInstanceReady { |                         // Defer via commands otherwise SceneSpawner is not available in the observer.
 | ||||||
|                             id: instance_id, |                         world.commands().trigger(SceneInstanceReady { instance_id }); | ||||||
|                             parent: None, |  | ||||||
|                         }); |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 Err(SceneSpawnError::NonExistentScene { .. }) => { |                 Err(SceneSpawnError::NonExistentScene { .. }) => { | ||||||
| @ -350,10 +348,8 @@ impl SceneSpawner { | |||||||
|                     // Scenes with parents need more setup before they are ready.
 |                     // Scenes with parents need more setup before they are ready.
 | ||||||
|                     // See `set_scene_instance_parent_sync()`.
 |                     // See `set_scene_instance_parent_sync()`.
 | ||||||
|                     if parent.is_none() { |                     if parent.is_none() { | ||||||
|                         world.send_event(SceneInstanceReady { |                         // Defer via commands otherwise SceneSpawner is not available in the observer.
 | ||||||
|                             id: instance_id, |                         world.commands().trigger(SceneInstanceReady { instance_id }); | ||||||
|                             parent: None, |  | ||||||
|                         }); |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 Err(SceneSpawnError::NonExistentRealScene { .. }) => { |                 Err(SceneSpawnError::NonExistentRealScene { .. }) => { | ||||||
| @ -392,10 +388,10 @@ impl SceneSpawner { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 world.send_event(SceneInstanceReady { |                 // Defer via commands otherwise SceneSpawner is not available in the observer.
 | ||||||
|                     id: instance_id, |                 world | ||||||
|                     parent: Some(parent), |                     .commands() | ||||||
|                 }); |                     .trigger_targets(SceneInstanceReady { instance_id }, parent); | ||||||
|             } else { |             } else { | ||||||
|                 self.scenes_with_parent.push((instance_id, parent)); |                 self.scenes_with_parent.push((instance_id, parent)); | ||||||
|             } |             } | ||||||
| @ -479,7 +475,7 @@ mod tests { | |||||||
|     use bevy_app::App; |     use bevy_app::App; | ||||||
|     use bevy_asset::Handle; |     use bevy_asset::Handle; | ||||||
|     use bevy_asset::{AssetPlugin, AssetServer}; |     use bevy_asset::{AssetPlugin, AssetServer}; | ||||||
|     use bevy_ecs::event::EventReader; |     use bevy_ecs::observer::Trigger; | ||||||
|     use bevy_ecs::prelude::ReflectComponent; |     use bevy_ecs::prelude::ReflectComponent; | ||||||
|     use bevy_ecs::query::With; |     use bevy_ecs::query::With; | ||||||
|     use bevy_ecs::system::{Commands, Res, ResMut, RunSystemOnce}; |     use bevy_ecs::system::{Commands, Res, ResMut, RunSystemOnce}; | ||||||
| @ -545,9 +541,13 @@ mod tests { | |||||||
|     #[reflect(Component)] |     #[reflect(Component)] | ||||||
|     struct ComponentA; |     struct ComponentA; | ||||||
| 
 | 
 | ||||||
|  |     #[derive(Resource, Default)] | ||||||
|  |     struct TriggerCount(u32); | ||||||
|  | 
 | ||||||
|     fn setup() -> App { |     fn setup() -> App { | ||||||
|         let mut app = App::new(); |         let mut app = App::new(); | ||||||
|         app.add_plugins((AssetPlugin::default(), ScenePlugin)); |         app.add_plugins((AssetPlugin::default(), ScenePlugin)); | ||||||
|  |         app.init_resource::<TriggerCount>(); | ||||||
| 
 | 
 | ||||||
|         app.register_type::<ComponentA>(); |         app.register_type::<ComponentA>(); | ||||||
|         app.world_mut().spawn(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] |     #[test] | ||||||
|     fn event_scene() { |     fn observe_scene() { | ||||||
|         let mut app = setup(); |         let mut app = setup(); | ||||||
| 
 | 
 | ||||||
|         // Build scene.
 |         // Build scene.
 | ||||||
| @ -583,25 +625,30 @@ mod tests { | |||||||
|                     scene_spawner.spawn(scene.clone()) |                     scene_spawner.spawn(scene.clone()) | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|         // Check for event arrival.
 |         // Check trigger.
 | ||||||
|         app.update(); |         observe_trigger(&mut app, scene_id, Entity::PLACEHOLDER); | ||||||
|         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] |     #[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(); |         let mut app = setup(); | ||||||
| 
 | 
 | ||||||
|         // Build scene.
 |         // Build scene.
 | ||||||
| @ -616,68 +663,12 @@ mod tests { | |||||||
|             }, |             }, | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         // Check for event arrival.
 |         // Check trigger.
 | ||||||
|         app.update(); |         observe_trigger(&mut app, scene_id, scene_entity); | ||||||
|         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)) |  | ||||||
|             }) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn event_dynamic_scene() { |     fn observe_dynamic_scene_as_child() { | ||||||
|         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() { |  | ||||||
|         let mut app = setup(); |         let mut app = setup(); | ||||||
| 
 | 
 | ||||||
|         // Build scene.
 |         // Build scene.
 | ||||||
| @ -692,26 +683,8 @@ mod tests { | |||||||
|             }, |             }, | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         // Check for event arrival.
 |         // Check trigger.
 | ||||||
|         app.update(); |         observe_trigger(&mut app, scene_id, scene_entity); | ||||||
|         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"); |  | ||||||
|             }, |  | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Robin KAY
						Robin KAY