Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move EntityHash related types into bevy_ecs #11498

Merged
merged 19 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ path = "benches/bevy_math/bezier.rs"
harness = false

[[bench]]
name = "utils"
path = "benches/bevy_utils/entity_hash.rs"
name = "entity_hash"
path = "benches/bevy_ecs/world/entity_hash.rs"
harness = false
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use bevy_ecs::entity::Entity;
use bevy_utils::EntityHashSet;
use bevy_ecs::entity::{Entity, EntityHashMap, EntityHashSet};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
Expand Down Expand Up @@ -28,7 +27,7 @@ fn make_entity(rng: &mut impl Rng, size: usize) -> Entity {
e
}

fn entity_set_build_and_lookup(c: &mut Criterion) {
pub fn entity_set_build_and_lookup(c: &mut Criterion) {
let mut group = c.benchmark_group("entity_hash");
for size in SIZES {
// Get some random-but-consistent entities to use for all the benches below.
Expand Down
11 changes: 8 additions & 3 deletions benches/benches/bevy_ecs/world/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use criterion::criterion_group;

mod commands;
mod spawn;
mod world_get;

use commands::*;

mod spawn;
use spawn::*;

mod world_get;
use world_get::*;

mod entity_hash;
use entity_hash::*;

criterion_group!(
world_benches,
empty_commands,
Expand All @@ -30,4 +34,5 @@ criterion_group!(
query_get_many::<2>,
query_get_many::<5>,
query_get_many::<10>,
entity_set_build_and_lookup
);
1 change: 1 addition & 0 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ thiserror = "1.0"

[dev-dependencies]
rand = "0.8"
static_assertions = "1.1.0"

[[example]]
name = "events"
Expand Down
95 changes: 95 additions & 0 deletions crates/bevy_ecs/src/entity/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::hash::{BuildHasher, Hasher};

use bevy_reflect::Reflect;
doonv marked this conversation as resolved.
Show resolved Hide resolved
use bevy_utils::hashbrown;

use super::Entity;

/// A [`BuildHasher`] that results in a [`EntityHasher`].
#[derive(Default, Clone, Reflect)]
pub struct EntityHash;

impl BuildHasher for EntityHash {
type Hasher = EntityHasher;

fn build_hasher(&self) -> Self::Hasher {
Self::Hasher::default()
}
}

/// A very fast hash that is only designed to work on generational indices
/// like [`Entity`]. It will panic if attempting to hash a type containing
/// non-u64 fields.
///
/// This is heavily optimized for typical cases, where you have mostly live
/// entities, and works particularly well for contiguous indices.
///
/// If you have an unusual case -- say all your indices are multiples of 256
/// or most of the entities are dead generations -- then you might want also to
/// try [`AHasher`](bevy_utils::AHasher) for a slower hash computation but fewer lookup conflicts.
#[derive(Debug, Default)]
pub struct EntityHasher {
hash: u64,
}

impl Hasher for EntityHasher {
#[inline]
fn finish(&self) -> u64 {
self.hash
}

fn write(&mut self, _bytes: &[u8]) {
panic!("EntityHasher can only hash u64 fields.");
}

#[inline]
fn write_u64(&mut self, bits: u64) {
// SwissTable (and thus `hashbrown`) cares about two things from the hash:
// - H1: low bits (masked by `2ⁿ-1`) to pick the slot in which to store the item
// - H2: high 7 bits are used to SIMD optimize hash collision probing
// For more see <https://abseil.io/about/design/swisstables#metadata-layout>

// This hash function assumes that the entity ids are still well-distributed,
// so for H1 leaves the entity id alone in the low bits so that id locality
// will also give memory locality for things spawned together.
// For H2, take advantage of the fact that while multiplication doesn't
// spread entropy to the low bits, it's incredibly good at spreading it
// upward, which is exactly where we need it the most.

// While this does include the generation in the output, it doesn't do so
// *usefully*. H1 won't care until you have over 3 billion entities in
// the table, and H2 won't care until something hits generation 33 million.
// Thus the comment suggesting that this is best for live entities,
// where there won't be generation conflicts where it would matter.

// The high 32 bits of this are ⅟φ for Fibonacci hashing. That works
// particularly well for hashing for the same reason as described in
// <https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/>
// It loses no information because it has a modular inverse.
// (Specifically, `0x144c_bc89_u32 * 0x9e37_79b9_u32 == 1`.)
//
// The low 32 bits make that part of the just product a pass-through.
const UPPER_PHI: u64 = 0x9e37_79b9_0000_0001;

// This is `(MAGIC * index + generation) << 32 + index`, in a single instruction.
self.hash = bits.wrapping_mul(UPPER_PHI);
}
}

/// A [`HashMap`](hashbrown::HashMap) pre-configured to use [`EntityHash`] hashing.
pub type EntityHashMap<V> = hashbrown::HashMap<Entity, V, EntityHash>;

/// A [`HashSet`](hashbrown::HashSet) pre-configured to use [`EntityHash`] hashing.
pub type EntityHashSet = hashbrown::HashSet<Entity, EntityHash>;

#[cfg(test)]
mod tests {
use super::*;
use bevy_reflect::Reflect;
use static_assertions::assert_impl_all;

// Check that the HashMaps are Clone if the key/values are Clone
assert_impl_all!(EntityHashMap::<usize>: Clone);
// EntityHashMap should implement Reflect
assert_impl_all!(EntityHashMap::<i32>: Reflect);
}
35 changes: 17 additions & 18 deletions crates/bevy_ecs/src/entity/map_entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ use crate::{
identifier::masks::{IdentifierMask, HIGH_MASK},
world::World,
};
use bevy_utils::EntityHashMap;

use super::EntityHashMap;

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

/// An implementor of this trait knows how to map an [`Entity`] into another [`Entity`].
///
/// Usually this is done by using an [`EntityHashMap<Entity, Entity>`] to map source entities
/// Usually this is done by using an [`EntityHashMap<Entity>`] to map source entities
/// (mapper inputs) to the current world's entities (mapper outputs).
///
/// More generally, this can be used to map [`Entity`] references between any two [`Worlds`](World).
Expand All @@ -56,10 +57,10 @@ pub trait MapEntities {
///
/// ```
/// # use bevy_ecs::entity::{Entity, EntityMapper};
/// # use bevy_utils::EntityHashMap;
/// # use bevy_ecs::entity::EntityHashMap;
/// #
/// pub struct SimpleEntityMapper {
/// map: EntityHashMap<Entity, Entity>,
/// map: EntityHashMap<Entity>,
/// }
///
/// // Example implementation of EntityMapper where we map an entity to another entity if it exists
Expand Down Expand Up @@ -97,7 +98,7 @@ impl EntityMapper for SceneEntityMapper<'_> {
}
}

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

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

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

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

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

#[cfg(test)]
mod tests {
use bevy_utils::EntityHashMap;

use crate::{
entity::{Entity, EntityMapper, SceneEntityMapper},
entity::{Entity, EntityHashMap, EntityMapper, SceneEntityMapper},
world::World,
};

Expand Down
16 changes: 11 additions & 5 deletions crates/bevy_ecs/src/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@
//! [`EntityWorldMut::insert`]: crate::world::EntityWorldMut::insert
//! [`EntityWorldMut::remove`]: crate::world::EntityWorldMut::remove
mod map_entities;
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
doonv marked this conversation as resolved.
Show resolved Hide resolved
pub use map_entities::*;

mod hash;
pub use hash::*;

use bevy_utils::tracing::warn;
pub use map_entities::*;

use crate::{
archetype::{ArchetypeId, ArchetypeRow},
Expand Down Expand Up @@ -122,7 +126,8 @@ type IdCursor = isize;
/// [`EntityCommands`]: crate::system::EntityCommands
/// [`Query::get`]: crate::system::Query::get
/// [`World`]: crate::world::World
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Reflect)]
#[reflect_value(Hash, PartialEq, Serialize, Deserialize)]
// Alignment repr necessary to allow LLVM to better output
// optimised codegen for `to_bits`, `PartialEq` and `Ord`.
#[repr(C, align(8))]
Expand Down Expand Up @@ -218,7 +223,7 @@ impl Entity {
/// // ... replace the entities with valid ones.
/// ```
///
/// Deriving [`Reflect`](bevy_reflect::Reflect) for a component that has an `Entity` field:
/// Deriving [`Reflect`] for a component that has an `Entity` field:
///
/// ```no_run
/// # use bevy_ecs::{prelude::*, component::*};
Expand Down Expand Up @@ -1092,7 +1097,7 @@ mod tests {
#[test]
fn entity_hash_keeps_similar_ids_together() {
use std::hash::BuildHasher;
let hash = bevy_utils::EntityHash;
let hash = EntityHash;

let first_id = 0xC0FFEE << 8;
let first_hash = hash.hash_one(Entity::from_raw(first_id));
Expand All @@ -1107,7 +1112,8 @@ mod tests {
#[test]
fn entity_hash_id_bitflip_affects_high_7_bits() {
use std::hash::BuildHasher;
let hash = bevy_utils::EntityHash;

let hash = EntityHash;

let first_id = 0xC0FFEE;
let first_hash = hash.hash_one(Entity::from_raw(first_id)) >> 57;
Expand Down
21 changes: 8 additions & 13 deletions crates/bevy_ecs/src/reflect/map_entities.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use crate::{
component::Component,
entity::{Entity, MapEntities, SceneEntityMapper},
entity::{Entity, EntityHashMap, MapEntities, SceneEntityMapper},
world::World,
};
use bevy_reflect::FromType;
use bevy_utils::EntityHashMap;

/// 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 @@ -18,33 +17,29 @@ pub struct ReflectMapEntities {
}

impl ReflectMapEntities {
/// A general method for applying [`MapEntities`] behavior to all elements in an [`EntityHashMap<Entity, Entity>`].
/// A general method for applying [`MapEntities`] behavior to all elements in an [`EntityHashMap<Entity>`].
///
/// Be mindful in its usage: Works best in situations where the entities in the [`EntityHashMap<Entity, Entity>`] are newly
/// 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, Entity>`] might already contain valid entity references, you should use [`map_entities`](Self::map_entities).
/// 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, Entity>,
) {
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, Entity>`]. Unlike
/// 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, Entity>`].
/// 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, Entity>,
entity_map: &mut EntityHashMap<Entity>,
entities: &[Entity],
) {
SceneEntityMapper::world_scope(entity_map, world, |world, mapper| {
Expand Down
6 changes: 2 additions & 4 deletions crates/bevy_ecs/src/reflect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use std::ops::{Deref, DerefMut};

use crate as bevy_ecs;
use crate::{entity::Entity, system::Resource};
use bevy_reflect::{impl_reflect_value, ReflectDeserialize, ReflectSerialize, TypeRegistryArc};
use crate::system::Resource;
use bevy_reflect::TypeRegistryArc;

mod bundle;
mod component;
Expand Down Expand Up @@ -40,5 +40,3 @@ impl DerefMut for AppTypeRegistry {
&mut self.0
}
}

impl_reflect_value!((in bevy_ecs) Entity(Hash, PartialEq, Serialize, Deserialize));
doonv marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading