From e8041150eed08354b3806502a4fc07b6686c232f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Fri, 2 Sep 2022 11:56:21 +0000 Subject: [PATCH] can clone a scene (#5855) # Objective - Easier to work with model assets - Models are often one mesh, many textures. This can be hard to use in Bevy as it's not possible to clone the scene to have one scene for each material. It's still possible to instantiate the texture-less scene, then modify the texture material once spawned but that means happening during play and is quite more painful ## Solution - Expose the code to clone a scene. This code already existed but was only possible to use to spawn the scene --- crates/bevy_scene/src/dynamic_scene.rs | 4 +- crates/bevy_scene/src/scene.rs | 73 +++++++++++++++++++++++++- crates/bevy_scene/src/scene_spawner.rs | 51 +++--------------- 3 files changed, 81 insertions(+), 47 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 1de7202bfab4e..7a292aaf69666 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -78,8 +78,8 @@ impl DynamicScene { /// Write the dynamic entities and their corresponding components to the given world. /// - /// This method will return a `SceneSpawnError` if either a type is not registered - /// or doesn't reflect the `Component` trait. + /// This method will return a [`SceneSpawnError`] if a type either is not registered + /// or doesn't reflect the [`Component`](bevy_ecs::component::Component) trait. pub fn write_to_world( &self, world: &mut World, diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 4cd871fa7283a..1e67402a11ad9 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -1,6 +1,13 @@ -use bevy_ecs::world::World; +use bevy_app::AppTypeRegistry; +use bevy_ecs::{ + entity::EntityMap, + reflect::{ReflectComponent, ReflectMapEntities}, + world::World, +}; use bevy_reflect::TypeUuid; +use crate::{InstanceInfo, SceneSpawnError}; + /// To spawn a scene, you can use either: /// * [`SceneSpawner::spawn`](crate::SceneSpawner::spawn) /// * adding the [`SceneBundle`](crate::SceneBundle) to an entity @@ -17,4 +24,68 @@ impl Scene { pub fn new(world: World) -> Self { Self { world } } + + /// Clone the scene. + /// + /// This method will return a [`SceneSpawnError`] if a type either is not registered in the + /// provided [`AppTypeRegistry`] or doesn't reflect the [`Component`](bevy_ecs::component::Component) trait. + pub fn clone_with(&self, type_registry: &AppTypeRegistry) -> Result { + let mut new_world = World::new(); + self.write_to_world_with(&mut new_world, type_registry)?; + Ok(Self { world: new_world }) + } + + /// Write the entities and their corresponding components to the given world. + /// + /// This method will return a [`SceneSpawnError`] if a type either is not registered in the + /// provided [`AppTypeRegistry`] or doesn't reflect the [`Component`](bevy_ecs::component::Component) trait. + pub fn write_to_world_with( + &self, + world: &mut World, + type_registry: &AppTypeRegistry, + ) -> Result { + let mut instance_info = InstanceInfo { + entity_map: EntityMap::default(), + }; + + let type_registry = type_registry.read(); + for archetype in self.world.archetypes().iter() { + for scene_entity in archetype.entities() { + let entity = *instance_info + .entity_map + .entry(*scene_entity) + .or_insert_with(|| world.spawn().id()); + for component_id in archetype.components() { + let component_info = self + .world + .components() + .get_info(component_id) + .expect("component_ids in archetypes should have ComponentInfo"); + + let reflect_component = type_registry + .get(component_info.type_id().unwrap()) + .ok_or_else(|| SceneSpawnError::UnregisteredType { + type_name: component_info.name().to_string(), + }) + .and_then(|registration| { + registration.data::().ok_or_else(|| { + SceneSpawnError::UnregisteredComponent { + type_name: component_info.name().to_string(), + } + }) + })?; + reflect_component.copy(&self.world, world, *scene_entity, entity); + } + } + } + for registration in type_registry.iter() { + if let Some(map_entities_reflect) = registration.data::() { + map_entities_reflect + .map_entities(world, &instance_info.entity_map) + .unwrap(); + } + } + + Ok(instance_info) + } } diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index d9bfcb213687e..73eaa93e4cd18 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -4,7 +4,6 @@ use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_ecs::{ entity::{Entity, EntityMap}, event::{Events, ManualEventReader}, - reflect::{ReflectComponent, ReflectMapEntities}, system::{Command, Resource}, world::{Mut, World}, }; @@ -13,9 +12,11 @@ use bevy_utils::{tracing::error, HashMap}; use thiserror::Error; use uuid::Uuid; +/// Informations about a scene instance. #[derive(Debug)] -struct InstanceInfo { - entity_map: EntityMap, +pub struct InstanceInfo { + /// Mapping of entities from the scene world to the instance world. + pub entity_map: EntityMap, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -161,11 +162,6 @@ impl SceneSpawner { scene_handle: Handle, instance_id: InstanceId, ) -> Result { - let mut instance_info = InstanceInfo { - entity_map: EntityMap::default(), - }; - let type_registry = world.resource::().clone(); - let type_registry = type_registry.read(); world.resource_scope(|world, scenes: Mut>| { let scene = scenes @@ -174,42 +170,9 @@ impl SceneSpawner { handle: scene_handle.clone(), })?; - for archetype in scene.world.archetypes().iter() { - for scene_entity in archetype.entities() { - let entity = *instance_info - .entity_map - .entry(*scene_entity) - .or_insert_with(|| world.spawn().id()); - for component_id in archetype.components() { - let component_info = scene - .world - .components() - .get_info(component_id) - .expect("component_ids in archetypes should have ComponentInfo"); - - let reflect_component = type_registry - .get(component_info.type_id().unwrap()) - .ok_or_else(|| SceneSpawnError::UnregisteredType { - type_name: component_info.name().to_string(), - }) - .and_then(|registration| { - registration.data::().ok_or_else(|| { - SceneSpawnError::UnregisteredComponent { - type_name: component_info.name().to_string(), - } - }) - })?; - reflect_component.copy(&scene.world, world, *scene_entity, entity); - } - } - } - for registration in type_registry.iter() { - if let Some(map_entities_reflect) = registration.data::() { - map_entities_reflect - .map_entities(world, &instance_info.entity_map) - .unwrap(); - } - } + let instance_info = + scene.write_to_world_with(world, &world.resource::().clone())?; + self.spawned_instances.insert(instance_id, instance_info); let spawned = self .spawned_scenes