Skip to content

Commit

Permalink
Change ReflectMapEntities to operate on components before insertion
Browse files Browse the repository at this point in the history
  • Loading branch information
jrobsonchase committed Jul 31, 2024
1 parent d61c067 commit 255e846
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 102 deletions.
13 changes: 13 additions & 0 deletions crates/bevy_ecs/src/entity/map_entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ impl<T: EntityMapper> DynEntityMapper for T {
}
}

/// Helper to construct an [`EntityMapper`] from a [`DynEntityMapper`].
pub struct UnDynEntityMapper<'a>(pub &'a mut dyn DynEntityMapper);

impl<'a> EntityMapper for UnDynEntityMapper<'a> {
fn map_entity(&mut self, entity: Entity) -> Entity {
self.0.dyn_map_entity(entity)
}

fn mappings(&self) -> impl Iterator<Item = (Entity, Entity)> {
self.0.dyn_mappings().into_iter()
}
}

impl EntityMapper for SceneEntityMapper<'_> {
/// Returns the corresponding mapped entity or reserves a new dead entity ID in the current world if it is absent.
fn map_entity(&mut self, entity: Entity) -> Entity {
Expand Down
63 changes: 14 additions & 49 deletions crates/bevy_ecs/src/reflect/map_entities.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::{
component::Component,
entity::{Entity, EntityHashMap, MapEntities, SceneEntityMapper},
entity::{
DynEntityMapper, Entity, EntityHashMap, MapEntities, SceneEntityMapper, UnDynEntityMapper,
},
world::World,
};
use bevy_reflect::FromType;
use bevy_reflect::{FromReflect, FromType, Reflect};

/// For a specific type of component, this maps any fields with values of type [`Entity`] to a new world.
/// Since a given `Entity` ID is only valid for the world it came from, when performing deserialization
Expand All @@ -12,63 +14,26 @@ use bevy_reflect::FromType;
/// See [`SceneEntityMapper`] and [`MapEntities`] for more information.
#[derive(Clone)]
pub struct ReflectMapEntities {
map_all_entities: fn(&mut World, &mut SceneEntityMapper),
map_entities: fn(&mut World, &mut SceneEntityMapper, &[Entity]),
map_entities: fn(&mut dyn Reflect, &mut dyn DynEntityMapper),
}

impl ReflectMapEntities {
/// A general method for applying [`MapEntities`] behavior to all elements in an [`EntityHashMap<Entity>`].
/// A general method for applying [`MapEntities`] behavior to a reflected component.
///
/// Be mindful in its usage: Works best in situations where the entities in the [`EntityHashMap<Entity>`] are newly
/// created, before systems have a chance to add new components. If some of the entities referred to
/// by the [`EntityHashMap<Entity>`] might already contain valid entity references, you should use [`map_entities`](Self::map_entities).
///
/// An example of this: A scene can be loaded with `Parent` components, but then a `Parent` component can be added
/// to these entities after they have been loaded. If you reload the scene using [`map_all_entities`](Self::map_all_entities), those `Parent`
/// components with already valid entity references could be updated to point at something else entirely.
pub fn map_all_entities(&self, world: &mut World, entity_map: &mut EntityHashMap<Entity>) {
SceneEntityMapper::world_scope(entity_map, world, self.map_all_entities);
}

/// A general method for applying [`MapEntities`] behavior to elements in an [`EntityHashMap<Entity>`]. Unlike
/// [`map_all_entities`](Self::map_all_entities), this is applied to specific entities, not all values
/// in the [`EntityHashMap<Entity>`].
///
/// This is useful mostly for when you need to be careful not to update components that already contain valid entity
/// values. See [`map_all_entities`](Self::map_all_entities) for more details.
pub fn map_entities(
&self,
world: &mut World,
entity_map: &mut EntityHashMap<Entity>,
entities: &[Entity],
) {
SceneEntityMapper::world_scope(entity_map, world, |world, mapper| {
(self.map_entities)(world, mapper, entities);
});
/// created, before systems have a chance to add new components.
pub fn map_entities(&self, component: &mut dyn Reflect, mapper: &mut dyn DynEntityMapper) {
(self.map_entities)(component, mapper);
}
}

impl<C: Component + MapEntities> FromType<C> for ReflectMapEntities {
impl<C: Component + MapEntities + Reflect + FromReflect> FromType<C> for ReflectMapEntities {
fn from_type() -> Self {
ReflectMapEntities {
map_entities: |world, entity_mapper, entities| {
for &entity in entities {
if let Some(mut component) = world.get_mut::<C>(entity) {
component.map_entities(entity_mapper);
}
}
},
map_all_entities: |world, entity_mapper| {
let entities = entity_mapper
.get_map()
.values()
.copied()
.collect::<Vec<Entity>>();
for entity in &entities {
if let Some(mut component) = world.get_mut::<C>(*entity) {
component.map_entities(entity_mapper);
}
}
map_entities: |component, entity_mapper| {
let mut concrete = C::from_reflect(&*component).unwrap();
concrete.map_entities(&mut UnDynEntityMapper(entity_mapper));
component.apply(&concrete as _);
},
}
}
Expand Down
54 changes: 23 additions & 31 deletions crates/bevy_scene/src/dynamic_scene.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::{ron, DynamicSceneBuilder, Scene, SceneSpawnError};
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::entity::{EntityHashMap, SceneEntityMapper};
use bevy_ecs::{
entity::Entity,
reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities},
world::World,
};
use bevy_reflect::{Reflect, TypePath, TypeRegistry};
use bevy_utils::TypeIdMap;

#[cfg(feature = "serialize")]
use crate::serde::SceneSerializer;
Expand Down Expand Up @@ -71,23 +70,26 @@ impl DynamicScene {
) -> Result<(), SceneSpawnError> {
let type_registry = type_registry.read();

// For each component types that reference other entities, we keep track
// of which entities in the scene use that component.
// This is so we can update the scene-internal references to references
// of the actual entities in the world.
let mut scene_mappings: TypeIdMap<Vec<Entity>> = Default::default();

// First ensure that every entity in the scene has a corresponding world
// entity in the entity map.
for scene_entity in &self.entities {
// Fetch the entity with the given entity id from the `entity_map`
// or spawn a new entity with a transiently unique id if there is
// no corresponding entry.
let entity = *entity_map
entity_map
.entry(scene_entity.entity)
.or_insert_with(|| world.spawn_empty().id());
let entity_mut = &mut world.entity_mut(entity);
}

for scene_entity in &self.entities {
// Fetch the entity with the given entity id from the `entity_map`.
let entity = *entity_map
.get(&scene_entity.entity)
.expect("should have previously spawned an empty entity");

// Apply/ add each component to the given entity.
for component in &scene_entity.components {
let mut component = component.clone_value();
let type_info = component.get_represented_type_info().ok_or_else(|| {
SceneSpawnError::NoRepresentedType {
type_path: component.reflect_type_path().to_string(),
Expand All @@ -105,29 +107,19 @@ impl DynamicScene {
}
})?;

// If this component references entities in the scene, track it
// so we can update it to the entity in the world.
if registration.data::<ReflectMapEntities>().is_some() {
scene_mappings
.entry(registration.type_id())
.or_default()
.push(entity);
// If this component references entities in the scene, update
// them to the entities in the world.
if let Some(map_entities) = registration.data::<ReflectMapEntities>() {
SceneEntityMapper::world_scope(entity_map, world, |_, mapper| {
map_entities.map_entities(&mut *component, mapper);
});
}

// If the entity already has the given component attached,
// just apply the (possibly) new value, otherwise add the
// component to the entity.
reflect_component.apply_or_insert(entity_mut, &**component, &type_registry);
}
}

// Updates references to entities in the scene to entities in the world
for (type_id, entities) in scene_mappings.into_iter() {
let registration = type_registry.get(type_id).expect(
"we should be getting TypeId from this TypeRegistration in the first place",
);
if let Some(map_entities_reflect) = registration.data::<ReflectMapEntities>() {
map_entities_reflect.map_entities(world, entity_map, &entities);
reflect_component.apply_or_insert(
&mut world.entity_mut(entity),
&*component,
&type_registry,
);
}
}

Expand Down
51 changes: 29 additions & 22 deletions crates/bevy_scene/src/scene.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{DynamicScene, SceneSpawnError};
use bevy_asset::Asset;
use bevy_ecs::entity::{Entity, EntityHashMap};
use bevy_ecs::entity::{Entity, EntityHashMap, SceneEntityMapper};
use bevy_ecs::{
reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource},
world::World,
};
use bevy_reflect::TypePath;
use bevy_reflect::{Reflect, TypePath};

/// To spawn a scene, you can use either:
/// * [`SceneSpawner::spawn`](crate::SceneSpawner::spawn)
Expand Down Expand Up @@ -92,7 +92,7 @@ impl Scene {

for archetype in self.world.archetypes().iter() {
for scene_entity in archetype.entities() {
let entity = entity_map
let entity = *entity_map
.entry(scene_entity.id())
.or_insert_with(|| world.spawn_empty().id());
for component_id in archetype.components() {
Expand All @@ -102,35 +102,42 @@ impl Scene {
.get_info(component_id)
.expect("component_ids in archetypes should have ComponentInfo");

let reflect_component = type_registry
let registration = type_registry
.get(component_info.type_id().unwrap())
.ok_or_else(|| SceneSpawnError::UnregisteredType {
std_type_name: component_info.name().to_string(),
})
.and_then(|registration| {
registration.data::<ReflectComponent>().ok_or_else(|| {
SceneSpawnError::UnregisteredComponent {
type_path: registration.type_info().type_path().to_string(),
}
})
})?;
reflect_component.copy(
&self.world,
world,
scene_entity.id(),
*entity,
let reflect_component =
registration.data::<ReflectComponent>().ok_or_else(|| {
SceneSpawnError::UnregisteredComponent {
type_path: registration.type_info().type_path().to_string(),
}
})?;

let Some(mut component) = reflect_component
.reflect(self.world.entity(scene_entity.id()))
.map(Reflect::clone_value)
else {
continue;
};

// If this component references entities in the scene,
// update them to the entities in the world.
if let Some(map_entities) = registration.data::<ReflectMapEntities>() {
SceneEntityMapper::world_scope(entity_map, world, |_, mapper| {
map_entities.map_entities(&mut *component, mapper);
});
}

reflect_component.apply_or_insert(
&mut world.entity_mut(entity),
&*component,
&type_registry,
);
}
}
}

for registration in type_registry.iter() {
if let Some(map_entities_reflect) = registration.data::<ReflectMapEntities>() {
map_entities_reflect.map_all_entities(world, entity_map);
}
}

Ok(())
}
}

0 comments on commit 255e846

Please sign in to comment.