Hash stability guarantees (#11690)
# Objective We currently over/underpromise hash stability: - `HashMap`/`HashSet` use `BuildHasherDefault<AHasher>` instead of `RandomState`. As a result, the hash is stable within the same run. - [aHash isn't stable between devices (and versions)](https://github.com/tkaitchuck/ahash?tab=readme-ov-file#goals-and-non-goals), yet it's used for `StableHashMap`/`StableHashSet` - the specialized hashmaps are stable Interestingly, `StableHashMap`/`StableHashSet` aren't used by Bevy itself (anymore). ## Solution Add/fix documentation ## Alternatives For `StableHashMap`/`StableHashSet`: - remove them - revive #7107 --- ## Changelog - added iteration stability guarantees for different hashmaps
This commit is contained in:
parent
381f3d3fa5
commit
8faaef17e5
@ -1,7 +1,7 @@
|
|||||||
//! Helpers for working with Bevy reflection.
|
//! Helpers for working with Bevy reflection.
|
||||||
|
|
||||||
use crate::TypeInfo;
|
use crate::TypeInfo;
|
||||||
use bevy_utils::{FixedState, StableHashMap};
|
use bevy_utils::{FixedState, NoOpTypeIdHash, TypeIdMap};
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
hash::BuildHasher,
|
hash::BuildHasher,
|
||||||
@ -195,7 +195,7 @@ impl<T: TypedProperty> NonGenericTypeCell<T> {
|
|||||||
/// ```
|
/// ```
|
||||||
/// [`impl_type_path`]: crate::impl_type_path
|
/// [`impl_type_path`]: crate::impl_type_path
|
||||||
/// [`TypePath`]: crate::TypePath
|
/// [`TypePath`]: crate::TypePath
|
||||||
pub struct GenericTypeCell<T: TypedProperty>(RwLock<StableHashMap<TypeId, &'static T::Stored>>);
|
pub struct GenericTypeCell<T: TypedProperty>(RwLock<TypeIdMap<&'static T::Stored>>);
|
||||||
|
|
||||||
/// See [`GenericTypeCell`].
|
/// See [`GenericTypeCell`].
|
||||||
pub type GenericTypeInfoCell = GenericTypeCell<TypeInfo>;
|
pub type GenericTypeInfoCell = GenericTypeCell<TypeInfo>;
|
||||||
@ -205,9 +205,7 @@ pub type GenericTypePathCell = GenericTypeCell<TypePathComponent>;
|
|||||||
impl<T: TypedProperty> GenericTypeCell<T> {
|
impl<T: TypedProperty> GenericTypeCell<T> {
|
||||||
/// Initialize a [`GenericTypeCell`] for generic types.
|
/// Initialize a [`GenericTypeCell`] for generic types.
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
// Use `bevy_utils::StableHashMap` over `bevy_utils::HashMap`
|
Self(RwLock::new(TypeIdMap::with_hasher(NoOpTypeIdHash)))
|
||||||
// because `BuildHasherDefault` is unfortunately not const.
|
|
||||||
Self(RwLock::new(StableHashMap::with_hasher(FixedState)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the [`TypedProperty`] stored in the cell.
|
/// Returns a reference to the [`TypedProperty`] stored in the cell.
|
||||||
|
|||||||
@ -87,13 +87,17 @@ impl BuildHasher for FixedState {
|
|||||||
/// speed keyed hashing algorithm intended for use in in-memory hashmaps.
|
/// speed keyed hashing algorithm intended for use in in-memory hashmaps.
|
||||||
///
|
///
|
||||||
/// aHash is designed for performance and is NOT cryptographically secure.
|
/// aHash is designed for performance and is NOT cryptographically secure.
|
||||||
|
///
|
||||||
|
/// Within the same execution of the program iteration order of different
|
||||||
|
/// `HashMap`s only depends on the order of insertions and deletions,
|
||||||
|
/// but it will not be stable between multiple executions of the program.
|
||||||
pub type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<AHasher>>;
|
pub type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<AHasher>>;
|
||||||
|
|
||||||
/// A stable hash map implementing aHash, a high speed keyed hashing algorithm
|
/// A stable hash map implementing aHash, a high speed keyed hashing algorithm
|
||||||
/// intended for use in in-memory hashmaps.
|
/// intended for use in in-memory hashmaps.
|
||||||
///
|
///
|
||||||
/// Unlike [`HashMap`] this has an iteration order that only depends on the order
|
/// Unlike [`HashMap`] the iteration order stability extends between executions
|
||||||
/// of insertions and deletions and not a random source.
|
/// using the same Bevy version on the same device.
|
||||||
///
|
///
|
||||||
/// aHash is designed for performance and is NOT cryptographically secure.
|
/// aHash is designed for performance and is NOT cryptographically secure.
|
||||||
pub type StableHashMap<K, V> = hashbrown::HashMap<K, V, FixedState>;
|
pub type StableHashMap<K, V> = hashbrown::HashMap<K, V, FixedState>;
|
||||||
@ -102,13 +106,17 @@ pub type StableHashMap<K, V> = hashbrown::HashMap<K, V, FixedState>;
|
|||||||
/// speed keyed hashing algorithm intended for use in in-memory hashmaps.
|
/// speed keyed hashing algorithm intended for use in in-memory hashmaps.
|
||||||
///
|
///
|
||||||
/// aHash is designed for performance and is NOT cryptographically secure.
|
/// aHash is designed for performance and is NOT cryptographically secure.
|
||||||
|
///
|
||||||
|
/// Within the same execution of the program iteration order of different
|
||||||
|
/// `HashSet`s only depends on the order of insertions and deletions,
|
||||||
|
/// but it will not be stable between multiple executions of the program.
|
||||||
pub type HashSet<K> = hashbrown::HashSet<K, BuildHasherDefault<AHasher>>;
|
pub type HashSet<K> = hashbrown::HashSet<K, BuildHasherDefault<AHasher>>;
|
||||||
|
|
||||||
/// A stable hash set implementing aHash, a high speed keyed hashing algorithm
|
/// A stable hash set implementing aHash, a high speed keyed hashing algorithm
|
||||||
/// intended for use in in-memory hashmaps.
|
/// intended for use in in-memory hashmaps.
|
||||||
///
|
///
|
||||||
/// Unlike [`HashSet`] this has an iteration order that only depends on the order
|
/// Unlike [`HashMap`] the iteration order stability extends between executions
|
||||||
/// of insertions and deletions and not a random source.
|
/// using the same Bevy version on the same device.
|
||||||
///
|
///
|
||||||
/// aHash is designed for performance and is NOT cryptographically secure.
|
/// aHash is designed for performance and is NOT cryptographically secure.
|
||||||
pub type StableHashSet<K> = hashbrown::HashSet<K, FixedState>;
|
pub type StableHashSet<K> = hashbrown::HashSet<K, FixedState>;
|
||||||
@ -224,6 +232,7 @@ impl Hasher for PassHasher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing.
|
/// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing.
|
||||||
|
/// Iteration order only depends on the order of insertions and deletions.
|
||||||
pub type PreHashMap<K, V> = hashbrown::HashMap<Hashed<K>, V, PassHash>;
|
pub type PreHashMap<K, V> = hashbrown::HashMap<Hashed<K>, V, PassHash>;
|
||||||
|
|
||||||
/// Extension methods intended to add functionality to [`PreHashMap`].
|
/// Extension methods intended to add functionality to [`PreHashMap`].
|
||||||
@ -322,17 +331,30 @@ impl Hasher for EntityHasher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A [`HashMap`] pre-configured to use [`EntityHash`] hashing.
|
/// A [`HashMap`] pre-configured to use [`EntityHash`] hashing.
|
||||||
|
/// Iteration order only depends on the order of insertions and deletions.
|
||||||
pub type EntityHashMap<K, V> = hashbrown::HashMap<K, V, EntityHash>;
|
pub type EntityHashMap<K, V> = hashbrown::HashMap<K, V, EntityHash>;
|
||||||
|
|
||||||
/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing.
|
/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing.
|
||||||
|
/// Iteration order only depends on the order of insertions and deletions.
|
||||||
pub type EntityHashSet<T> = hashbrown::HashSet<T, EntityHash>;
|
pub type EntityHashSet<T> = hashbrown::HashSet<T, EntityHash>;
|
||||||
|
|
||||||
/// A specialized hashmap type with Key of [`TypeId`]
|
/// A specialized hashmap type with Key of [`TypeId`]
|
||||||
pub type TypeIdMap<V> =
|
/// Iteration order only depends on the order of insertions and deletions.
|
||||||
hashbrown::HashMap<TypeId, V, std::hash::BuildHasherDefault<NoOpTypeIdHasher>>;
|
pub type TypeIdMap<V> = hashbrown::HashMap<TypeId, V, NoOpTypeIdHash>;
|
||||||
|
|
||||||
|
/// [`BuildHasher`] for [`TypeId`]s.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NoOpTypeIdHash;
|
||||||
|
|
||||||
|
impl BuildHasher for NoOpTypeIdHash {
|
||||||
|
type Hasher = NoOpTypeIdHasher;
|
||||||
|
|
||||||
|
fn build_hasher(&self) -> Self::Hasher {
|
||||||
|
NoOpTypeIdHasher(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[derive(Default)]
|
|
||||||
pub struct NoOpTypeIdHasher(u64);
|
pub struct NoOpTypeIdHasher(u64);
|
||||||
|
|
||||||
// TypeId already contains a high-quality hash, so skip re-hashing that hash.
|
// TypeId already contains a high-quality hash, so skip re-hashing that hash.
|
||||||
@ -469,4 +491,18 @@ mod tests {
|
|||||||
|
|
||||||
std::hash::Hash::hash(&TypeId::of::<()>(), &mut Hasher);
|
std::hash::Hash::hash(&TypeId::of::<()>(), &mut Hasher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stable_hash_within_same_program_execution() {
|
||||||
|
let mut map_1 = HashMap::new();
|
||||||
|
let mut map_2 = HashMap::new();
|
||||||
|
for i in 1..10 {
|
||||||
|
map_1.insert(i, i);
|
||||||
|
map_2.insert(i, i);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
map_1.iter().collect::<Vec<_>>(),
|
||||||
|
map_2.iter().collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user