Skip to content

Commit

Permalink
make EntityHashMap and EntityHashSet proper types (#16912)
Browse files Browse the repository at this point in the history
# Objective

`EntityHashMap` and `EntityHashSet` iterators do not implement
`EntitySetIterator`.

## Solution

Make them newtypes instead of aliases. The methods that create the
iterators can then produce their own newtypes that carry the `Hasher`
generic and implement `EntitySetIterator`. Functionality remains the
same otherwise.
There are some other small benefits, f.e. the removal of `with_hasher`
associated functions, and the ability to implement more traits
ourselves.

`MainEntityHashMap` and `MainEntityHashSet` are currently left as the
previous type aliases, because supporting general `TrustedEntityBorrow`
hashing is more complex. However, it can also be done.

## Testing

Pre-existing `EntityHashMap` tests.

## Migration Guide

Users of `with_hasher` and `with_capacity_and_hasher` on
`EntityHashMap`/`Set` must now use `new` and `with_capacity`
respectively.
If the non-newtyped versions are required, they can be obtained via
`Deref`, `DerefMut` or `into_inner` calls.
  • Loading branch information
Victoronz authored Dec 20, 2024
1 parent 65835f5 commit 8ac90ac
Show file tree
Hide file tree
Showing 6 changed files with 707 additions and 28 deletions.
25 changes: 2 additions & 23 deletions crates/bevy_ecs/src/entity/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ use core::hash::{BuildHasher, Hasher};

#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use bevy_utils::hashbrown;

use super::Entity;

/// A [`BuildHasher`] that results in a [`EntityHasher`].
#[derive(Default, Clone)]
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct EntityHash;

Expand All @@ -20,7 +17,7 @@ impl BuildHasher for EntityHash {
}

/// A very fast hash that is only designed to work on generational indices
/// like [`Entity`]. It will panic if attempting to hash a type containing
/// like [`Entity`](super::Entity). It will panic if attempting to hash a type containing
/// non-u64 fields.
///
/// This is heavily optimized for typical cases, where you have mostly live
Expand Down Expand Up @@ -78,21 +75,3 @@ impl Hasher for EntityHasher {
self.hash = bits.wrapping_mul(UPPER_PHI);
}
}

/// A [`HashMap`](hashbrown::HashMap) pre-configured to use [`EntityHash`] hashing.
pub type EntityHashMap<V> = hashbrown::HashMap<Entity, V, EntityHash>;

/// A [`HashSet`](hashbrown::HashSet) pre-configured to use [`EntityHash`] hashing.
pub type EntityHashSet = hashbrown::HashSet<Entity, EntityHash>;

#[cfg(test)]
mod tests {
use super::*;
use static_assertions::assert_impl_all;

// Check that the HashMaps are Clone if the key/values are Clone
assert_impl_all!(EntityHashMap::<usize>: Clone);
// EntityHashMap should implement Reflect
#[cfg(feature = "bevy_reflect")]
assert_impl_all!(EntityHashMap::<i32>: Reflect);
}
279 changes: 279 additions & 0 deletions crates/bevy_ecs/src/entity/hash_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
use core::{
fmt::{self, Debug, Formatter},
iter::FusedIterator,
marker::PhantomData,
ops::{Deref, DerefMut, Index},
};

#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use bevy_utils::hashbrown::hash_map::{self, HashMap};

use super::{Entity, EntityHash, EntitySetIterator, TrustedEntityBorrow};

/// A [`HashMap`] pre-configured to use [`EntityHash`] hashing.
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EntityHashMap<V>(pub(crate) HashMap<Entity, V, EntityHash>);

impl<V> EntityHashMap<V> {
/// Creates an empty `EntityHashMap`.
///
/// Equivalent to [`HashMap::with_hasher(EntityHash)`].
///
/// [`HashMap::with_hasher(EntityHash)`]: HashMap::with_hasher
pub fn new() -> Self {
Self(HashMap::with_hasher(EntityHash))
}

/// Creates an empty `EntityHashMap` with the specified capacity.
///
/// Equivalent to [`HashMap::with_capacity_and_hasher(n, EntityHash)`].
///
/// [`HashMap:with_capacity_and_hasher(n, EntityHash)`]: HashMap::with_capacity_and_hasher
pub fn with_capacity(n: usize) -> Self {
Self(HashMap::with_capacity_and_hasher(n, EntityHash))
}

/// Returns the inner [`HashMap`].
pub fn into_inner(self) -> HashMap<Entity, V, EntityHash> {
self.0
}

/// An iterator visiting all keys in arbitrary order.
/// The iterator element type is `&'a Entity`.
///
/// Equivalent to [`HashMap::keys`].
pub fn keys(&self) -> Keys<'_, V> {
Keys(self.0.keys(), PhantomData)
}

/// Creates a consuming iterator visiting all the keys in arbitrary order.
/// The map cannot be used after calling this.
/// The iterator element type is [`Entity`].
///
/// Equivalent to [`HashMap::into_keys`].
pub fn into_keys(self) -> IntoKeys<V> {
IntoKeys(self.0.into_keys(), PhantomData)
}
}

impl<V> Default for EntityHashMap<V> {
fn default() -> Self {
Self(Default::default())
}
}

impl<V> Deref for EntityHashMap<V> {
type Target = HashMap<Entity, V, EntityHash>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<V> DerefMut for EntityHashMap<V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl<'a, V: Copy> Extend<&'a (Entity, V)> for EntityHashMap<V> {
fn extend<T: IntoIterator<Item = &'a (Entity, V)>>(&mut self, iter: T) {
self.0.extend(iter);
}
}

impl<'a, V: Copy> Extend<(&'a Entity, &'a V)> for EntityHashMap<V> {
fn extend<T: IntoIterator<Item = (&'a Entity, &'a V)>>(&mut self, iter: T) {
self.0.extend(iter);
}
}

impl<V> Extend<(Entity, V)> for EntityHashMap<V> {
fn extend<T: IntoIterator<Item = (Entity, V)>>(&mut self, iter: T) {
self.0.extend(iter);
}
}

impl<V, const N: usize> From<[(Entity, V); N]> for EntityHashMap<V> {
fn from(value: [(Entity, V); N]) -> Self {
Self(HashMap::from_iter(value))
}
}

impl<V> FromIterator<(Entity, V)> for EntityHashMap<V> {
fn from_iter<I: IntoIterator<Item = (Entity, V)>>(iterable: I) -> Self {
Self(HashMap::from_iter(iterable))
}
}

impl<V, Q: TrustedEntityBorrow + ?Sized> Index<&Q> for EntityHashMap<V> {
type Output = V;
fn index(&self, key: &Q) -> &V {
self.0.index(&key.entity())
}
}

impl<'a, V> IntoIterator for &'a EntityHashMap<V> {
type Item = (&'a Entity, &'a V);
type IntoIter = hash_map::Iter<'a, Entity, V>;

fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}

impl<'a, V> IntoIterator for &'a mut EntityHashMap<V> {
type Item = (&'a Entity, &'a mut V);
type IntoIter = hash_map::IterMut<'a, Entity, V>;

fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}

impl<V> IntoIterator for EntityHashMap<V> {
type Item = (Entity, V);
type IntoIter = hash_map::IntoIter<Entity, V>;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

/// An iterator over the keys of a [`EntityHashMap`] in arbitrary order.
/// The iterator element type is `&'a Entity`.
///
/// /// This struct is created by the [`keys`] method on [`EntityHashMap`]. See its documentation for more.
///
/// [`keys`]: EntityHashMap::keys
pub struct Keys<'a, V, S = EntityHash>(hash_map::Keys<'a, Entity, V>, PhantomData<S>);

impl<'a, V> Keys<'a, V> {
/// Returns the inner [`Keys`](hash_map::Keys).
pub fn into_inner(self) -> hash_map::Keys<'a, Entity, V> {
self.0
}
}

impl<'a, V> Deref for Keys<'a, V> {
type Target = hash_map::Keys<'a, Entity, V>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<V> DerefMut for Keys<'_, V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl<'a, V> Iterator for Keys<'a, V> {
type Item = &'a Entity;

fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}

impl<V> ExactSizeIterator for Keys<'_, V> {}

impl<V> FusedIterator for Keys<'_, V> {}

impl<V> Clone for Keys<'_, V> {
fn clone(&self) -> Self {
Self(self.0.clone(), PhantomData)
}
}

impl<V: Debug> Debug for Keys<'_, V> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("Keys").field(&self.0).field(&self.1).finish()
}
}

impl<V> Default for Keys<'_, V> {
fn default() -> Self {
Self(Default::default(), PhantomData)
}
}

// SAFETY: Keys stems from a correctly behaving `HashMap<Entity, V, EntityHash>`.
unsafe impl<V> EntitySetIterator for Keys<'_, V> {}

/// An owning iterator over the keys of a [`EntityHashMap`] in arbitrary order.
/// The iterator element type is [`Entity`].
///
/// This struct is created by the [`into_keys`] method on [`EntityHashMap`].
/// See its documentation for more.
/// The map cannot be used after calling that method.
///
/// [`into_keys`]: EntityHashMap::into_keys
pub struct IntoKeys<V, S = EntityHash>(hash_map::IntoKeys<Entity, V>, PhantomData<S>);

impl<V> IntoKeys<V> {
/// Returns the inner [`IntoKeys`](hash_map::IntoKeys).
pub fn into_inner(self) -> hash_map::IntoKeys<Entity, V> {
self.0
}
}

impl<V> Deref for IntoKeys<V> {
type Target = hash_map::IntoKeys<Entity, V>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<V> DerefMut for IntoKeys<V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl<V> Iterator for IntoKeys<V> {
type Item = Entity;

fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}

impl<V> ExactSizeIterator for IntoKeys<V> {}

impl<V> FusedIterator for IntoKeys<V> {}

impl<V: Debug> Debug for IntoKeys<V> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("IntoKeys")
.field(&self.0)
.field(&self.1)
.finish()
}
}

impl<V> Default for IntoKeys<V> {
fn default() -> Self {
Self(Default::default(), PhantomData)
}
}

// SAFETY: IntoKeys stems from a correctly behaving `HashMap<Entity, V, EntityHash>`.
unsafe impl<V> EntitySetIterator for IntoKeys<V> {}

#[cfg(test)]
mod tests {
use super::*;
use bevy_reflect::Reflect;
use static_assertions::assert_impl_all;

// Check that the HashMaps are Clone if the key/values are Clone
assert_impl_all!(EntityHashMap::<usize>: Clone);
// EntityHashMap should implement Reflect
#[cfg(feature = "bevy_reflect")]
assert_impl_all!(EntityHashMap::<i32>: Reflect);
}
Loading

0 comments on commit 8ac90ac

Please sign in to comment.