diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index f77d677c89271..97296abc62393 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -8,10 +8,10 @@ use anyhow::Result; use bevy_ecs::system::{Res, ResMut}; use bevy_log::warn; use bevy_tasks::TaskPool; -use bevy_utils::{HashMap, Uuid}; +use bevy_utils::{Entry, HashMap, Uuid}; use crossbeam_channel::TryRecvError; use parking_lot::{Mutex, RwLock}; -use std::{collections::hash_map::Entry, path::Path, sync::Arc}; +use std::{path::Path, sync::Arc}; use thiserror::Error; /// Errors that occur while loading assets with an `AssetServer` diff --git a/crates/bevy_ecs/src/entity/map_entities.rs b/crates/bevy_ecs/src/entity/map_entities.rs index b6cf162177859..00080bd0edd2f 100644 --- a/crates/bevy_ecs/src/entity/map_entities.rs +++ b/crates/bevy_ecs/src/entity/map_entities.rs @@ -1,6 +1,5 @@ use crate::entity::Entity; -use bevy_utils::HashMap; -use std::collections::hash_map::Entry; +use bevy_utils::{Entry, HashMap}; use thiserror::Error; #[derive(Error, Debug)] diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index 792501131542c..c8cdbfd91fbb6 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -3,10 +3,9 @@ use crate::{ entity::Entity, storage::{BlobVec, SparseSet}, }; -use bevy_utils::{AHasher, HashMap}; +use bevy_utils::HashMap; use std::{ cell::UnsafeCell, - hash::{Hash, Hasher}, ops::{Index, IndexMut}, ptr::NonNull, }; @@ -415,7 +414,7 @@ impl Table { /// Can be accessed via [`Storages`](crate::storage::Storages) pub struct Tables { tables: Vec, - table_ids: HashMap, + table_ids: HashMap, TableId>, } impl Default for Tables { @@ -472,18 +471,21 @@ impl Tables { component_ids: &[ComponentId], components: &Components, ) -> TableId { - let mut hasher = AHasher::default(); - component_ids.hash(&mut hasher); - let hash = hasher.finish(); let tables = &mut self.tables; - *self.table_ids.entry(hash).or_insert_with(move || { - let mut table = Table::with_capacity(0, component_ids.len()); - for component_id in component_ids.iter() { - table.add_column(components.get_info_unchecked(*component_id)); - } - tables.push(table); - TableId(tables.len() - 1) - }) + let (_key, value) = self + .table_ids + .raw_entry_mut() + .from_key(component_ids) + .or_insert_with(|| { + let mut table = Table::with_capacity(0, component_ids.len()); + for component_id in component_ids.iter() { + table.add_column(components.get_info_unchecked(*component_id)); + } + tables.push(table); + (component_ids.to_vec(), TableId(tables.len() - 1)) + }); + + *value } pub fn iter(&self) -> std::slice::Iter<'_, Table> { diff --git a/crates/bevy_input/src/input.rs b/crates/bevy_input/src/input.rs index b98e2b25e85ca..328e162561baa 100644 --- a/crates/bevy_input/src/input.rs +++ b/crates/bevy_input/src/input.rs @@ -29,13 +29,13 @@ use bevy_ecs::schedule::State; /// * Call the [`Input::release`] method for each release event. /// * Call the [`Input::clear`] method at each frame start, before processing events. #[derive(Debug, Clone)] -pub struct Input { +pub struct Input { pressed: HashSet, just_pressed: HashSet, just_released: HashSet, } -impl Default for Input { +impl Default for Input { fn default() -> Self { Self { pressed: Default::default(), diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index f437a31a88dd8..9d5c431e6ddd6 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -25,6 +25,7 @@ thiserror = "1.0" serde = "1" smallvec = { version = "1.6", features = ["serde", "union", "const_generics"], optional = true } glam = { version = "0.20.0", features = ["serde"], optional = true } +hashbrown = { version = "0.11", features = ["serde"], optional = true } [dev-dependencies] ron = "0.7.0" diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 1f600abcefc18..0b1d4097e1e11 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -6,7 +6,7 @@ use crate::{ }; use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_value}; -use bevy_utils::{AHashExt, Duration, HashMap, HashSet}; +use bevy_utils::{Duration, HashMap, HashSet}; use serde::{Deserialize, Serialize}; use std::{ any::Any, diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index a123df1737852..4fdba328c6714 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -1,6 +1,6 @@ -use std::{any::Any, collections::hash_map::Entry}; +use std::any::Any; -use bevy_utils::HashMap; +use bevy_utils::{Entry, HashMap}; use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index f85c46cbcfa4c..f8a413e6f6030 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -1,6 +1,6 @@ use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; -use bevy_utils::HashMap; -use std::{any::Any, borrow::Cow, collections::hash_map::Entry}; +use bevy_utils::{Entry, HashMap}; +use std::{any::Any, borrow::Cow}; /// A reflected Rust regular struct type. /// diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index d4ec249133ee8..77ed782eb61cb 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -10,8 +10,8 @@ use crate::{ use bevy_app::EventReader; use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_ecs::system::{Res, ResMut}; -use bevy_utils::{tracing::error, HashMap, HashSet}; -use std::{collections::hash_map::Entry, hash::Hash, ops::Deref, sync::Arc}; +use bevy_utils::{tracing::error, Entry, HashMap, HashSet}; +use std::{hash::Hash, ops::Deref, sync::Arc}; use thiserror::Error; use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout}; diff --git a/crates/bevy_render/src/texture/texture_cache.rs b/crates/bevy_render/src/texture/texture_cache.rs index cca353a1d5e62..43cac472c154f 100644 --- a/crates/bevy_render/src/texture/texture_cache.rs +++ b/crates/bevy_render/src/texture/texture_cache.rs @@ -3,7 +3,7 @@ use crate::{ renderer::RenderDevice, }; use bevy_ecs::prelude::ResMut; -use bevy_utils::HashMap; +use bevy_utils::{Entry, HashMap}; use wgpu::{TextureDescriptor, TextureViewDescriptor}; /// The internal representation of a [`CachedTexture`] used to track whether it was recently used @@ -39,7 +39,7 @@ impl TextureCache { descriptor: TextureDescriptor<'static>, ) -> CachedTexture { match self.textures.entry(descriptor) { - std::collections::hash_map::Entry::Occupied(mut entry) => { + Entry::Occupied(mut entry) => { for texture in entry.get_mut().iter_mut() { if !texture.taken { texture.frames_since_last_use = 0; @@ -64,7 +64,7 @@ impl TextureCache { default_view, } } - std::collections::hash_map::Entry::Vacant(entry) => { + Entry::Vacant(entry) => { let texture = render_device.create_texture(entry.key()); let default_view = texture.create_view(&TextureViewDescriptor::default()); entry.insert(vec![CachedTextureMeta { diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index 8d65543b2cfde..c6b93f336d695 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -14,6 +14,7 @@ ahash = "0.7.0" tracing = {version = "0.1", features = ["release_max_level_info"]} instant = { version = "0.1", features = ["wasm-bindgen"] } uuid = { version = "0.8", features = ["v4", "serde"] } +hashbrown = { version = "0.11", features = ["serde"] } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = {version = "0.2.0", features = ["js"]} diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index eeb0ba668311d..d762e9577e7fb 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -3,12 +3,22 @@ pub mod label; pub use ahash::AHasher; pub use enum_variant_meta::*; +pub type Entry<'a, K, V> = hashbrown::hash_map::Entry<'a, K, V, RandomState>; +pub use hashbrown; +use hashbrown::hash_map::RawEntryMut; pub use instant::{Duration, Instant}; pub use tracing; pub use uuid::Uuid; use ahash::RandomState; -use std::{future::Future, pin::Pin}; +use std::{ + fmt::Debug, + future::Future, + hash::{BuildHasher, Hash, Hasher}, + marker::PhantomData, + ops::Deref, + pin::Pin, +}; #[cfg(not(target_arch = "wasm32"))] pub type BoxedFuture<'a, T> = Pin + Send + 'a>>; @@ -32,178 +42,170 @@ impl std::hash::BuildHasher for FixedState { } } -/// A [`HashMap`][std::collections::HashMap] implementing [`aHash`], a high +/// A [`HashMap`][hashbrown::HashMap] implementing aHash, a high /// speed keyed hashing algorithm intended for use in in-memory hashmaps. /// -/// `aHash` is designed for performance and is NOT cryptographically secure. -/// -/// # Construction -/// -/// Users may be surprised when a `HashMap` cannot be constructed with `HashMap::new()`: -/// -/// ```compile_fail -/// # fn main() { -/// use bevy_utils::HashMap; -/// -/// // Produces an error like "no function or associated item named `new` found [...]" -/// let map: HashMap = HashMap::new(); -/// # } -/// ``` +/// aHash is designed for performance and is NOT cryptographically secure. +pub type HashMap = hashbrown::HashMap; + +/// A stable hash map implementing aHash, a high speed keyed hashing algorithm +/// intended for use in in-memory hashmaps. /// -/// The standard library's [`HashMap::new`][std::collections::HashMap::new] is -/// implemented only for `HashMap`s which use the -/// [`DefaultHasher`][std::collections::hash_map::DefaultHasher], so it's not -/// available for Bevy's `HashMap`. +/// Unlike [`HashMap`] this has an iteration order that only depends on the order +/// of insertions and deletions and not a random source. /// -/// However, an empty `HashMap` can easily be constructed using the `Default` -/// implementation: +/// aHash is designed for performance and is NOT cryptographically secure. +pub type StableHashMap = hashbrown::HashMap; + +/// A [`HashSet`][hashbrown::HashSet] implementing aHash, a high +/// speed keyed hashing algorithm intended for use in in-memory hashmaps. /// -/// ``` -/// # fn main() { -/// use bevy_utils::HashMap; +/// aHash is designed for performance and is NOT cryptographically secure. +pub type HashSet = hashbrown::HashSet; + +/// A stable hash set implementing aHash, a high speed keyed hashing algorithm +/// intended for use in in-memory hashmaps. /// -/// // This works! -/// let map: HashMap = HashMap::default(); -/// assert!(map.is_empty()); -/// # } -/// ``` +/// Unlike [`HashSet`] this has an iteration order that only depends on the order +/// of insertions and deletions and not a random source. /// -/// [`aHash`]: https://github.com/tkaitchuck/aHash -pub type HashMap = std::collections::HashMap; +/// aHash is designed for performance and is NOT cryptographically secure. +pub type StableHashSet = hashbrown::HashSet; + +/// A pre-hashed value of a specific type. Pre-hashing enables memoization of hashes that are expensive to compute. +/// It also enables faster [`PartialEq`] comparisons by short circuiting on hash equality. +/// See [`PassHash`] and [`PassHasher`] for a "pass through" [`BuildHasher`] and [`Hasher`] implementation +/// designed to work with [`Hashed`] +/// See [`PreHashMap`] for a hashmap pre-configured to use [`Hashed`] keys. +pub struct Hashed { + hash: u64, + value: V, + marker: PhantomData, +} + +impl Hashed { + /// Pre-hashes the given value using the [`BuildHasher`] configured in the [`Hashed`] type. + pub fn new(value: V) -> Self { + let builder = H::default(); + let mut hasher = builder.build_hasher(); + value.hash(&mut hasher); + Self { + hash: hasher.finish(), + value, + marker: PhantomData, + } + } -pub trait AHashExt { - fn with_capacity(capacity: usize) -> Self; + /// The pre-computed hash. + #[inline] + pub fn hash(&self) -> u64 { + self.hash + } } -impl AHashExt for HashMap { - /// Creates an empty `HashMap` with the specified capacity with aHash. - /// - /// The hash map will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash map will not allocate. - /// - /// # Examples - /// - /// ``` - /// use bevy_utils::{HashMap, AHashExt}; - /// let mut map: HashMap<&str, i32> = HashMap::with_capacity(10); - /// assert!(map.capacity() >= 10); - /// ``` +impl Hash for Hashed { #[inline] - fn with_capacity(capacity: usize) -> Self { - HashMap::with_capacity_and_hasher(capacity, RandomState::default()) + fn hash(&self, state: &mut R) { + state.write_u64(self.hash); } } -/// A stable std hash map implementing `aHash`, a high speed keyed hashing algorithm -/// intended for use in in-memory hashmaps. -/// -/// Unlike [`HashMap`] this has an iteration order that only depends on the order -/// of insertions and deletions and not a random source. -/// -/// `aHash` is designed for performance and is NOT cryptographically secure. -pub type StableHashMap = std::collections::HashMap; - -impl AHashExt for StableHashMap { - /// Creates an empty `StableHashMap` with the specified capacity with `aHash`. - /// - /// The hash map will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash map will not allocate. - /// - /// # Examples - /// - /// ``` - /// use bevy_utils::{StableHashMap, AHashExt}; - /// let mut map: StableHashMap<&str, i32> = StableHashMap::with_capacity(10); - /// assert!(map.capacity() >= 10); - /// ``` +impl Deref for Hashed { + type Target = V; + #[inline] - fn with_capacity(capacity: usize) -> Self { - StableHashMap::with_capacity_and_hasher(capacity, FixedState::default()) + fn deref(&self) -> &Self::Target { + &self.value } } -/// A [`HashSet`][std::collections::HashSet] implementing [`aHash`], a high -/// speed keyed hashing algorithm intended for use in in-memory hashmaps. -/// -/// `aHash` is designed for performance and is NOT cryptographically secure. -/// -/// # Construction -/// -/// Users may be surprised when a `HashSet` cannot be constructed with `HashSet::new()`: -/// -/// ```compile_fail -/// # fn main() { -/// use bevy_utils::HashSet; -/// -/// // Produces an error like "no function or associated item named `new` found [...]" -/// let map: HashSet = HashSet::new(); -/// # } -/// ``` -/// -/// The standard library's [`HashSet::new`][std::collections::HashSet::new] is -/// implemented only for `HashSet`s which use the -/// [`DefaultHasher`][std::collections::hash_map::DefaultHasher], so it's not -/// available for Bevy's `HashSet`. -/// -/// However, an empty `HashSet` can easily be constructed using the `Default` -/// implementation: -/// -/// ``` -/// # fn main() { -/// use bevy_utils::HashSet; -/// -/// // This works! -/// let map: HashSet = HashSet::default(); -/// assert!(map.is_empty()); -/// # } -/// ``` -/// -/// [`aHash`]: https://github.com/tkaitchuck/aHash -pub type HashSet = std::collections::HashSet; - -impl AHashExt for HashSet { - /// Creates an empty `HashSet` with the specified capacity with aHash. - /// - /// The hash set will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash set will not allocate. - /// - /// # Examples - /// - /// ``` - /// use bevy_utils::{HashSet, AHashExt}; - /// let set: HashSet = HashSet::with_capacity(10); - /// assert!(set.capacity() >= 10); - /// ``` +impl PartialEq for Hashed { + /// A fast impl of [`PartialEq`] that first checks that `other`'s pre-computed hash + /// matches this value's pre-computed hash. #[inline] - fn with_capacity(capacity: usize) -> Self { - HashSet::with_capacity_and_hasher(capacity, RandomState::default()) + fn eq(&self, other: &Self) -> bool { + self.hash == other.hash && self.value.eq(&other.value) } } -/// A stable std hash set implementing `aHash`, a high speed keyed hashing algorithm -/// intended for use in in-memory hashmaps. -/// -/// Unlike [`HashSet`] this has an iteration order that only depends on the order -/// of insertions and deletions and not a random source. -/// -/// `aHash` is designed for performance and is NOT cryptographically secure. -pub type StableHashSet = std::collections::HashSet; - -impl AHashExt for StableHashSet { - /// Creates an empty `StableHashSet` with the specified capacity with `aHash`. - /// - /// The hash set will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash set will not allocate. - /// - /// # Examples - /// - /// ``` - /// use bevy_utils::{StableHashSet, AHashExt}; - /// let set: StableHashSet = StableHashSet::with_capacity(10); - /// assert!(set.capacity() >= 10); - /// ``` +impl Debug for Hashed { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Hashed") + .field("hash", &self.hash) + .field("value", &self.value) + .finish() + } +} + +impl Clone for Hashed { + #[inline] + fn clone(&self) -> Self { + Self { + hash: self.hash, + value: self.value.clone(), + marker: PhantomData, + } + } +} + +impl Eq for Hashed {} + +/// A [`BuildHasher`] that results in a [`PassHasher`]. +#[derive(Default)] +pub struct PassHash; + +impl BuildHasher for PassHash { + type Hasher = PassHasher; + + fn build_hasher(&self) -> Self::Hasher { + PassHasher::default() + } +} + +#[derive(Debug, Default)] +pub struct PassHasher { + hash: u64, +} + +impl Hasher for PassHasher { + fn write(&mut self, _bytes: &[u8]) { + panic!("can only hash u64 using PassHasher"); + } + + #[inline] + fn write_u64(&mut self, i: u64) { + self.hash = i; + } + + #[inline] + fn finish(&self) -> u64 { + self.hash + } +} + +/// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing. +pub type PreHashMap = hashbrown::HashMap, V, PassHash>; + +/// Extension methods intended to add functionality to [`PreHashMap`]. +pub trait PreHashMapExt { + /// Tries to get or insert the value for the given `key` using the pre-computed hash first. + /// If the [`PreHashMap`] does not already contain the `key`, it will clone it and insert + /// the value returned by `func`. + fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V; +} + +impl PreHashMapExt for PreHashMap { #[inline] - fn with_capacity(capacity: usize) -> Self { - StableHashSet::with_capacity_and_hasher(capacity, FixedState::default()) + fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V { + let entry = self + .raw_entry_mut() + .from_key_hashed_nocheck(key.hash(), key); + match entry { + RawEntryMut::Occupied(entry) => entry.into_mut(), + RawEntryMut::Vacant(entry) => { + let (_, value) = entry.insert_hashed_nocheck(key.hash(), key.clone(), func()); + value + } + } } }