Skip to content

Commit 3bfc427

Browse files
Add mappings to EntityMapper (#13727)
# Objective - Fixes #13703 ## Solution - Added `mappings` to the `EntityMapper` trait, which returns an iterator over currently tracked `Entity` to `Entity` mappings. - Added `DynEntityMapper` as an [object safe](https://doc.rust-lang.org/reference/items/traits.html#object-safety) alternative to `EntityMapper`. - Added `assert_object_safe` as a helper for ensuring traits are object safe. ## Testing - Added new unit test `entity_mapper_iteration` which tests the `SceneEntityMapper` implementation of `EntityMapper::mappings`. - Added unit tests to ensure `DynEntityMapper`, `DynEq` and `DynHash` are object safe. - Passed CI on my Windows 10 development environment --- ## Changelog - Added `mappings` to `EntityMapper` trait. ## Migration Guide - If you are implementing `EntityMapper` yourself, you can use the below as a stub implementation: ```rust fn mappings(&self) -> impl Iterator<Item = (Entity, Entity)> { unimplemented!() } ``` - If you were using `EntityMapper` as a trait object (`dyn EntityMapper`), instead use `dyn DynEntityMapper` and its associated methods. ## Notes - The original issue proposed returning a `Vec` from `EntityMapper` instead of an `impl Iterator` to preserve its object safety. This is a simpler option, but also forces an allocation where it isn't strictly needed. I've opted for this split into `DynEntityMapper` and `EntityMapper` as it's been done several times across Bevy already, and provides maximum flexibility to users. - `assert_object_safe` is an empty function, since the assertion actually happens once you try to use a `dyn T` for some trait `T`. I have still added this function to clearly document what object safety is within Bevy, and to create a standard way to communicate that a given trait must be object safe. - Other traits should have tests added to ensure object safety, but I've left those off to avoid cluttering this PR further. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent d38d8a1 commit 3bfc427

File tree

4 files changed

+126
-0
lines changed

4 files changed

+126
-0
lines changed

crates/bevy_ecs/src/entity/map_entities.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ pub trait MapEntities {
5252
///
5353
/// More generally, this can be used to map [`Entity`] references between any two [`Worlds`](World).
5454
///
55+
/// Note that this trait is _not_ [object safe](https://doc.rust-lang.org/reference/items/traits.html#object-safety).
56+
/// Please see [`DynEntityMapper`] for an object safe alternative.
57+
///
5558
/// ## Example
5659
///
5760
/// ```
@@ -68,11 +71,55 @@ pub trait MapEntities {
6871
/// fn map_entity(&mut self, entity: Entity) -> Entity {
6972
/// self.map.get(&entity).copied().unwrap_or(entity)
7073
/// }
74+
///
75+
/// fn mappings(&self) -> impl Iterator<Item = (Entity, Entity)> {
76+
/// self.map.iter().map(|(&source, &target)| (source, target))
77+
/// }
7178
/// }
7279
/// ```
7380
pub trait EntityMapper {
7481
/// Map an entity to another entity
7582
fn map_entity(&mut self, entity: Entity) -> Entity;
83+
84+
/// Iterate over all entity to entity mappings.
85+
///
86+
/// # Examples
87+
///
88+
/// ```rust
89+
/// # use bevy_ecs::entity::{Entity, EntityMapper};
90+
/// # fn example(mapper: impl EntityMapper) {
91+
/// for (source, target) in mapper.mappings() {
92+
/// println!("Will map from {source} to {target}");
93+
/// }
94+
/// # }
95+
/// ```
96+
fn mappings(&self) -> impl Iterator<Item = (Entity, Entity)>;
97+
}
98+
99+
/// An [object safe](https://doc.rust-lang.org/reference/items/traits.html#object-safety) version
100+
/// of [`EntityMapper`]. This trait is automatically implemented for type that implements `EntityMapper`.
101+
pub trait DynEntityMapper {
102+
/// Map an entity to another entity.
103+
///
104+
/// This is an [object safe](https://doc.rust-lang.org/reference/items/traits.html#object-safety)
105+
/// alternative to [`EntityMapper::map_entity`].
106+
fn dyn_map_entity(&mut self, entity: Entity) -> Entity;
107+
108+
/// Iterate over all entity to entity mappings.
109+
///
110+
/// This is an [object safe](https://doc.rust-lang.org/reference/items/traits.html#object-safety)
111+
/// alternative to [`EntityMapper::mappings`].
112+
fn dyn_mappings(&self) -> Vec<(Entity, Entity)>;
113+
}
114+
115+
impl<T: EntityMapper> DynEntityMapper for T {
116+
fn dyn_map_entity(&mut self, entity: Entity) -> Entity {
117+
<T as EntityMapper>::map_entity(self, entity)
118+
}
119+
120+
fn dyn_mappings(&self) -> Vec<(Entity, Entity)> {
121+
<T as EntityMapper>::mappings(self).collect()
122+
}
76123
}
77124

78125
impl EntityMapper for SceneEntityMapper<'_> {
@@ -95,6 +142,10 @@ impl EntityMapper for SceneEntityMapper<'_> {
95142

96143
new
97144
}
145+
146+
fn mappings(&self) -> impl Iterator<Item = (Entity, Entity)> {
147+
self.map.iter().map(|(&source, &target)| (source, target))
148+
}
98149
}
99150

100151
/// A wrapper for [`EntityHashMap<Entity>`], augmenting it with the ability to allocate new [`Entity`] references in a destination
@@ -171,10 +222,12 @@ impl<'m> SceneEntityMapper<'m> {
171222

172223
#[cfg(test)]
173224
mod tests {
225+
use crate::entity::DynEntityMapper;
174226
use crate::{
175227
entity::{Entity, EntityHashMap, EntityMapper, SceneEntityMapper},
176228
world::World,
177229
};
230+
use bevy_utils::assert_object_safe;
178231

179232
#[test]
180233
fn entity_mapper() {
@@ -220,4 +273,29 @@ mod tests {
220273
assert_eq!(entity.index(), dead_ref.index());
221274
assert!(entity.generation() > dead_ref.generation());
222275
}
276+
277+
#[test]
278+
fn entity_mapper_iteration() {
279+
let mut old_world = World::new();
280+
let mut new_world = World::new();
281+
282+
let mut map = EntityHashMap::default();
283+
let mut mapper = SceneEntityMapper::new(&mut map, &mut new_world);
284+
285+
assert_eq!(mapper.mappings().collect::<Vec<_>>(), vec![]);
286+
287+
let old_entity = old_world.spawn_empty().id();
288+
289+
let new_entity = mapper.map_entity(old_entity);
290+
291+
assert_eq!(
292+
mapper.mappings().collect::<Vec<_>>(),
293+
vec![(old_entity, new_entity)]
294+
);
295+
}
296+
297+
#[test]
298+
fn dyn_entity_mapper_object_safe() {
299+
assert_object_safe::<dyn DynEntityMapper>();
300+
}
223301
}

crates/bevy_ecs/src/label.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,19 @@ macro_rules! define_label {
197197
$crate::intern::Interner::new();
198198
};
199199
}
200+
201+
#[cfg(test)]
202+
mod tests {
203+
use super::{DynEq, DynHash};
204+
use bevy_utils::assert_object_safe;
205+
206+
#[test]
207+
fn dyn_eq_object_safe() {
208+
assert_object_safe::<dyn DynEq>();
209+
}
210+
211+
#[test]
212+
fn dyn_hash_object_safe() {
213+
assert_object_safe::<dyn DynHash>();
214+
}
215+
}

crates/bevy_utils/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ pub mod syncunsafecell;
2323

2424
mod cow_arc;
2525
mod default;
26+
mod object_safe;
27+
pub use object_safe::assert_object_safe;
2628
mod once;
2729
mod parallel_queue;
2830

crates/bevy_utils/src/object_safe.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/// Assert that a given `T` is [object safe](https://doc.rust-lang.org/reference/items/traits.html#object-safety).
2+
/// Will fail to compile if that is not the case.
3+
///
4+
/// # Examples
5+
///
6+
/// ```rust
7+
/// # use bevy_utils::assert_object_safe;
8+
/// // Concrete types are always object safe
9+
/// struct MyStruct;
10+
/// assert_object_safe::<MyStruct>();
11+
///
12+
/// // Trait objects are where that safety comes into question.
13+
/// // This trait is object safe...
14+
/// trait ObjectSafe { }
15+
/// assert_object_safe::<dyn ObjectSafe>();
16+
/// ```
17+
///
18+
/// ```compile_fail
19+
/// # use bevy_utils::assert_object_safe;
20+
/// // ...but this trait is not.
21+
/// trait NotObjectSafe {
22+
/// const VALUE: usize;
23+
/// }
24+
/// assert_object_safe::<dyn NotObjectSafe>();
25+
/// // Error: the trait `NotObjectSafe` cannot be made into an object
26+
/// ```
27+
pub fn assert_object_safe<T: ?Sized>() {
28+
// This space is left intentionally blank. The type parameter T is sufficient to induce a compiler
29+
// error without a function body.
30+
}

0 commit comments

Comments
 (0)