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
This commit is contained in:
François 2022-09-02 11:56:21 +00:00
parent 79e7c93060
commit e8041150ee
3 changed files with 80 additions and 46 deletions

View File

@ -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,

View File

@ -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<Scene, SceneSpawnError> {
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<InstanceInfo, SceneSpawnError> {
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::<ReflectComponent>().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::<ReflectMapEntities>() {
map_entities_reflect
.map_entities(world, &instance_info.entity_map)
.unwrap();
}
}
Ok(instance_info)
}
}

View File

@ -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<Scene>,
instance_id: InstanceId,
) -> Result<InstanceId, SceneSpawnError> {
let mut instance_info = InstanceInfo {
entity_map: EntityMap::default(),
};
let type_registry = world.resource::<AppTypeRegistry>().clone();
let type_registry = type_registry.read();
world.resource_scope(|world, scenes: Mut<Assets<Scene>>| {
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 instance_info =
scene.write_to_world_with(world, &world.resource::<AppTypeRegistry>().clone())?;
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::<ReflectComponent>().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::<ReflectMapEntities>() {
map_entities_reflect
.map_entities(world, &instance_info.entity_map)
.unwrap();
}
}
self.spawned_instances.insert(instance_id, instance_info);
let spawned = self
.spawned_scenes