Newtype hashbrown (#18694)

# Objective

- Fixes #18690
- Closes [#2065](https://github.com/bevyengine/bevy-website/pull/2065)
- Alternative to #18691

The changes to the Hash made in #15801 to the
[BuildHasher](https://doc.rust-lang.org/std/hash/trait.BuildHasher.html)
resulted in serious migration problems and downgraded UX for users of
Bevy's re-exported hashmaps. Once merged, we need to go in and remove
the migration guide added as part of #15801.

## Solution

- Newtype `HashMap` and `HashSet` instead of type aliases
- Added `Deref/Mut` to allow accessing future `hashbrown` methods
without maintenance from Bevy
- Added bidirectional `From` implementations to provide escape hatch for
API incompatibility
- Added inlinable re-exports of all methods directly to Bevy's types.
This ensures `HashMap::new()` works (since the `Deref` implementation
wont cover these kinds of invocations).

## Testing

- CI

---

## Migration Guide

- If you relied on Bevy's `HashMap` and/or `HashSet` types to be
identical to `hashbrown`, consider using `From` and `Into` to convert
between the `hashbrown` and Bevy types as required.
- If you relied on `hashbrown/serde` or `hashbrown/rayon` features, you
may need to enable `bevy_platform_support/serialize` and/or
`bevy_platform_support/rayon` respectively.

---

## Notes

- Did not replicate the Rayon traits, users will need to rely on the
`Deref/Mut` or `From` implementations for those methods.
- Did not re-expose the `unsafe` methods from `hashbrown`. In most cases
users will still have access via `Deref/Mut` anyway.
- I have added `inline` to all methods as they are trivial wrappings of
existing methods.
- I chose to make `HashMap::new` and `HashSet::new` const, which is
different to `hashbrown`. We can do this because we default to a
fixed-state build-hasher. Mild ergonomic win over using
`HashMap::with_hasher(FixedHasher)`.
This commit is contained in:
Zachary Harrold 2025-04-07 03:52:49 +10:00 committed by François Mockers
parent 3d17865c2e
commit f3f4e80d87
8 changed files with 2393 additions and 77 deletions

View File

@ -131,9 +131,7 @@ fn build_over_map(
.filter(|e| !cancelled_pointers.contains(&e.pointer))
{
let pointer = entities_under_pointer.pointer;
let layer_map = pointer_over_map
.entry(pointer)
.or_insert_with(BTreeMap::new);
let layer_map = pointer_over_map.entry(pointer).or_default();
for (entity, pick_data) in entities_under_pointer.picks.iter() {
let layer = entities_under_pointer.order;
let hits = layer_map.entry(FloatOrd(layer)).or_default();

View File

@ -14,7 +14,10 @@ default = ["std"]
# Functionality
## Adds serialization support through `serde`.
serialize = ["hashbrown/serde"]
serialize = ["dep:serde", "hashbrown/serde"]
## Adds integration with Rayon.
rayon = ["dep:rayon", "hashbrown/rayon"]
# Platform Compatibility
@ -28,10 +31,11 @@ std = [
"portable-atomic-util/std",
"spin/std",
"foldhash/std",
"serde?/std",
]
## Allows access to the `alloc` crate.
alloc = ["portable-atomic-util/alloc", "dep:hashbrown"]
alloc = ["portable-atomic-util/alloc", "dep:hashbrown", "serde?/alloc"]
## `critical-section` provides the building blocks for synchronization primitives
## on all platforms, including `no_std`.
@ -57,6 +61,8 @@ hashbrown = { version = "0.15.1", features = [
"equivalent",
"raw-entry",
], optional = true, default-features = false }
serde = { version = "1", default-features = false, optional = true }
rayon = { version = "1", default-features = false, optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-time = { version = "1.1", default-features = false, optional = true }

View File

@ -1,68 +0,0 @@
//! Provides [`HashMap`] and [`HashSet`] from [`hashbrown`] with some customized defaults.
//!
//! Also provides the [`HashTable`] type, which is specific to [`hashbrown`].
//!
//! Note that due to the implementation details of [`hashbrown`], [`HashMap::new`] is only implemented for `HashMap<K, V, RandomState>`.
//! Whereas, Bevy exports `HashMap<K, V, FixedHasher>` as its default [`HashMap`] type, meaning [`HashMap::new`] will typically fail.
//! To bypass this issue, use [`HashMap::default`] instead.
pub use hash_map::HashMap;
pub use hash_set::HashSet;
pub use hash_table::HashTable;
pub use hashbrown::Equivalent;
pub mod hash_map {
//! Provides [`HashMap`]
use crate::hash::FixedHasher;
use hashbrown::hash_map as hb;
// Re-exports to match `std::collections::hash_map`
pub use {
crate::hash::{DefaultHasher, RandomState},
hb::{
Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, OccupiedEntry, VacantEntry,
Values, ValuesMut,
},
};
// Additional items from `hashbrown`
pub use hb::{
EntryRef, ExtractIf, OccupiedError, RawEntryBuilder, RawEntryBuilderMut, RawEntryMut,
RawOccupiedEntryMut,
};
/// Shortcut for [`HashMap`](hb::HashMap) with [`FixedHasher`] as the default hashing provider.
pub type HashMap<K, V, S = FixedHasher> = hb::HashMap<K, V, S>;
/// Shortcut for [`Entry`](hb::Entry) with [`FixedHasher`] as the default hashing provider.
pub type Entry<'a, K, V, S = FixedHasher> = hb::Entry<'a, K, V, S>;
}
pub mod hash_set {
//! Provides [`HashSet`]
use crate::hash::FixedHasher;
use hashbrown::hash_set as hb;
// Re-exports to match `std::collections::hash_set`
pub use hb::{Difference, Drain, Intersection, IntoIter, Iter, SymmetricDifference, Union};
// Additional items from `hashbrown`
pub use hb::{ExtractIf, OccupiedEntry, VacantEntry};
/// Shortcut for [`HashSet`](hb::HashSet) with [`FixedHasher`] as the default hashing provider.
pub type HashSet<T, S = FixedHasher> = hb::HashSet<T, S>;
/// Shortcut for [`Entry`](hb::Entry) with [`FixedHasher`] as the default hashing provider.
pub type Entry<'a, T, S = FixedHasher> = hb::Entry<'a, T, S>;
}
pub mod hash_table {
//! Provides [`HashTable`]
pub use hashbrown::hash_table::{
AbsentEntry, Drain, Entry, ExtractIf, HashTable, IntoIter, Iter, IterHash, IterHashMut,
IterMut, OccupiedEntry, VacantEntry,
};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
//! Provides [`HashTable`]
pub use hashbrown::hash_table::{
AbsentEntry, Drain, Entry, ExtractIf, HashTable, IntoIter, Iter, IterHash, IterHashMut,
IterMut, OccupiedEntry, VacantEntry,
};

View File

@ -0,0 +1,12 @@
//! Provides [`HashMap`] and [`HashSet`] from [`hashbrown`] with some customized defaults.
//!
//! Also provides the [`HashTable`] type, which is specific to [`hashbrown`].
pub use hash_map::HashMap;
pub use hash_set::HashSet;
pub use hash_table::HashTable;
pub use hashbrown::Equivalent;
pub mod hash_map;
pub mod hash_set;
pub mod hash_table;

View File

@ -331,10 +331,7 @@ impl SceneSpawner {
Ok(_) => {
self.spawned_instances
.insert(instance_id, InstanceInfo { entity_map });
let spawned = self
.spawned_dynamic_scenes
.entry(handle.id())
.or_insert_with(HashSet::default);
let spawned = self.spawned_dynamic_scenes.entry(handle.id()).or_default();
spawned.insert(instance_id);
// Scenes with parents need more setup before they are ready.