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

Hash stability guarantees #11690

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 3 additions & 5 deletions crates/bevy_reflect/src/utility.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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>;
Expand All @@ -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.
Expand Down
50 changes: 43 additions & 7 deletions crates/bevy_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand All @@ -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>;
Expand Down Expand Up @@ -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`].
Expand Down Expand Up @@ -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>;

#[doc(hidden)]
/// [`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)]
pub struct NoOpTypeIdHasher(u64);

// TypeId already contains a high-quality hash, so skip re-hashing that hash.
Expand Down Expand Up @@ -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<_>>()
);
}
}