Skip to main content

bevy_utils/
map.rs

1use core::{any::TypeId, hash::Hash};
2
3use bevy_platform::{
4    collections::HashMap,
5    hash::{Hashed, NoOpHash, PassHash},
6};
7use indexmap::map::IndexMap;
8
9/// The [`hash_map::Entry`][bevy_platform::collections::hash_map::Entry] type for [`TypeIdHashMap`].
10pub use bevy_platform::collections::hash_map::Entry as TypeIdHashMapEntry;
11
12/// The [`Entry`][indexmap::map::Entry] type for [`TypeIdIndexMap`].
13pub use indexmap::map::Entry as TypeIdIndexMapEntry;
14
15/// Deprecated compatibility alias for [`TypeIdIndexMapEntry`].
16#[deprecated(
17    since = "0.20.0",
18    note = "use `TypeIdHashMapEntry` or `TypeIdIndexMapEntry` instead"
19)]
20pub use indexmap::map::Entry as TypeIdMapEntry;
21
22/// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing.
23/// Iteration order only depends on the order of insertions and deletions.
24pub type PreHashMap<K, V> = HashMap<Hashed<K>, V, PassHash>;
25
26/// Extension methods intended to add functionality to [`PreHashMap`].
27pub trait PreHashMapExt<K, V> {
28    /// Tries to get or insert the value for the given `key` using the pre-computed hash first.
29    /// If the [`PreHashMap`] does not already contain the `key`, it will clone it and insert
30    /// the value returned by `func`.
31    fn get_or_insert_with<F: FnOnce() -> V>(&mut self, key: &Hashed<K>, func: F) -> &mut V;
32}
33
34impl<K: Hash + Eq + PartialEq + Clone, V> PreHashMapExt<K, V> for PreHashMap<K, V> {
35    #[inline]
36    fn get_or_insert_with<F: FnOnce() -> V>(&mut self, key: &Hashed<K>, func: F) -> &mut V {
37        use bevy_platform::collections::hash_map::RawEntryMut;
38        let entry = self
39            .raw_entry_mut()
40            .from_key_hashed_nocheck(key.hash(), key);
41        match entry {
42            RawEntryMut::Occupied(entry) => entry.into_mut(),
43            RawEntryMut::Vacant(entry) => {
44                let (_, value) = entry.insert_hashed_nocheck(key.hash(), key.clone(), func());
45                value
46            }
47        }
48    }
49}
50
51/// A specialized hash map type with a key of [`TypeId`].
52pub type TypeIdHashMap<V> = HashMap<TypeId, V, NoOpHash>;
53
54/// A specialized index map type with a key of [`TypeId`].
55/// Iteration order only depends on the order of insertions and deletions.
56pub type TypeIdIndexMap<V> = IndexMap<TypeId, V, NoOpHash>;
57
58/// Deprecated compatibility alias for [`TypeIdIndexMap`].
59#[deprecated(
60    since = "0.20.0",
61    note = "use `TypeIdHashMap` or `TypeIdIndexMap` instead"
62)]
63pub type TypeIdMap<V> = TypeIdIndexMap<V>;
64
65/// Extension trait to make use of [`TypeIdIndexMap`] more ergonomic.
66///
67/// Each function on this trait is a trivial wrapper for a map function,
68/// replacing a `TypeId` key with a generic parameter `T`.
69///
70/// # Examples
71///
72/// ```rust
73/// # use std::any::TypeId;
74/// # use bevy_utils::TypeIdIndexMap;
75/// use bevy_utils::TypeIdMapExt;
76///
77/// struct MyType;
78///
79/// // Using the built-in map functions requires manually looking up `TypeId`s.
80/// let mut map = TypeIdIndexMap::default();
81/// map.insert(TypeId::of::<MyType>(), 7);
82/// assert_eq!(map.get(&TypeId::of::<MyType>()), Some(&7));
83///
84/// // Using `TypeIdMapExt` functions does the lookup for you.
85/// map.insert_type::<MyType>(7);
86/// assert_eq!(map.get_type::<MyType>(), Some(&7));
87/// ```
88pub trait TypeIdMapExt<V> {
89    /// Inserts a value for the type `T`.
90    ///
91    /// If the map did not previously contain this key then [`None`] is returned,
92    /// otherwise the value for this key is updated and the old value returned.
93    fn insert_type<T: ?Sized + 'static>(&mut self, v: V) -> Option<V>;
94
95    /// Returns a reference to the value for type `T`, if one exists.
96    fn get_type<T: ?Sized + 'static>(&self) -> Option<&V>;
97
98    /// Returns a mutable reference to the value for type `T`, if one exists.
99    fn get_type_mut<T: ?Sized + 'static>(&mut self) -> Option<&mut V>;
100
101    /// Removes type `T` from the map, returning the value for this
102    /// key if it was previously present.
103    fn remove_type<T: ?Sized + 'static>(&mut self) -> Option<V>;
104
105    /// Gets the type `T`'s entry in the map for in-place manipulation.
106    fn entry_type<T: ?Sized + 'static>(&mut self) -> TypeIdIndexMapEntry<'_, TypeId, V>;
107}
108
109impl<V> TypeIdMapExt<V> for TypeIdIndexMap<V> {
110    #[inline]
111    fn insert_type<T: ?Sized + 'static>(&mut self, v: V) -> Option<V> {
112        self.insert(TypeId::of::<T>(), v)
113    }
114
115    #[inline]
116    fn get_type<T: ?Sized + 'static>(&self) -> Option<&V> {
117        self.get(&TypeId::of::<T>())
118    }
119
120    #[inline]
121    fn get_type_mut<T: ?Sized + 'static>(&mut self) -> Option<&mut V> {
122        self.get_mut(&TypeId::of::<T>())
123    }
124
125    #[inline]
126    fn remove_type<T: ?Sized + 'static>(&mut self) -> Option<V> {
127        self.shift_remove(&TypeId::of::<T>())
128    }
129
130    #[inline]
131    fn entry_type<T: ?Sized + 'static>(&mut self) -> TypeIdIndexMapEntry<'_, TypeId, V> {
132        self.entry(TypeId::of::<T>())
133    }
134}
135
136/// Extension trait to make use of [`TypeIdHashMap`] more ergonomic.
137pub trait TypeIdHashMapExt<V> {
138    /// Inserts a value for the type `T`.
139    ///
140    /// If the map did not previously contain this key then [`None`] is returned,
141    /// otherwise the value for this key is updated and the old value returned.
142    fn insert_type<T: ?Sized + 'static>(&mut self, v: V) -> Option<V>;
143
144    /// Returns a reference to the value for type `T`, if one exists.
145    fn get_type<T: ?Sized + 'static>(&self) -> Option<&V>;
146
147    /// Returns a mutable reference to the value for type `T`, if one exists.
148    fn get_type_mut<T: ?Sized + 'static>(&mut self) -> Option<&mut V>;
149
150    /// Removes type `T` from the map, returning the value for this
151    /// key if it was previously present.
152    fn remove_type<T: ?Sized + 'static>(&mut self) -> Option<V>;
153
154    /// Gets the type `T`'s entry in the map for in-place manipulation.
155    fn entry_type<T: ?Sized + 'static>(
156        &mut self,
157    ) -> TypeIdHashMapEntry<'_, TypeId, V, NoOpHash>;
158}
159
160impl<V> TypeIdHashMapExt<V> for TypeIdHashMap<V> {
161    #[inline]
162    fn insert_type<T: ?Sized + 'static>(&mut self, v: V) -> Option<V> {
163        self.insert(TypeId::of::<T>(), v)
164    }
165
166    #[inline]
167    fn get_type<T: ?Sized + 'static>(&self) -> Option<&V> {
168        self.get(&TypeId::of::<T>())
169    }
170
171    #[inline]
172    fn get_type_mut<T: ?Sized + 'static>(&mut self) -> Option<&mut V> {
173        self.get_mut(&TypeId::of::<T>())
174    }
175
176    #[inline]
177    fn remove_type<T: ?Sized + 'static>(&mut self) -> Option<V> {
178        self.remove(&TypeId::of::<T>())
179    }
180
181    #[inline]
182    fn entry_type<T: ?Sized + 'static>(
183        &mut self,
184    ) -> TypeIdHashMapEntry<'_, TypeId, V, NoOpHash> {
185        self.entry(TypeId::of::<T>())
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192    use static_assertions::assert_impl_all;
193
194    // Check that the HashMaps are Clone if the key/values are Clone
195    assert_impl_all!(PreHashMap::<u64, usize>: Clone);
196
197    #[test]
198    fn fast_typeid_hash() {
199        struct Hasher;
200
201        impl core::hash::Hasher for Hasher {
202            fn finish(&self) -> u64 {
203                0
204            }
205            fn write(&mut self, _: &[u8]) {
206                panic!("Hashing of core::any::TypeId changed");
207            }
208            fn write_u64(&mut self, _: u64) {}
209        }
210
211        Hash::hash(&TypeId::of::<()>(), &mut Hasher);
212    }
213
214    crate::cfg::alloc! {
215        #[test]
216        fn stable_hash_within_same_program_execution() {
217            use alloc::vec::Vec;
218
219            let mut map_1 = <HashMap<_, _>>::default();
220            let mut map_2 = <HashMap<_, _>>::default();
221            for i in 1..10 {
222                map_1.insert(i, i);
223                map_2.insert(i, i);
224            }
225            assert_eq!(
226                map_1.iter().collect::<Vec<_>>(),
227                map_2.iter().collect::<Vec<_>>()
228            );
229        }
230    }
231}