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 2 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
1 change: 1 addition & 0 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ thiserror = "1.0"

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

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

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

/// 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 {
EntityHasher::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`] 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!("can only hash u64 using EntityHasher");
}

#[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`] pre-configured to use [`EntityHash`] hashing.
pub type EntityHashMap<K, V> = hashbrown::HashMap<K, V, EntityHash>;

/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing.
pub type EntityHashSet<T> = hashbrown::HashSet<T, EntityHash>;
doonv marked this conversation as resolved.
Show resolved Hide resolved

#[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::<u64, usize>: Clone);
// EntityHashMap should implement Reflect
assert_impl_all!(EntityHashMap::<i32, i32>: Reflect);
}
10 changes: 4 additions & 6 deletions crates/bevy_ecs/src/entity/map_entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ 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.
///
Expand Down Expand Up @@ -136,12 +137,9 @@ impl<'m> EntityMapper<'m> {

#[cfg(test)]
mod tests {
use bevy_utils::EntityHashMap;
use super::{Entity, EntityHashMap, EntityMapper};

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

#[test]
fn entity_mapper() {
Expand Down
14 changes: 10 additions & 4 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 @@ -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
3 changes: 1 addition & 2 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, EntityMapper, MapEntities},
entity::{Entity, EntityHashMap, EntityMapper, MapEntities},
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 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
3 changes: 2 additions & 1 deletion crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use bevy_asset::{
};
use bevy_core::Name;
use bevy_core_pipeline::prelude::Camera3dBundle;
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::{entity::Entity, world::World};
use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder};
use bevy_log::{error, warn};
Expand Down Expand Up @@ -33,7 +34,7 @@ use bevy_scene::Scene;
#[cfg(not(target_arch = "wasm32"))]
use bevy_tasks::IoTaskPool;
use bevy_transform::components::Transform;
use bevy_utils::{EntityHashMap, HashMap, HashSet};
use bevy_utils::{HashMap, HashSet};
use gltf::{
accessor::Iter,
mesh::{util::ReadIndices, Mode},
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
StandardMaterial,
};
use bevy_asset::Handle;
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::{bundle::Bundle, component::Component, prelude::Entity, reflect::ReflectComponent};
use bevy_reflect::Reflect;
use bevy_render::{
Expand All @@ -11,7 +12,6 @@ use bevy_render::{
view::{InheritedVisibility, ViewVisibility, Visibility, VisibleEntities},
};
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::EntityHashMap;

/// A component bundle for PBR entities with a [`Mesh`] and a [`StandardMaterial`].
pub type PbrBundle = MaterialMeshBundle<StandardMaterial>;
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashSet;

use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::prelude::*;
use bevy_math::{
AspectRatio, Mat4, UVec2, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles,
Expand All @@ -16,7 +17,7 @@ use bevy_render::{
view::{InheritedVisibility, RenderLayers, ViewVisibility, VisibleEntities},
};
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::{tracing::warn, EntityHashMap};
use bevy_utils::tracing::warn;

use crate::*;

Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_pbr/src/light_probe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use bevy_app::{App, Plugin};
use bevy_asset::load_internal_asset;
use bevy_core_pipeline::core_3d::Camera3d;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::{
component::Component,
entity::Entity,
Expand All @@ -24,7 +25,7 @@ use bevy_render::{
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::prelude::GlobalTransform;
use bevy_utils::{EntityHashMap, FloatOrd};
use bevy_utils::FloatOrd;

use crate::light_probe::environment_map::{
binding_arrays_are_usable, EnvironmentMapIds, EnvironmentMapLight, RenderViewEnvironmentMaps,
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_pbr/src/lightmap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, AssetId, Handle};
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::{
component::Component,
entity::Entity,
Expand All @@ -43,7 +44,7 @@ use bevy_render::{
mesh::Mesh, render_asset::RenderAssets, render_resource::Shader, texture::Image,
view::ViewVisibility, Extract, ExtractSchedule, RenderApp,
};
use bevy_utils::{EntityHashMap, HashSet};
use bevy_utils::HashSet;

use crate::RenderMeshInstances;

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use bevy_transform::{components::GlobalTransform, prelude::Transform};
use bevy_utils::{
nonmax::NonMaxU32,
tracing::{error, warn},
EntityHashMap,
};
use bevy_ecs::entity::EntityHashMap;
use std::{hash::Hash, num::NonZeroU64, ops::Range};

use crate::*;
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use bevy_core_pipeline::{
deferred::{AlphaMask3dDeferred, Opaque3dDeferred},
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::{
prelude::*,
query::ROQueryItem,
Expand All @@ -33,7 +34,7 @@ use bevy_render::{
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::components::GlobalTransform;
use bevy_utils::{tracing::error, EntityHashMap, Entry, HashMap, Hashed};
use bevy_utils::{tracing::error, Entry, HashMap, Hashed};
use std::cell::Cell;
use thread_local::ThreadLocal;

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/render/morph.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{iter, mem};

use bevy_derive::{Deref, DerefMut};
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::prelude::*;
use bevy_render::{
batching::NoAutomaticBatching,
Expand All @@ -10,7 +11,6 @@ use bevy_render::{
view::ViewVisibility,
Extract,
};
use bevy_utils::EntityHashMap;
use bytemuck::Pod;

#[derive(Component)]
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/render/skin.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bevy_asset::Assets;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::prelude::*;
use bevy_math::Mat4;
use bevy_render::{
Expand All @@ -11,7 +12,6 @@ use bevy_render::{
Extract,
};
use bevy_transform::prelude::GlobalTransform;
use bevy_utils::EntityHashMap;

/// Maximum number of joints supported for skinned meshes.
pub const MAX_JOINTS: usize = 256;
Expand Down
6 changes: 1 addition & 5 deletions crates/bevy_reflect/src/impls/std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ impl_reflect_value!(isize(
impl_reflect_value!(f32(Debug, PartialEq, Serialize, Deserialize, Default));
impl_reflect_value!(f64(Debug, PartialEq, Serialize, Deserialize, Default));
impl_type_path!(str);
impl_type_path!(::bevy_utils::EntityHash);
impl_reflect_value!(::alloc::string::String(
Debug,
Hash,
Expand Down Expand Up @@ -1516,15 +1515,12 @@ mod tests {
Enum, FromReflect, Reflect, ReflectSerialize, TypeInfo, TypeRegistry, Typed, VariantInfo,
VariantType,
};
use bevy_utils::HashMap;
use bevy_utils::{Duration, Instant};
use bevy_utils::{EntityHashMap, HashMap};
use static_assertions::assert_impl_all;
use std::f32::consts::{PI, TAU};
use std::path::Path;

// EntityHashMap should implement Reflect
assert_impl_all!(EntityHashMap<i32, i32>: Reflect);

#[test]
fn can_serialize_duration() {
let mut type_registry = TypeRegistry::default();
Expand Down
5 changes: 1 addition & 4 deletions crates/bevy_render/src/extract_instances.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ use bevy_app::{App, Plugin};
use bevy_asset::{Asset, AssetId, Handle};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
prelude::Entity,
query::{QueryFilter, QueryItem, ReadOnlyQueryData},
system::{lifetimeless::Read, Query, ResMut, Resource},
entity::EntityHashMap, prelude::Entity, query::{QueryFilter, QueryItem, ReadOnlyQueryData}, system::{lifetimeless::Read, Query, ResMut, Resource}
};
use bevy_utils::EntityHashMap;

use crate::{prelude::ViewVisibility, Extract, ExtractSchedule, RenderApp};

Expand Down
3 changes: 1 addition & 2 deletions crates/bevy_render/src/primitives/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use std::borrow::Borrow;

use bevy_ecs::{component::Component, prelude::Entity, reflect::ReflectComponent};
use bevy_ecs::{component::Component, entity::EntityHashMap, prelude::Entity, reflect::ReflectComponent};
use bevy_math::{Affine3A, Mat3A, Mat4, Vec3, Vec3A, Vec4, Vec4Swizzles};
use bevy_reflect::Reflect;
use bevy_utils::EntityHashMap;

/// An axis-aligned bounding box, defined by:
/// - a center,
Expand Down
Loading
Loading