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