Skip to content

Commit

Permalink
can clone a scene (#5855)
Browse files Browse the repository at this point in the history
# 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
  • Loading branch information
mockersf committed Sep 2, 2022
1 parent 79e7c93 commit e804115
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 47 deletions.
4 changes: 2 additions & 2 deletions crates/bevy_scene/src/dynamic_scene.rs
Expand Up @@ -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,
Expand Down
73 changes: 72 additions & 1 deletion 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
Expand All @@ -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)
}
}
51 changes: 7 additions & 44 deletions crates/bevy_scene/src/scene_spawner.rs
Expand Up @@ -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},
};
Expand All @@ -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)]
Expand Down Expand Up @@ -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
Expand All @@ -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::<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();
}
}
let instance_info =
scene.write_to_world_with(world, &world.resource::<AppTypeRegistry>().clone())?;

self.spawned_instances.insert(instance_id, instance_info);
let spawned = self
.spawned_scenes
Expand Down

0 comments on commit e804115

Please sign in to comment.