Skip to content

Commit eebc92a

Browse files
Illiuxnicopap
andauthored
Make scene handling of entity references robust (#7335)
# Objective - Handle dangling entity references inside scenes - Handle references to entities with generation > 0 inside scenes - Fix a latent bug in `Parent`'s `MapEntities` implementation, which would, if the parent was outside the scene, cause the scene to be loaded into the new world with a parent reference potentially pointing to some random entity in that new world. - Fixes #4793 and addresses #7235 ## Solution - DynamicScenes now identify entities with a `Entity` instead of a u32, therefore including generation - `World` exposes a new `reserve_generations` function that despawns an entity and advances its generation by some extra amount. - `MapEntities` implementations have a new `get_or_reserve` function available that will always return an `Entity`, establishing a new mapping to a dead entity when the entity they are called with is not in the `EntityMap`. Subsequent calls with that same `Entity` will return the same newly created dead entity reference, preserving equality semantics. - As a result, after loading a scene containing references to dead entities (or entities otherwise outside the scene), those references will all point to different generations on a single entity id in the new world. --- ## Changelog ### Changed - In serialized scenes, entities are now identified by a u64 instead of a u32. - In serialized scenes, components with entity references now have those references serialize as u64s instead of structs. ### Fixed - Scenes containing components with entity references will now deserialize and add to a world reliably. ## Migration Guide - `MapEntities` implementations must change from a `&EntityMap` parameter to a `&mut EntityMapper` parameter and can no longer return a `Result`. Finally, they should switch from calling `EntityMap::get` to calling `EntityMapper::get_or_reserve`. --------- Co-authored-by: Nicola Papale <[email protected]>
1 parent ba532e4 commit eebc92a

File tree

12 files changed

+360
-125
lines changed

12 files changed

+360
-125
lines changed

crates/bevy_ecs/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ thread_local = "1.1.4"
2626
fixedbitset = "0.4.2"
2727
rustc-hash = "1.1"
2828
downcast-rs = "1.2"
29-
serde = { version = "1", features = ["derive"] }
29+
serde = "1"
3030
thiserror = "1.0"
3131

3232
[dev-dependencies]

crates/bevy_ecs/src/entity/map_entities.rs

+151-34
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,5 @@
1-
use crate::entity::Entity;
1+
use crate::{entity::Entity, world::World};
22
use bevy_utils::{Entry, HashMap};
3-
use std::fmt;
4-
5-
/// The errors that might be returned while using [`MapEntities::map_entities`].
6-
#[derive(Debug)]
7-
pub enum MapEntitiesError {
8-
EntityNotFound(Entity),
9-
}
10-
11-
impl std::error::Error for MapEntitiesError {}
12-
13-
impl fmt::Display for MapEntitiesError {
14-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15-
match self {
16-
MapEntitiesError::EntityNotFound(_) => {
17-
write!(f, "the given entity does not exist in the map")
18-
}
19-
}
20-
}
21-
}
223

234
/// Operation to map all contained [`Entity`] fields in a type to new values.
245
///
@@ -33,7 +14,7 @@ impl fmt::Display for MapEntitiesError {
3314
///
3415
/// ```rust
3516
/// use bevy_ecs::prelude::*;
36-
/// use bevy_ecs::entity::{EntityMap, MapEntities, MapEntitiesError};
17+
/// use bevy_ecs::entity::{EntityMapper, MapEntities};
3718
///
3819
/// #[derive(Component)]
3920
/// struct Spring {
@@ -42,10 +23,9 @@ impl fmt::Display for MapEntitiesError {
4223
/// }
4324
///
4425
/// impl MapEntities for Spring {
45-
/// fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> {
46-
/// self.a = entity_map.get(self.a)?;
47-
/// self.b = entity_map.get(self.b)?;
48-
/// Ok(())
26+
/// fn map_entities(&mut self, entity_mapper: &mut EntityMapper) {
27+
/// self.a = entity_mapper.get_or_reserve(self.a);
28+
/// self.b = entity_mapper.get_or_reserve(self.b);
4929
/// }
5030
/// }
5131
/// ```
@@ -55,16 +35,22 @@ pub trait MapEntities {
5535
/// Updates all [`Entity`] references stored inside using `entity_map`.
5636
///
5737
/// Implementors should look up any and all [`Entity`] values stored within and
58-
/// update them to the mapped values via `entity_map`.
59-
fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError>;
38+
/// update them to the mapped values via `entity_mapper`.
39+
fn map_entities(&mut self, entity_mapper: &mut EntityMapper);
6040
}
6141

6242
/// A mapping from one set of entities to another.
6343
///
6444
/// The API generally follows [`HashMap`], but each [`Entity`] is returned by value, as they are [`Copy`].
6545
///
66-
/// This is typically used to coordinate data transfer between sets of entities, such as between a scene and the world or over the network.
67-
/// This is required as [`Entity`] identifiers are opaque; you cannot and do not want to reuse identifiers directly.
46+
/// This is typically used to coordinate data transfer between sets of entities, such as between a scene and the world
47+
/// or over the network. This is required as [`Entity`] identifiers are opaque; you cannot and do not want to reuse
48+
/// identifiers directly.
49+
///
50+
/// On its own, an `EntityMap` is not capable of allocating new entity identifiers, which is needed to map references
51+
/// to entities that lie outside the source entity set. To do this, an `EntityMap` can be wrapped in an
52+
/// [`EntityMapper`] which scopes it to a particular destination [`World`] and allows new identifiers to be allocated.
53+
/// This functionality can be accessed through [`Self::world_scope()`].
6854
#[derive(Default, Debug)]
6955
pub struct EntityMap {
7056
map: HashMap<Entity, Entity>,
@@ -91,11 +77,8 @@ impl EntityMap {
9177
}
9278

9379
/// Returns the corresponding mapped entity.
94-
pub fn get(&self, entity: Entity) -> Result<Entity, MapEntitiesError> {
95-
self.map
96-
.get(&entity)
97-
.cloned()
98-
.ok_or(MapEntitiesError::EntityNotFound(entity))
80+
pub fn get(&self, entity: Entity) -> Option<Entity> {
81+
self.map.get(&entity).copied()
9982
}
10083

10184
/// An iterator visiting all keys in arbitrary order.
@@ -122,4 +105,138 @@ impl EntityMap {
122105
pub fn iter(&self) -> impl Iterator<Item = (Entity, Entity)> + '_ {
123106
self.map.iter().map(|(from, to)| (*from, *to))
124107
}
108+
109+
/// Creates an [`EntityMapper`] from this [`EntityMap`] and scoped to the provided [`World`], then calls the
110+
/// provided function with it. This allows one to allocate new entity references in the provided `World` that are
111+
/// guaranteed to never point at a living entity now or in the future. This functionality is useful for safely
112+
/// mapping entity identifiers that point at entities outside the source world. The passed function, `f`, is called
113+
/// within the scope of the passed world. Its return value is then returned from `world_scope` as the generic type
114+
/// parameter `R`.
115+
pub fn world_scope<R>(
116+
&mut self,
117+
world: &mut World,
118+
f: impl FnOnce(&mut World, &mut EntityMapper) -> R,
119+
) -> R {
120+
let mut mapper = EntityMapper::new(self, world);
121+
let result = f(world, &mut mapper);
122+
mapper.finish(world);
123+
result
124+
}
125+
}
126+
127+
/// A wrapper for [`EntityMap`], augmenting it with the ability to allocate new [`Entity`] references in a destination
128+
/// world. These newly allocated references are guaranteed to never point to any living entity in that world.
129+
///
130+
/// References are allocated by returning increasing generations starting from an internally initialized base
131+
/// [`Entity`]. After it is finished being used by [`MapEntities`] implementations, this entity is despawned and the
132+
/// requisite number of generations reserved.
133+
pub struct EntityMapper<'m> {
134+
/// The wrapped [`EntityMap`].
135+
map: &'m mut EntityMap,
136+
/// A base [`Entity`] used to allocate new references.
137+
dead_start: Entity,
138+
/// The number of generations this mapper has allocated thus far.
139+
generations: u32,
140+
}
141+
142+
impl<'m> EntityMapper<'m> {
143+
/// Returns the corresponding mapped entity or reserves a new dead entity ID if it is absent.
144+
pub fn get_or_reserve(&mut self, entity: Entity) -> Entity {
145+
if let Some(mapped) = self.map.get(entity) {
146+
return mapped;
147+
}
148+
149+
// this new entity reference is specifically designed to never represent any living entity
150+
let new = Entity {
151+
generation: self.dead_start.generation + self.generations,
152+
index: self.dead_start.index,
153+
};
154+
self.generations += 1;
155+
156+
self.map.insert(entity, new);
157+
158+
new
159+
}
160+
161+
/// Gets a reference to the underlying [`EntityMap`].
162+
pub fn get_map(&'m self) -> &'m EntityMap {
163+
self.map
164+
}
165+
166+
/// Gets a mutable reference to the underlying [`EntityMap`]
167+
pub fn get_map_mut(&'m mut self) -> &'m mut EntityMap {
168+
self.map
169+
}
170+
171+
/// Creates a new [`EntityMapper`], spawning a temporary base [`Entity`] in the provided [`World`]
172+
fn new(map: &'m mut EntityMap, world: &mut World) -> Self {
173+
Self {
174+
map,
175+
// SAFETY: Entities data is kept in a valid state via `EntityMap::world_scope`
176+
dead_start: unsafe { world.entities_mut().alloc() },
177+
generations: 0,
178+
}
179+
}
180+
181+
/// Reserves the allocated references to dead entities within the world. This frees the temporary base
182+
/// [`Entity`] while reserving extra generations via [`crate::entity::Entities::reserve_generations`]. Because this
183+
/// renders the [`EntityMapper`] unable to safely allocate any more references, this method takes ownership of
184+
/// `self` in order to render it unusable.
185+
fn finish(self, world: &mut World) {
186+
// SAFETY: Entities data is kept in a valid state via `EntityMap::world_scope`
187+
let entities = unsafe { world.entities_mut() };
188+
assert!(entities.free(self.dead_start).is_some());
189+
assert!(entities.reserve_generations(self.dead_start.index, self.generations));
190+
}
191+
}
192+
193+
#[cfg(test)]
194+
mod tests {
195+
use super::{EntityMap, EntityMapper};
196+
use crate::{entity::Entity, world::World};
197+
198+
#[test]
199+
fn entity_mapper() {
200+
const FIRST_IDX: u32 = 1;
201+
const SECOND_IDX: u32 = 2;
202+
203+
let mut map = EntityMap::default();
204+
let mut world = World::new();
205+
let mut mapper = EntityMapper::new(&mut map, &mut world);
206+
207+
let mapped_ent = Entity::new(FIRST_IDX, 0);
208+
let dead_ref = mapper.get_or_reserve(mapped_ent);
209+
210+
assert_eq!(
211+
dead_ref,
212+
mapper.get_or_reserve(mapped_ent),
213+
"should persist the allocated mapping from the previous line"
214+
);
215+
assert_eq!(
216+
mapper.get_or_reserve(Entity::new(SECOND_IDX, 0)).index(),
217+
dead_ref.index(),
218+
"should re-use the same index for further dead refs"
219+
);
220+
221+
mapper.finish(&mut world);
222+
// Next allocated entity should be a further generation on the same index
223+
let entity = world.spawn_empty().id();
224+
assert_eq!(entity.index(), dead_ref.index());
225+
assert!(entity.generation() > dead_ref.generation());
226+
}
227+
228+
#[test]
229+
fn world_scope_reserves_generations() {
230+
let mut map = EntityMap::default();
231+
let mut world = World::new();
232+
233+
let dead_ref = map.world_scope(&mut world, |_, mapper| {
234+
mapper.get_or_reserve(Entity::new(0, 0))
235+
});
236+
237+
// Next allocated entity should be a further generation on the same index
238+
let entity = world.spawn_empty().id();
239+
assert_eq!(entity.index(), dead_ref.index());
240+
assert!(entity.generation() > dead_ref.generation());
241+
}
125242
}

crates/bevy_ecs/src/entity/mod.rs

+64-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ type IdCursor = isize;
115115
/// [`EntityCommands`]: crate::system::EntityCommands
116116
/// [`Query::get`]: crate::system::Query::get
117117
/// [`World`]: crate::world::World
118-
#[derive(Clone, Copy, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
118+
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
119119
pub struct Entity {
120120
generation: u32,
121121
index: u32,
@@ -227,6 +227,25 @@ impl Entity {
227227
}
228228
}
229229

230+
impl Serialize for Entity {
231+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
232+
where
233+
S: serde::Serializer,
234+
{
235+
serializer.serialize_u64(self.to_bits())
236+
}
237+
}
238+
239+
impl<'de> Deserialize<'de> for Entity {
240+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241+
where
242+
D: serde::Deserializer<'de>,
243+
{
244+
let id: u64 = serde::de::Deserialize::deserialize(deserializer)?;
245+
Ok(Entity::from_bits(id))
246+
}
247+
}
248+
230249
impl fmt::Debug for Entity {
231250
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232251
write!(f, "{}v{}", self.index, self.generation)
@@ -604,6 +623,25 @@ impl Entities {
604623
self.meta.get_unchecked_mut(index as usize).location = location;
605624
}
606625

626+
/// Increments the `generation` of a freed [`Entity`]. The next entity ID allocated with this
627+
/// `index` will count `generation` starting from the prior `generation` + the specified
628+
/// value + 1.
629+
///
630+
/// Does nothing if no entity with this `index` has been allocated yet.
631+
pub(crate) fn reserve_generations(&mut self, index: u32, generations: u32) -> bool {
632+
if (index as usize) >= self.meta.len() {
633+
return false;
634+
}
635+
636+
let meta = &mut self.meta[index as usize];
637+
if meta.location.archetype_id == ArchetypeId::INVALID {
638+
meta.generation += generations;
639+
true
640+
} else {
641+
false
642+
}
643+
}
644+
607645
/// Get the [`Entity`] with a given id, if it exists in this [`Entities`] collection
608646
/// Returns `None` if this [`Entity`] is outside of the range of currently reserved Entities
609647
///
@@ -847,4 +885,29 @@ mod tests {
847885
const C4: u32 = Entity::from_bits(0x00dd_00ff_0000_0000).generation();
848886
assert_eq!(0x00dd_00ff, C4);
849887
}
888+
889+
#[test]
890+
fn reserve_generations() {
891+
let mut entities = Entities::new();
892+
let entity = entities.alloc();
893+
entities.free(entity);
894+
895+
assert!(entities.reserve_generations(entity.index, 1));
896+
}
897+
898+
#[test]
899+
fn reserve_generations_and_alloc() {
900+
const GENERATIONS: u32 = 10;
901+
902+
let mut entities = Entities::new();
903+
let entity = entities.alloc();
904+
entities.free(entity);
905+
906+
assert!(entities.reserve_generations(entity.index, GENERATIONS));
907+
908+
// The very next entitiy allocated should be a further generation on the same index
909+
let next_entity = entities.alloc();
910+
assert_eq!(next_entity.index(), entity.index());
911+
assert!(next_entity.generation > entity.generation + GENERATIONS);
912+
}
850913
}

0 commit comments

Comments
 (0)