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
+ }
+ }
}
}