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.
|
||||
|
||||
use crate::TypeInfo;
|
||||
use bevy_utils::{FixedState, StableHashMap};
|
||||
use bevy_utils::{FixedState, NoOpTypeIdHash, TypeIdMap};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
hash::BuildHasher,
|
||||
@ -195,7 +195,7 @@ impl<T: TypedProperty> NonGenericTypeCell<T> {
|
||||
/// ```
|
||||
/// [`impl_type_path`]: crate::impl_type_path
|
||||
/// [`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`].
|
||||
pub type GenericTypeInfoCell = GenericTypeCell<TypeInfo>;
|
||||
@ -205,9 +205,7 @@ pub type GenericTypePathCell = GenericTypeCell<TypePathComponent>;
|
||||
impl<T: TypedProperty> GenericTypeCell<T> {
|
||||
/// Initialize a [`GenericTypeCell`] for generic types.
|
||||
pub const fn new() -> Self {
|
||||
// Use `bevy_utils::StableHashMap` over `bevy_utils::HashMap`
|
||||
// because `BuildHasherDefault` is unfortunately not const.
|
||||
Self(RwLock::new(StableHashMap::with_hasher(FixedState)))
|
||||
Self(RwLock::new(TypeIdMap::with_hasher(NoOpTypeIdHash)))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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>>;
|
||||
|
||||
/// A stable 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.
|
||||
/// Unlike [`HashMap`] the iteration order stability extends between executions
|
||||
/// using the same Bevy version on the same device.
|
||||
///
|
||||
/// aHash is designed for performance and is NOT cryptographically secure.
|
||||
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.
|
||||
///
|
||||
/// 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>>;
|
||||
|
||||
/// A stable 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.
|
||||
/// Unlike [`HashMap`] the iteration order stability extends between executions
|
||||
/// using the same Bevy version on the same device.
|
||||
///
|
||||
/// aHash is designed for performance and is NOT cryptographically secure.
|
||||
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.
|
||||
/// Iteration order only depends on the order of insertions and deletions.
|
||||
pub type PreHashMap<K, V> = hashbrown::HashMap<Hashed<K>, V, PassHash>;
|
||||
|
||||
/// Extension methods intended to add functionality to [`PreHashMap`].
|
||||
@ -322,17 +331,30 @@ impl Hasher for EntityHasher {
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
|
||||
/// 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>;
|
||||
|
||||
/// A specialized hashmap type with Key of [`TypeId`]
|
||||
pub type TypeIdMap<V> =
|
||||
hashbrown::HashMap<TypeId, V, std::hash::BuildHasherDefault<NoOpTypeIdHasher>>;
|
||||
/// Iteration order only depends on the order of insertions and deletions.
|
||||
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)]
|
||||
#[derive(Default)]
|
||||
pub struct NoOpTypeIdHasher(u64);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
#[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