Skip to content

Commit 1c67e02

Browse files
authored
Move EntityHash related types into bevy_ecs (#11498)
# Objective Reduce the size of `bevy_utils` (#11478) ## Solution Move `EntityHash` related types into `bevy_ecs`. This also allows us access to `Entity`, which means we no longer need `EntityHashMap`'s first generic argument. --- ## Changelog - Moved `bevy::utils::{EntityHash, EntityHasher, EntityHashMap, EntityHashSet}` into `bevy::ecs::entity::hash` . - Removed `EntityHashMap`'s first generic argument. It is now hardcoded to always be `Entity`. ## Migration Guide - Uses of `bevy::utils::{EntityHash, EntityHasher, EntityHashMap, EntityHashSet}` now have to be imported from `bevy::ecs::entity::hash`. - Uses of `EntityHashMap` no longer have to specify the first generic parameter. It is now hardcoded to always be `Entity`.
1 parent c1a4e29 commit 1c67e02

File tree

35 files changed

+262
-229
lines changed

35 files changed

+262
-229
lines changed

benches/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,6 @@ path = "benches/bevy_math/bezier.rs"
6363
harness = false
6464

6565
[[bench]]
66-
name = "utils"
67-
path = "benches/bevy_utils/entity_hash.rs"
66+
name = "entity_hash"
67+
path = "benches/bevy_ecs/world/entity_hash.rs"
6868
harness = false

benches/benches/bevy_utils/entity_hash.rs renamed to benches/benches/bevy_ecs/world/entity_hash.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use bevy_ecs::entity::Entity;
2-
use bevy_utils::EntityHashSet;
1+
use bevy_ecs::entity::{Entity, EntityHashMap, EntityHashSet};
32
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
43
use rand::{Rng, SeedableRng};
54
use rand_chacha::ChaCha8Rng;
@@ -28,7 +27,7 @@ fn make_entity(rng: &mut impl Rng, size: usize) -> Entity {
2827
e
2928
}
3029

31-
fn entity_set_build_and_lookup(c: &mut Criterion) {
30+
pub fn entity_set_build_and_lookup(c: &mut Criterion) {
3231
let mut group = c.benchmark_group("entity_hash");
3332
for size in SIZES {
3433
// Get some random-but-consistent entities to use for all the benches below.

benches/benches/bevy_ecs/world/mod.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
use criterion::criterion_group;
22

33
mod commands;
4-
mod spawn;
5-
mod world_get;
6-
74
use commands::*;
5+
6+
mod spawn;
87
use spawn::*;
8+
9+
mod world_get;
910
use world_get::*;
1011

12+
mod entity_hash;
13+
use entity_hash::*;
14+
1115
criterion_group!(
1216
world_benches,
1317
empty_commands,
@@ -30,4 +34,5 @@ criterion_group!(
3034
query_get_many::<2>,
3135
query_get_many::<5>,
3236
query_get_many::<10>,
37+
entity_set_build_and_lookup
3338
);

crates/bevy_ecs/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ thiserror = "1.0"
3232

3333
[dev-dependencies]
3434
rand = "0.8"
35+
static_assertions = "1.1.0"
3536

3637
[[example]]
3738
name = "events"

crates/bevy_ecs/src/entity/hash.rs

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use std::hash::{BuildHasher, Hasher};
2+
3+
#[cfg(feature = "bevy_reflect")]
4+
use bevy_reflect::Reflect;
5+
use bevy_utils::hashbrown;
6+
7+
use super::Entity;
8+
9+
/// A [`BuildHasher`] that results in a [`EntityHasher`].
10+
#[derive(Default, Clone)]
11+
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
12+
pub struct EntityHash;
13+
14+
impl BuildHasher for EntityHash {
15+
type Hasher = EntityHasher;
16+
17+
fn build_hasher(&self) -> Self::Hasher {
18+
Self::Hasher::default()
19+
}
20+
}
21+
22+
/// A very fast hash that is only designed to work on generational indices
23+
/// like [`Entity`]. It will panic if attempting to hash a type containing
24+
/// non-u64 fields.
25+
///
26+
/// This is heavily optimized for typical cases, where you have mostly live
27+
/// entities, and works particularly well for contiguous indices.
28+
///
29+
/// If you have an unusual case -- say all your indices are multiples of 256
30+
/// or most of the entities are dead generations -- then you might want also to
31+
/// try [`AHasher`](bevy_utils::AHasher) for a slower hash computation but fewer lookup conflicts.
32+
#[derive(Debug, Default)]
33+
pub struct EntityHasher {
34+
hash: u64,
35+
}
36+
37+
impl Hasher for EntityHasher {
38+
#[inline]
39+
fn finish(&self) -> u64 {
40+
self.hash
41+
}
42+
43+
fn write(&mut self, _bytes: &[u8]) {
44+
panic!("EntityHasher can only hash u64 fields.");
45+
}
46+
47+
#[inline]
48+
fn write_u64(&mut self, bits: u64) {
49+
// SwissTable (and thus `hashbrown`) cares about two things from the hash:
50+
// - H1: low bits (masked by `2ⁿ-1`) to pick the slot in which to store the item
51+
// - H2: high 7 bits are used to SIMD optimize hash collision probing
52+
// For more see <https://abseil.io/about/design/swisstables#metadata-layout>
53+
54+
// This hash function assumes that the entity ids are still well-distributed,
55+
// so for H1 leaves the entity id alone in the low bits so that id locality
56+
// will also give memory locality for things spawned together.
57+
// For H2, take advantage of the fact that while multiplication doesn't
58+
// spread entropy to the low bits, it's incredibly good at spreading it
59+
// upward, which is exactly where we need it the most.
60+
61+
// While this does include the generation in the output, it doesn't do so
62+
// *usefully*. H1 won't care until you have over 3 billion entities in
63+
// the table, and H2 won't care until something hits generation 33 million.
64+
// Thus the comment suggesting that this is best for live entities,
65+
// where there won't be generation conflicts where it would matter.
66+
67+
// The high 32 bits of this are ⅟φ for Fibonacci hashing. That works
68+
// particularly well for hashing for the same reason as described in
69+
// <https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/>
70+
// It loses no information because it has a modular inverse.
71+
// (Specifically, `0x144c_bc89_u32 * 0x9e37_79b9_u32 == 1`.)
72+
//
73+
// The low 32 bits make that part of the just product a pass-through.
74+
const UPPER_PHI: u64 = 0x9e37_79b9_0000_0001;
75+
76+
// This is `(MAGIC * index + generation) << 32 + index`, in a single instruction.
77+
self.hash = bits.wrapping_mul(UPPER_PHI);
78+
}
79+
}
80+
81+
/// A [`HashMap`](hashbrown::HashMap) pre-configured to use [`EntityHash`] hashing.
82+
pub type EntityHashMap<V> = hashbrown::HashMap<Entity, V, EntityHash>;
83+
84+
/// A [`HashSet`](hashbrown::HashSet) pre-configured to use [`EntityHash`] hashing.
85+
pub type EntityHashSet = hashbrown::HashSet<Entity, EntityHash>;
86+
87+
#[cfg(test)]
88+
mod tests {
89+
use super::*;
90+
#[cfg(feature = "bevy_reflect")]
91+
use bevy_reflect::Reflect;
92+
use static_assertions::assert_impl_all;
93+
94+
// Check that the HashMaps are Clone if the key/values are Clone
95+
assert_impl_all!(EntityHashMap::<usize>: Clone);
96+
// EntityHashMap should implement Reflect
97+
#[cfg(feature = "bevy_reflect")]
98+
assert_impl_all!(EntityHashMap::<i32>: Reflect);
99+
}

crates/bevy_ecs/src/entity/map_entities.rs

+17-18
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ use crate::{
33
identifier::masks::{IdentifierMask, HIGH_MASK},
44
world::World,
55
};
6-
use bevy_utils::EntityHashMap;
6+
7+
use super::EntityHashMap;
78

89
/// Operation to map all contained [`Entity`] fields in a type to new values.
910
///
1011
/// As entity IDs are valid only for the [`World`] they're sourced from, using [`Entity`]
1112
/// as references in components copied from another world will be invalid. This trait
1213
/// allows defining custom mappings for these references via [`EntityMappers`](EntityMapper), which
1314
/// inject the entity mapping strategy between your `MapEntities` type and the current world
14-
/// (usually by using an [`EntityHashMap<Entity, Entity>`] between source entities and entities in the
15+
/// (usually by using an [`EntityHashMap<Entity>`] between source entities and entities in the
1516
/// current world).
1617
///
1718
/// Implementing this trait correctly is required for properly loading components
@@ -47,7 +48,7 @@ pub trait MapEntities {
4748

4849
/// An implementor of this trait knows how to map an [`Entity`] into another [`Entity`].
4950
///
50-
/// Usually this is done by using an [`EntityHashMap<Entity, Entity>`] to map source entities
51+
/// Usually this is done by using an [`EntityHashMap<Entity>`] to map source entities
5152
/// (mapper inputs) to the current world's entities (mapper outputs).
5253
///
5354
/// More generally, this can be used to map [`Entity`] references between any two [`Worlds`](World).
@@ -56,10 +57,10 @@ pub trait MapEntities {
5657
///
5758
/// ```
5859
/// # use bevy_ecs::entity::{Entity, EntityMapper};
59-
/// # use bevy_utils::EntityHashMap;
60+
/// # use bevy_ecs::entity::EntityHashMap;
6061
/// #
6162
/// pub struct SimpleEntityMapper {
62-
/// map: EntityHashMap<Entity, Entity>,
63+
/// map: EntityHashMap<Entity>,
6364
/// }
6465
///
6566
/// // Example implementation of EntityMapper where we map an entity to another entity if it exists
@@ -97,7 +98,7 @@ impl EntityMapper for SceneEntityMapper<'_> {
9798
}
9899
}
99100

100-
/// A wrapper for [`EntityHashMap<Entity, Entity>`], augmenting it with the ability to allocate new [`Entity`] references in a destination
101+
/// A wrapper for [`EntityHashMap<Entity>`], augmenting it with the ability to allocate new [`Entity`] references in a destination
101102
/// world. These newly allocated references are guaranteed to never point to any living entity in that world.
102103
///
103104
/// References are allocated by returning increasing generations starting from an internally initialized base
@@ -110,9 +111,9 @@ pub struct SceneEntityMapper<'m> {
110111
/// or over the network. This is required as [`Entity`] identifiers are opaque; you cannot and do not want to reuse
111112
/// identifiers directly.
112113
///
113-
/// On its own, a [`EntityHashMap<Entity, Entity>`] is not capable of allocating new entity identifiers, which is needed to map references
114+
/// On its own, a [`EntityHashMap<Entity>`] is not capable of allocating new entity identifiers, which is needed to map references
114115
/// to entities that lie outside the source entity set. This functionality can be accessed through [`SceneEntityMapper::world_scope()`].
115-
map: &'m mut EntityHashMap<Entity, Entity>,
116+
map: &'m mut EntityHashMap<Entity>,
116117
/// A base [`Entity`] used to allocate new references.
117118
dead_start: Entity,
118119
/// The number of generations this mapper has allocated thus far.
@@ -129,18 +130,18 @@ impl<'m> SceneEntityMapper<'m> {
129130
self.map_entity(entity)
130131
}
131132

132-
/// Gets a reference to the underlying [`EntityHashMap<Entity, Entity>`].
133-
pub fn get_map(&'m self) -> &'m EntityHashMap<Entity, Entity> {
133+
/// Gets a reference to the underlying [`EntityHashMap<Entity>`].
134+
pub fn get_map(&'m self) -> &'m EntityHashMap<Entity> {
134135
self.map
135136
}
136137

137-
/// Gets a mutable reference to the underlying [`EntityHashMap<Entity, Entity>`].
138-
pub fn get_map_mut(&'m mut self) -> &'m mut EntityHashMap<Entity, Entity> {
138+
/// Gets a mutable reference to the underlying [`EntityHashMap<Entity>`].
139+
pub fn get_map_mut(&'m mut self) -> &'m mut EntityHashMap<Entity> {
139140
self.map
140141
}
141142

142143
/// Creates a new [`SceneEntityMapper`], spawning a temporary base [`Entity`] in the provided [`World`]
143-
fn new(map: &'m mut EntityHashMap<Entity, Entity>, world: &mut World) -> Self {
144+
fn new(map: &'m mut EntityHashMap<Entity>, world: &mut World) -> Self {
144145
Self {
145146
map,
146147
// SAFETY: Entities data is kept in a valid state via `EntityMapper::world_scope`
@@ -160,14 +161,14 @@ impl<'m> SceneEntityMapper<'m> {
160161
assert!(entities.reserve_generations(self.dead_start.index(), self.generations));
161162
}
162163

163-
/// Creates an [`SceneEntityMapper`] from a provided [`World`] and [`EntityHashMap<Entity, Entity>`], then calls the
164+
/// Creates an [`SceneEntityMapper`] from a provided [`World`] and [`EntityHashMap<Entity>`], then calls the
164165
/// provided function with it. This allows one to allocate new entity references in this [`World`] that are
165166
/// guaranteed to never point at a living entity now or in the future. This functionality is useful for safely
166167
/// mapping entity identifiers that point at entities outside the source world. The passed function, `f`, is called
167168
/// within the scope of this world. Its return value is then returned from `world_scope` as the generic type
168169
/// parameter `R`.
169170
pub fn world_scope<R>(
170-
entity_map: &'m mut EntityHashMap<Entity, Entity>,
171+
entity_map: &'m mut EntityHashMap<Entity>,
171172
world: &mut World,
172173
f: impl FnOnce(&mut World, &mut Self) -> R,
173174
) -> R {
@@ -180,10 +181,8 @@ impl<'m> SceneEntityMapper<'m> {
180181

181182
#[cfg(test)]
182183
mod tests {
183-
use bevy_utils::EntityHashMap;
184-
185184
use crate::{
186-
entity::{Entity, EntityMapper, SceneEntityMapper},
185+
entity::{Entity, EntityHashMap, EntityMapper, SceneEntityMapper},
187186
world::World,
188187
};
189188

crates/bevy_ecs/src/entity/mod.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,14 @@
3636
//! [`EntityWorldMut::insert`]: crate::world::EntityWorldMut::insert
3737
//! [`EntityWorldMut::remove`]: crate::world::EntityWorldMut::remove
3838
mod map_entities;
39+
#[cfg(feature = "bevy_reflect")]
40+
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
41+
pub use map_entities::*;
42+
43+
mod hash;
44+
pub use hash::*;
3945

4046
use bevy_utils::tracing::warn;
41-
pub use map_entities::*;
4247

4348
use crate::{
4449
archetype::{ArchetypeId, ArchetypeRow},
@@ -123,6 +128,11 @@ type IdCursor = isize;
123128
/// [`Query::get`]: crate::system::Query::get
124129
/// [`World`]: crate::world::World
125130
#[derive(Clone, Copy)]
131+
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
132+
#[cfg_attr(
133+
feature = "bevy_reflect",
134+
reflect_value(Hash, PartialEq, Serialize, Deserialize)
135+
)]
126136
// Alignment repr necessary to allow LLVM to better output
127137
// optimised codegen for `to_bits`, `PartialEq` and `Ord`.
128138
#[repr(C, align(8))]
@@ -218,7 +228,7 @@ impl Entity {
218228
/// // ... replace the entities with valid ones.
219229
/// ```
220230
///
221-
/// Deriving [`Reflect`](bevy_reflect::Reflect) for a component that has an `Entity` field:
231+
/// Deriving [`Reflect`] for a component that has an `Entity` field:
222232
///
223233
/// ```no_run
224234
/// # use bevy_ecs::{prelude::*, component::*};
@@ -1092,7 +1102,7 @@ mod tests {
10921102
#[test]
10931103
fn entity_hash_keeps_similar_ids_together() {
10941104
use std::hash::BuildHasher;
1095-
let hash = bevy_utils::EntityHash;
1105+
let hash = EntityHash;
10961106

10971107
let first_id = 0xC0FFEE << 8;
10981108
let first_hash = hash.hash_one(Entity::from_raw(first_id));
@@ -1107,7 +1117,8 @@ mod tests {
11071117
#[test]
11081118
fn entity_hash_id_bitflip_affects_high_7_bits() {
11091119
use std::hash::BuildHasher;
1110-
let hash = bevy_utils::EntityHash;
1120+
1121+
let hash = EntityHash;
11111122

11121123
let first_id = 0xC0FFEE;
11131124
let first_hash = hash.hash_one(Entity::from_raw(first_id)) >> 57;

crates/bevy_ecs/src/reflect/map_entities.rs

+8-13
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
use crate::{
22
component::Component,
3-
entity::{Entity, MapEntities, SceneEntityMapper},
3+
entity::{Entity, EntityHashMap, MapEntities, SceneEntityMapper},
44
world::World,
55
};
66
use bevy_reflect::FromType;
7-
use bevy_utils::EntityHashMap;
87

98
/// For a specific type of component, this maps any fields with values of type [`Entity`] to a new world.
109
/// Since a given `Entity` ID is only valid for the world it came from, when performing deserialization
@@ -18,33 +17,29 @@ pub struct ReflectMapEntities {
1817
}
1918

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

38-
/// A general method for applying [`MapEntities`] behavior to elements in an [`EntityHashMap<Entity, Entity>`]. Unlike
33+
/// A general method for applying [`MapEntities`] behavior to elements in an [`EntityHashMap<Entity>`]. Unlike
3934
/// [`map_all_entities`](Self::map_all_entities), this is applied to specific entities, not all values
40-
/// in the [`EntityHashMap<Entity, Entity>`].
35+
/// in the [`EntityHashMap<Entity>`].
4136
///
4237
/// This is useful mostly for when you need to be careful not to update components that already contain valid entity
4338
/// values. See [`map_all_entities`](Self::map_all_entities) for more details.
4439
pub fn map_entities(
4540
&self,
4641
world: &mut World,
47-
entity_map: &mut EntityHashMap<Entity, Entity>,
42+
entity_map: &mut EntityHashMap<Entity>,
4843
entities: &[Entity],
4944
) {
5045
SceneEntityMapper::world_scope(entity_map, world, |world, mapper| {

0 commit comments

Comments
 (0)