Merge 43960f69d9
into 877d278785
This commit is contained in:
commit
c54d2c9108
@ -91,12 +91,19 @@ impl Plugin for ScenePlugin {
|
||||
app.world_mut()
|
||||
.register_component_hooks::<SceneRoot>()
|
||||
.on_remove(|mut world, context| {
|
||||
let Some(handle) = world.get::<SceneRoot>(context.entity) else {
|
||||
return;
|
||||
};
|
||||
let id = handle.id();
|
||||
if let Some(&SceneInstance(scene_instance)) =
|
||||
world.get::<SceneInstance>(context.entity)
|
||||
{
|
||||
let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else {
|
||||
return;
|
||||
};
|
||||
if let Some(instance_ids) = scene_spawner.spawned_scenes.get_mut(&id) {
|
||||
instance_ids.remove(&scene_instance);
|
||||
}
|
||||
scene_spawner.unregister_instance(scene_instance);
|
||||
}
|
||||
});
|
||||
@ -107,3 +114,299 @@ impl Plugin for ScenePlugin {
|
||||
impl Plugin for ScenePlugin {
|
||||
fn build(&self, _: &mut App) {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_app::App;
|
||||
use bevy_asset::{AssetPlugin, Assets};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
hierarchy::{ChildOf, Children},
|
||||
reflect::{AppTypeRegistry, ReflectComponent},
|
||||
world::World,
|
||||
};
|
||||
use bevy_reflect::Reflect;
|
||||
|
||||
use crate::{
|
||||
DynamicScene, DynamicSceneBuilder, DynamicSceneRoot, Scene, ScenePlugin, SceneRoot,
|
||||
};
|
||||
|
||||
#[derive(Component, Reflect, PartialEq, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct Circle {
|
||||
radius: f32,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, PartialEq, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct Rectangle {
|
||||
width: f32,
|
||||
height: f32,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, PartialEq, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct Triangle {
|
||||
base: f32,
|
||||
height: f32,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
struct FinishLine;
|
||||
|
||||
#[test]
|
||||
fn scene_spawns_and_respawns_after_change() {
|
||||
let mut app = App::new();
|
||||
|
||||
app.add_plugins((AssetPlugin::default(), ScenePlugin))
|
||||
.register_type::<Circle>()
|
||||
.register_type::<Rectangle>()
|
||||
.register_type::<Triangle>()
|
||||
.register_type::<FinishLine>();
|
||||
|
||||
let scene_handle = app
|
||||
.world_mut()
|
||||
.resource_mut::<Assets<Scene>>()
|
||||
.reserve_handle();
|
||||
|
||||
let scene_entity = app.world_mut().spawn(SceneRoot(scene_handle.clone())).id();
|
||||
app.update();
|
||||
|
||||
assert!(app.world().entity(scene_entity).get::<Children>().is_none());
|
||||
|
||||
let mut scene_1 = Scene {
|
||||
world: World::new(),
|
||||
};
|
||||
let root = scene_1.world.spawn_empty().id();
|
||||
scene_1.world.spawn((
|
||||
Rectangle {
|
||||
width: 10.0,
|
||||
height: 5.0,
|
||||
},
|
||||
FinishLine,
|
||||
ChildOf(root),
|
||||
));
|
||||
scene_1.world.spawn((Circle { radius: 7.0 }, ChildOf(root)));
|
||||
|
||||
app.world_mut()
|
||||
.resource_mut::<Assets<Scene>>()
|
||||
.insert(&scene_handle, scene_1);
|
||||
|
||||
app.update();
|
||||
|
||||
let child_root = app
|
||||
.world()
|
||||
.entity(scene_entity)
|
||||
.get::<Children>()
|
||||
.and_then(|children| children.first().cloned())
|
||||
.expect("There should be exactly one child on the scene root");
|
||||
let children = app
|
||||
.world()
|
||||
.entity(child_root)
|
||||
.get::<Children>()
|
||||
.expect("The child of the scene root should itself have 2 children");
|
||||
assert_eq!(children.len(), 2);
|
||||
|
||||
let finish_line = app.world().entity(children[0]);
|
||||
assert_eq!(finish_line.archetype().component_count(), 3);
|
||||
let (rectangle, _, child_of) =
|
||||
finish_line.components::<(&Rectangle, &FinishLine, &ChildOf)>();
|
||||
assert_eq!(
|
||||
rectangle,
|
||||
&Rectangle {
|
||||
width: 10.0,
|
||||
height: 5.0,
|
||||
}
|
||||
);
|
||||
assert_eq!(child_of.0, child_root);
|
||||
|
||||
let circle = app.world().entity(children[1]);
|
||||
assert_eq!(circle.archetype().component_count(), 2);
|
||||
let (circle, child_of) = circle.components::<(&Circle, &ChildOf)>();
|
||||
assert_eq!(circle, &Circle { radius: 7.0 });
|
||||
assert_eq!(child_of.0, child_root);
|
||||
|
||||
// Now that we know our scene contains exactly what we expect, we will change the scene
|
||||
// asset and ensure it contains the new scene results.
|
||||
|
||||
let mut scene_2 = Scene {
|
||||
world: World::new(),
|
||||
};
|
||||
let root = scene_2.world.spawn_empty().id();
|
||||
scene_2.world.spawn((
|
||||
Triangle {
|
||||
base: 1.0,
|
||||
height: 2.0,
|
||||
},
|
||||
ChildOf(root),
|
||||
));
|
||||
|
||||
app.world_mut()
|
||||
.resource_mut::<Assets<Scene>>()
|
||||
.insert(&scene_handle, scene_2);
|
||||
|
||||
app.update();
|
||||
app.update();
|
||||
|
||||
let child_root = app
|
||||
.world()
|
||||
.entity(scene_entity)
|
||||
.get::<Children>()
|
||||
.and_then(|children| children.first().cloned())
|
||||
.expect("There should be exactly one child on the scene root");
|
||||
let children = app
|
||||
.world()
|
||||
.entity(child_root)
|
||||
.get::<Children>()
|
||||
.expect("The child of the scene root should itself have 2 children");
|
||||
assert_eq!(children.len(), 1);
|
||||
|
||||
let triangle = app.world().entity(children[0]);
|
||||
assert_eq!(triangle.archetype().component_count(), 2);
|
||||
let (triangle, child_of) = triangle.components::<(&Triangle, &ChildOf)>();
|
||||
assert_eq!(
|
||||
triangle,
|
||||
&Triangle {
|
||||
base: 1.0,
|
||||
height: 2.0,
|
||||
}
|
||||
);
|
||||
assert_eq!(child_of.0, child_root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_scene_spawns_and_respawns_after_change() {
|
||||
let mut app = App::new();
|
||||
|
||||
app.add_plugins((AssetPlugin::default(), ScenePlugin))
|
||||
.register_type::<Circle>()
|
||||
.register_type::<Rectangle>()
|
||||
.register_type::<Triangle>()
|
||||
.register_type::<FinishLine>();
|
||||
|
||||
let scene_handle = app
|
||||
.world_mut()
|
||||
.resource_mut::<Assets<DynamicScene>>()
|
||||
.reserve_handle();
|
||||
|
||||
let scene_entity = app
|
||||
.world_mut()
|
||||
.spawn(DynamicSceneRoot(scene_handle.clone()))
|
||||
.id();
|
||||
app.update();
|
||||
|
||||
assert!(app.world().entity(scene_entity).get::<Children>().is_none());
|
||||
|
||||
let create_dynamic_scene = |mut scene: Scene, world: &World| {
|
||||
scene
|
||||
.world
|
||||
.insert_resource(world.resource::<AppTypeRegistry>().clone());
|
||||
DynamicSceneBuilder::from_world(&scene.world)
|
||||
.extract_entities(scene.world.iter_entities().map(|entity| entity.id()))
|
||||
.build()
|
||||
};
|
||||
|
||||
let mut scene_1 = Scene {
|
||||
world: World::new(),
|
||||
};
|
||||
let root = scene_1.world.spawn_empty().id();
|
||||
scene_1.world.spawn((
|
||||
Rectangle {
|
||||
width: 10.0,
|
||||
height: 5.0,
|
||||
},
|
||||
FinishLine,
|
||||
ChildOf(root),
|
||||
));
|
||||
scene_1.world.spawn((Circle { radius: 7.0 }, ChildOf(root)));
|
||||
|
||||
let scene_1 = create_dynamic_scene(scene_1, app.world());
|
||||
app.world_mut()
|
||||
.resource_mut::<Assets<DynamicScene>>()
|
||||
.insert(&scene_handle, scene_1);
|
||||
|
||||
app.update();
|
||||
|
||||
let child_root = app
|
||||
.world()
|
||||
.entity(scene_entity)
|
||||
.get::<Children>()
|
||||
.and_then(|children| children.first().cloned())
|
||||
.expect("There should be exactly one child on the scene root");
|
||||
let children = app
|
||||
.world()
|
||||
.entity(child_root)
|
||||
.get::<Children>()
|
||||
.expect("The child of the scene root should itself have 2 children");
|
||||
assert_eq!(children.len(), 2);
|
||||
|
||||
let finish_line = app.world().entity(children[0]);
|
||||
assert_eq!(finish_line.archetype().component_count(), 3);
|
||||
let (rectangle, _, child_of) =
|
||||
finish_line.components::<(&Rectangle, &FinishLine, &ChildOf)>();
|
||||
assert_eq!(
|
||||
rectangle,
|
||||
&Rectangle {
|
||||
width: 10.0,
|
||||
height: 5.0,
|
||||
}
|
||||
);
|
||||
assert_eq!(child_of.0, child_root);
|
||||
|
||||
let circle = app.world().entity(children[1]);
|
||||
assert_eq!(circle.archetype().component_count(), 2);
|
||||
let (circle, child_of) = circle.components::<(&Circle, &ChildOf)>();
|
||||
assert_eq!(circle, &Circle { radius: 7.0 });
|
||||
assert_eq!(child_of.0, child_root);
|
||||
|
||||
// Now that we know our scene contains exactly what we expect, we will change the scene
|
||||
// asset and ensure it contains the new scene results.
|
||||
|
||||
let mut scene_2 = Scene {
|
||||
world: World::new(),
|
||||
};
|
||||
let root = scene_2.world.spawn_empty().id();
|
||||
scene_2.world.spawn((
|
||||
Triangle {
|
||||
base: 1.0,
|
||||
height: 2.0,
|
||||
},
|
||||
ChildOf(root),
|
||||
));
|
||||
|
||||
let scene_2 = create_dynamic_scene(scene_2, app.world());
|
||||
|
||||
app.world_mut()
|
||||
.resource_mut::<Assets<DynamicScene>>()
|
||||
.insert(&scene_handle, scene_2);
|
||||
|
||||
app.update();
|
||||
app.update();
|
||||
|
||||
let child_root = app
|
||||
.world()
|
||||
.entity(scene_entity)
|
||||
.get::<Children>()
|
||||
.and_then(|children| children.first().cloned())
|
||||
.expect("There should be exactly one child on the scene root");
|
||||
let children = app
|
||||
.world()
|
||||
.entity(child_root)
|
||||
.get::<Children>()
|
||||
.expect("The child of the scene root should itself have 2 children");
|
||||
assert_eq!(children.len(), 1);
|
||||
|
||||
let triangle = app.world().entity(children[0]);
|
||||
assert_eq!(triangle.archetype().component_count(), 2);
|
||||
let (triangle, child_of) = triangle.components::<(&Triangle, &ChildOf)>();
|
||||
assert_eq!(
|
||||
triangle,
|
||||
&Triangle {
|
||||
base: 1.0,
|
||||
height: 2.0,
|
||||
}
|
||||
);
|
||||
assert_eq!(child_of.0, child_root);
|
||||
}
|
||||
}
|
||||
|
@ -36,9 +36,11 @@ pub struct SceneInstanceReady {
|
||||
|
||||
/// Information about a scene instance.
|
||||
#[derive(Debug)]
|
||||
pub struct InstanceInfo {
|
||||
struct InstanceInfo {
|
||||
/// Mapping of entities from the scene world to the instance world.
|
||||
pub entity_map: EntityHashMap<Entity>,
|
||||
entity_map: EntityHashMap<Entity>,
|
||||
/// The parent to attach this instance to.
|
||||
parent: Option<Entity>,
|
||||
}
|
||||
|
||||
/// Unique id identifying a scene instance.
|
||||
@ -55,11 +57,13 @@ impl InstanceId {
|
||||
/// 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_dynamic_sync`](Self::spawn_dynamic_sync)
|
||||
/// - [`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)
|
||||
@ -70,17 +74,20 @@ impl InstanceId {
|
||||
/// - [`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<AssetId<Scene>, HashSet<InstanceId>>,
|
||||
pub(crate) spawned_dynamic_scenes: HashMap<AssetId<DynamicScene>, HashSet<InstanceId>>,
|
||||
pub(crate) spawned_instances: HashMap<InstanceId, InstanceInfo>,
|
||||
scene_asset_event_reader: EventCursor<AssetEvent<DynamicScene>>,
|
||||
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId, Option<Entity>)>,
|
||||
spawned_instances: HashMap<InstanceId, InstanceInfo>,
|
||||
scene_asset_event_reader: EventCursor<AssetEvent<Scene>>,
|
||||
dynamic_scene_asset_event_reader: EventCursor<AssetEvent<DynamicScene>>,
|
||||
scenes_to_spawn: Vec<(Handle<Scene>, InstanceId, Option<Entity>)>,
|
||||
scenes_to_despawn: Vec<AssetId<DynamicScene>>,
|
||||
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId, Option<Entity>)>,
|
||||
scenes_to_despawn: Vec<AssetId<Scene>>,
|
||||
dynamic_scenes_to_despawn: Vec<AssetId<DynamicScene>>,
|
||||
instances_to_despawn: Vec<InstanceId>,
|
||||
scenes_with_parent: Vec<(InstanceId, Entity)>,
|
||||
instances_ready: Vec<(InstanceId, Option<Entity>)>,
|
||||
}
|
||||
|
||||
@ -156,7 +163,6 @@ impl SceneSpawner {
|
||||
let instance_id = InstanceId::new();
|
||||
self.dynamic_scenes_to_spawn
|
||||
.push((id.into(), instance_id, Some(parent)));
|
||||
self.scenes_with_parent.push((instance_id, parent));
|
||||
instance_id
|
||||
}
|
||||
|
||||
@ -172,15 +178,19 @@ impl SceneSpawner {
|
||||
let instance_id = InstanceId::new();
|
||||
self.scenes_to_spawn
|
||||
.push((id.into(), instance_id, Some(parent)));
|
||||
self.scenes_with_parent.push((instance_id, parent));
|
||||
instance_id
|
||||
}
|
||||
|
||||
/// Schedule the despawn of all instances of the provided dynamic scene.
|
||||
pub fn despawn(&mut self, id: impl Into<AssetId<DynamicScene>>) {
|
||||
/// Schedule the despawn of all instances of the provided scene.
|
||||
pub fn despawn(&mut self, id: impl Into<AssetId<Scene>>) {
|
||||
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<AssetId<DynamicScene>>) {
|
||||
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
|
||||
@ -195,8 +205,22 @@ impl SceneSpawner {
|
||||
self.spawned_instances.remove(&instance_id);
|
||||
}
|
||||
|
||||
/// Immediately despawns all instances of a dynamic scene.
|
||||
/// Immediately despawns all instances of a scene.
|
||||
pub fn despawn_sync(
|
||||
&mut self,
|
||||
world: &mut World,
|
||||
id: impl Into<AssetId<Scene>>,
|
||||
) -> 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<AssetId<DynamicScene>>,
|
||||
@ -211,15 +235,21 @@ impl SceneSpawner {
|
||||
|
||||
/// 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(instance) = self.spawned_instances.remove(instance_id) {
|
||||
for &entity in instance.entity_map.values() {
|
||||
if let Ok(entity_mut) = world.get_entity_mut(entity) {
|
||||
entity_mut.despawn();
|
||||
};
|
||||
}
|
||||
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,
|
||||
@ -230,10 +260,18 @@ impl SceneSpawner {
|
||||
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 });
|
||||
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)
|
||||
}
|
||||
|
||||
@ -261,8 +299,18 @@ impl SceneSpawner {
|
||||
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 });
|
||||
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)
|
||||
}
|
||||
|
||||
@ -286,8 +334,39 @@ impl SceneSpawner {
|
||||
|
||||
/// 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.
|
||||
/// 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<Scene>],
|
||||
) -> 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<DynamicScene>],
|
||||
@ -296,7 +375,16 @@ impl SceneSpawner {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -307,10 +395,13 @@ impl SceneSpawner {
|
||||
/// 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(())
|
||||
}
|
||||
|
||||
@ -332,18 +423,15 @@ impl SceneSpawner {
|
||||
|
||||
match Self::spawn_dynamic_internal(world, handle.id(), &mut entity_map) {
|
||||
Ok(_) => {
|
||||
self.spawned_instances
|
||||
.insert(instance_id, InstanceInfo { entity_map });
|
||||
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);
|
||||
|
||||
// Scenes with parents need more setup before they are ready.
|
||||
// See `set_scene_instance_parent_sync()`.
|
||||
if parent.is_none() {
|
||||
// We trigger `SceneInstanceReady` events after processing all scenes
|
||||
// SceneSpawner may not be available in the observer.
|
||||
self.instances_ready.push((instance_id, None));
|
||||
}
|
||||
// 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
|
||||
@ -360,16 +448,16 @@ impl SceneSpawner {
|
||||
|
||||
match Self::spawn_sync_internal(world, scene_handle.id(), &mut entity_map) {
|
||||
Ok(_) => {
|
||||
self.spawned_instances
|
||||
.insert(instance_id, InstanceInfo { entity_map });
|
||||
let instance_info = InstanceInfo { entity_map, parent };
|
||||
Self::set_scene_instance_parent_sync(world, &instance_info);
|
||||
|
||||
// Scenes with parents need more setup before they are ready.
|
||||
// See `set_scene_instance_parent_sync()`.
|
||||
if parent.is_none() {
|
||||
// We trigger `SceneInstanceReady` events after processing all scenes
|
||||
// SceneSpawner may not be available in the observer.
|
||||
self.instances_ready.push((instance_id, None));
|
||||
}
|
||||
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
|
||||
@ -382,32 +470,23 @@ impl SceneSpawner {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn set_scene_instance_parent_sync(&mut self, world: &mut World) {
|
||||
let scenes_with_parent = core::mem::take(&mut self.scenes_with_parent);
|
||||
|
||||
for (instance_id, parent) in scenes_with_parent {
|
||||
if let Some(instance) = self.spawned_instances.get(&instance_id) {
|
||||
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::<ChildOf>())
|
||||
{
|
||||
world.entity_mut(parent).add_child(entity);
|
||||
}
|
||||
}
|
||||
|
||||
// We trigger `SceneInstanceReady` events after processing all scenes
|
||||
// SceneSpawner may not be available in the observer.
|
||||
self.instances_ready.push((instance_id, Some(parent)));
|
||||
} else {
|
||||
self.scenes_with_parent.push((instance_id, parent));
|
||||
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::<ChildOf>())
|
||||
{
|
||||
world.entity_mut(parent).add_child(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -452,39 +531,44 @@ impl SceneSpawner {
|
||||
pub fn scene_spawner_system(world: &mut World) {
|
||||
world.resource_scope(|world, mut scene_spawner: Mut<SceneSpawner>| {
|
||||
// remove any loading instances where parent is deleted
|
||||
let mut dead_instances = <HashSet<_>>::default();
|
||||
scene_spawner
|
||||
.scenes_with_parent
|
||||
.retain(|(instance, parent)| {
|
||||
let retain = world.get_entity(*parent).is_ok();
|
||||
|
||||
if !retain {
|
||||
dead_instances.insert(*instance);
|
||||
}
|
||||
|
||||
retain
|
||||
});
|
||||
let is_parent_alive = |parent: &Option<Entity>| {
|
||||
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(|(_, instance, _)| !dead_instances.contains(instance));
|
||||
.retain(|(_, _, parent)| is_parent_alive(parent));
|
||||
scene_spawner
|
||||
.scenes_to_spawn
|
||||
.retain(|(_, instance, _)| !dead_instances.contains(instance));
|
||||
.retain(|(_, _, parent)| is_parent_alive(parent));
|
||||
|
||||
let scene_asset_events = world.resource::<Events<AssetEvent<DynamicScene>>>();
|
||||
let scene_asset_events = world.resource::<Events<AssetEvent<Scene>>>();
|
||||
let dynamic_scene_asset_events = world.resource::<Events<AssetEvent<DynamicScene>>>();
|
||||
let scene_spawner = &mut *scene_spawner;
|
||||
|
||||
let mut updated_spawned_scenes = Vec::new();
|
||||
let scene_spawner = &mut *scene_spawner;
|
||||
for event in scene_spawner
|
||||
.scene_asset_event_reader
|
||||
.read(scene_asset_events)
|
||||
{
|
||||
if let AssetEvent::Modified { id } = event {
|
||||
if scene_spawner.spawned_dynamic_scenes.contains_key(id) {
|
||||
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);
|
||||
@ -494,7 +578,9 @@ pub fn scene_spawner_system(world: &mut World) {
|
||||
scene_spawner
|
||||
.update_spawned_scenes(world, &updated_spawned_scenes)
|
||||
.unwrap();
|
||||
scene_spawner.set_scene_instance_parent_sync(world);
|
||||
scene_spawner
|
||||
.update_spawned_dynamic_scenes(world, &updated_spawned_dynamic_scenes)
|
||||
.unwrap();
|
||||
scene_spawner.trigger_scene_ready_events(world);
|
||||
});
|
||||
}
|
||||
@ -615,7 +701,7 @@ mod tests {
|
||||
|
||||
// let's try to delete the scene
|
||||
let mut scene_spawner = app.world_mut().resource_mut::<SceneSpawner>();
|
||||
scene_spawner.despawn(&scene_handle);
|
||||
scene_spawner.despawn_dynamic(&scene_handle);
|
||||
|
||||
// run the scene spawner system to despawn the scene
|
||||
app.update();
|
||||
|
12
release-content/migration-guides/scene_spawner_api.md
Normal file
12
release-content/migration-guides/scene_spawner_api.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: `SceneSpawner` methods have been renamed and replaced.
|
||||
pull_requests: [18358]
|
||||
---
|
||||
|
||||
Some methods on `SceneSpawner` have been renamed:
|
||||
- `despawn` -> `despawn_dynamic`
|
||||
- `despawn_sync` -> `despawn_dynamic_sync`
|
||||
- `update_spawned_scenes` -> `update_spawned_dynamic_scenes`
|
||||
|
||||
In their place, we've added `despawn`, `despawn_sync`, and `update_spawned_scenes` which all act on
|
||||
`Scene`s (as opposed to `DynamicScene`s).
|
Loading…
Reference in New Issue
Block a user