Skip to content

Commit

Permalink
Send SceneInstanceReady when spawning any kind of scene (#11741)
Browse files Browse the repository at this point in the history
# Objective

- Emit an event regardless of scene type (`Scene` and `DynamicScene`).
- Also send the `InstanceId` along.

Follow-up to #11002.
Fixes #2218.

## Solution

- Send `SceneInstanceReady` regardless of scene type.
- Make `SceneInstanceReady::parent` `Option`al.
- Add `SceneInstanceReady::id`.

---

## Changelog

### Changed

- `SceneInstanceReady` is now sent for `Scene` as well.
`SceneInstanceReady::parent` is an `Option` and
`SceneInstanceReady::id`, an `InstanceId`, is added to identify the
corresponding `Scene`.

## Migration Guide

- `SceneInstanceReady { parent: Entity }` is now `SceneInstanceReady {
id: InstanceId, parent: Option<Entity> }`.
  • Loading branch information
daxpedda authored Jul 6, 2024
1 parent 1ceb455 commit ea2a7e5
Showing 1 changed file with 183 additions and 28 deletions.
211 changes: 183 additions & 28 deletions crates/bevy_scene/src/scene_spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ use uuid::Uuid;
/// See also [`SceneSpawner::instance_is_ready`].
#[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: Entity,
pub parent: Option<Entity>,
}

/// Information about a scene instance.
Expand Down Expand Up @@ -63,8 +65,8 @@ pub struct SceneSpawner {
pub(crate) spawned_dynamic_scenes: HashMap<AssetId<DynamicScene>, HashSet<InstanceId>>,
pub(crate) spawned_instances: HashMap<InstanceId, InstanceInfo>,
scene_asset_event_reader: ManualEventReader<AssetEvent<DynamicScene>>,
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId)>,
scenes_to_spawn: Vec<(Handle<Scene>, InstanceId)>,
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId, Option<Entity>)>,
scenes_to_spawn: Vec<(Handle<Scene>, InstanceId, Option<Entity>)>,
scenes_to_despawn: Vec<AssetId<DynamicScene>>,
instances_to_despawn: Vec<InstanceId>,
scenes_with_parent: Vec<(InstanceId, Entity)>,
Expand Down Expand Up @@ -128,7 +130,8 @@ impl SceneSpawner {
/// Schedule the spawn of a new instance of the provided dynamic scene.
pub fn spawn_dynamic(&mut self, id: impl Into<Handle<DynamicScene>>) -> InstanceId {
let instance_id = InstanceId::new();
self.dynamic_scenes_to_spawn.push((id.into(), instance_id));
self.dynamic_scenes_to_spawn
.push((id.into(), instance_id, None));
instance_id
}

Expand All @@ -139,22 +142,24 @@ impl SceneSpawner {
parent: Entity,
) -> InstanceId {
let instance_id = InstanceId::new();
self.dynamic_scenes_to_spawn.push((id.into(), instance_id));
self.dynamic_scenes_to_spawn
.push((id.into(), instance_id, Some(parent)));
self.scenes_with_parent.push((instance_id, parent));
instance_id
}

/// Schedule the spawn of a new instance of the provided scene.
pub fn spawn(&mut self, id: impl Into<Handle<Scene>>) -> InstanceId {
let instance_id = InstanceId::new();
self.scenes_to_spawn.push((id.into(), instance_id));
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<Handle<Scene>>, parent: Entity) -> InstanceId {
let instance_id = InstanceId::new();
self.scenes_to_spawn.push((id.into(), instance_id));
self.scenes_to_spawn
.push((id.into(), instance_id, Some(parent)));
self.scenes_with_parent.push((instance_id, parent));
instance_id
}
Expand Down Expand Up @@ -296,7 +301,7 @@ impl SceneSpawner {
pub fn spawn_queued_scenes(&mut self, world: &mut World) -> Result<(), SceneSpawnError> {
let scenes_to_spawn = std::mem::take(&mut self.dynamic_scenes_to_spawn);

for (handle, instance_id) in 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) {
Expand All @@ -308,21 +313,41 @@ impl SceneSpawner {
.entry(handle.id())
.or_insert_with(HashSet::new);
spawned.insert(instance_id);

// 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,
});
}
}
Err(SceneSpawnError::NonExistentScene { .. }) => {
self.dynamic_scenes_to_spawn.push((handle, instance_id));
self.dynamic_scenes_to_spawn
.push((handle, instance_id, parent));
}
Err(err) => return Err(err),
}
}

let scenes_to_spawn = std::mem::take(&mut self.scenes_to_spawn);

for (scene_handle, instance_id) in scenes_to_spawn {
for (scene_handle, instance_id, parent) in scenes_to_spawn {
match self.spawn_sync_internal(world, scene_handle.id(), instance_id) {
Ok(_) => {}
Ok(_) => {
// 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,
});
}
}
Err(SceneSpawnError::NonExistentRealScene { .. }) => {
self.scenes_to_spawn.push((scene_handle, instance_id));
self.scenes_to_spawn
.push((scene_handle, instance_id, parent));
}
Err(err) => return Err(err),
}
Expand Down Expand Up @@ -356,7 +381,10 @@ impl SceneSpawner {
}
}

world.send_event(SceneInstanceReady { parent });
world.send_event(SceneInstanceReady {
id: instance_id,
parent: Some(parent),
});
} else {
self.scenes_with_parent.push((instance_id, parent));
}
Expand Down Expand Up @@ -403,10 +431,10 @@ pub fn scene_spawner_system(world: &mut World) {
});
scene_spawner
.dynamic_scenes_to_spawn
.retain(|(_, instance)| !dead_instances.contains(instance));
.retain(|(_, instance, _)| !dead_instances.contains(instance));
scene_spawner
.scenes_to_spawn
.retain(|(_, instance)| !dead_instances.contains(instance));
.retain(|(_, instance, _)| !dead_instances.contains(instance));

let scene_asset_events = world.resource::<Events<AssetEvent<DynamicScene>>>();

Expand Down Expand Up @@ -438,6 +466,7 @@ pub fn scene_spawner_system(world: &mut World) {
#[cfg(test)]
mod tests {
use bevy_app::App;
use bevy_asset::Handle;
use bevy_asset::{AssetPlugin, AssetServer};
use bevy_ecs::event::EventReader;
use bevy_ecs::prelude::ReflectComponent;
Expand Down Expand Up @@ -505,28 +534,150 @@ mod tests {
#[reflect(Component)]
struct ComponentA;

#[test]
fn event() {
fn setup() -> App {
let mut app = App::new();
app.add_plugins((AssetPlugin::default(), ScenePlugin));

app.register_type::<ComponentA>();
app.world_mut().spawn(ComponentA);
app.world_mut().spawn(ComponentA);

app
}

fn build_scene(app: &mut App) -> Handle<Scene> {
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(),
)
},
)
}

#[test]
fn event_scene() {
let mut app = setup();

// Build scene.
let scene =
let scene = build_scene(&mut app);

// Spawn scene.
let scene_id =
app.world_mut()
.run_system_once(|world: &World, asset_server: Res<'_, AssetServer>| {
asset_server.add(DynamicScene::from_world(world))
.run_system_once(move |mut scene_spawner: ResMut<'_, SceneSpawner>| {
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");
},
);
}

#[test]
fn event_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)
},
);

// 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))
})
}

#[test]
fn event_dynamic_scene() {
let mut app = setup();

// Build scene.
let scene = build_dynamic_scene(&mut app);

// Spawn scene.
let scene_entity = app.world_mut().run_system_once(
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();

// 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 scene_entity = commands.spawn_empty().id();
scene_spawner.spawn_dynamic_as_child(scene.clone(), scene_entity);
scene_entity
let entity = commands.spawn_empty().id();
let id = scene_spawner.spawn_dynamic_as_child(scene.clone(), entity);
(id, entity)
},
);

Expand All @@ -536,13 +687,17 @@ mod tests {
move |mut ev_scene: EventReader<'_, '_, SceneInstanceReady>| {
let mut events = ev_scene.read();

let event = events.next().expect("found no `SceneInstanceReady` event");
assert_eq!(
events.next().expect("found no `SceneInstanceReady` event"),
&SceneInstanceReady {
parent: scene_entity
},
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");
},
);
Expand Down

0 comments on commit ea2a7e5

Please sign in to comment.