Skip to main content

goud_engine/core/handle/
map.rs

1//! HandleMap: a slot-map that associates handles with values.
2
3use super::allocator::HandleAllocator;
4use super::handle_type::Handle;
5
6/// A map that associates handles with values using generational indices.
7///
8/// `HandleMap` is a slot-map data structure that combines a `HandleAllocator`
9/// with value storage. It provides O(1) insertion, lookup, and removal with
10/// generational safety (stale handles return None, never wrong values).
11///
12/// # Type Parameters
13///
14/// - `T`: Marker type for the handle (provides type safety)
15/// - `V`: The value type stored in the map
16///
17/// # Example
18///
19/// ```
20/// use goud_engine::core::handle::HandleMap;
21///
22/// struct Texture;
23///
24/// let mut textures: HandleMap<Texture, String> = HandleMap::new();
25/// let handle = textures.insert("player.png".to_string());
26///
27/// assert_eq!(textures.get(handle), Some(&"player.png".to_string()));
28/// textures.remove(handle);
29/// assert!(textures.get(handle).is_none());
30/// ```
31///
32/// # Thread Safety
33///
34/// `HandleMap` is NOT thread-safe. For concurrent access, wrap in
35/// appropriate synchronization primitives.
36pub struct HandleMap<T, V> {
37    /// The handle allocator managing index and generation tracking.
38    pub(crate) allocator: HandleAllocator<T>,
39
40    /// Storage for values, indexed by handle index.
41    ///
42    /// Entries are `Some(value)` for live handles, `None` for deallocated slots.
43    /// The index in this vector corresponds to `handle.index()`.
44    pub(crate) values: Vec<Option<V>>,
45}
46
47impl<T, V> HandleMap<T, V> {
48    /// Creates a new, empty handle map.
49    ///
50    /// # Example
51    ///
52    /// ```
53    /// use goud_engine::core::handle::HandleMap;
54    ///
55    /// struct Mesh;
56    /// struct MeshData { vertex_count: usize }
57    ///
58    /// let map: HandleMap<Mesh, MeshData> = HandleMap::new();
59    /// assert!(map.is_empty());
60    /// assert_eq!(map.len(), 0);
61    /// ```
62    #[inline]
63    pub fn new() -> Self {
64        Self {
65            allocator: HandleAllocator::new(),
66            values: Vec::new(),
67        }
68    }
69
70    /// Creates a new handle map with pre-allocated capacity.
71    ///
72    /// This is useful when you know approximately how many entries you'll need,
73    /// as it avoids repeated reallocations during bulk insertion.
74    ///
75    /// # Arguments
76    ///
77    /// * `capacity` - The number of entries to pre-allocate space for
78    ///
79    /// # Example
80    ///
81    /// ```
82    /// use goud_engine::core::handle::HandleMap;
83    ///
84    /// struct Entity;
85    /// struct EntityData { name: String }
86    ///
87    /// let map: HandleMap<Entity, EntityData> = HandleMap::with_capacity(1000);
88    /// assert!(map.is_empty());
89    /// ```
90    #[inline]
91    pub fn with_capacity(capacity: usize) -> Self {
92        Self {
93            allocator: HandleAllocator::with_capacity(capacity),
94            values: Vec::with_capacity(capacity),
95        }
96    }
97
98    /// Inserts a value into the map and returns a handle to it.
99    ///
100    /// The returned handle can be used to retrieve, modify, or remove the value.
101    /// The handle remains valid until the value is removed.
102    ///
103    /// # Arguments
104    ///
105    /// * `value` - The value to insert
106    ///
107    /// # Returns
108    ///
109    /// A handle that can be used to access the inserted value.
110    ///
111    /// # Example
112    ///
113    /// ```
114    /// use goud_engine::core::handle::HandleMap;
115    ///
116    /// struct Shader;
117    /// struct ShaderData { name: String }
118    ///
119    /// let mut map: HandleMap<Shader, ShaderData> = HandleMap::new();
120    ///
121    /// let h1 = map.insert(ShaderData { name: "basic".to_string() });
122    /// let h2 = map.insert(ShaderData { name: "pbr".to_string() });
123    ///
124    /// assert_ne!(h1, h2);
125    /// assert!(h1.is_valid());
126    /// assert!(h2.is_valid());
127    /// assert_eq!(map.len(), 2);
128    /// ```
129    pub fn insert(&mut self, value: V) -> Handle<T> {
130        let handle = self.allocator.allocate();
131        let index = handle.index() as usize;
132
133        // Ensure values vec is large enough
134        if index >= self.values.len() {
135            self.values.resize_with(index + 1, || None);
136        }
137
138        self.values[index] = Some(value);
139        handle
140    }
141
142    /// Removes a value from the map by its handle.
143    ///
144    /// If the handle is valid and points to an existing value, the value is
145    /// removed and returned. The handle becomes stale after this operation.
146    ///
147    /// # Arguments
148    ///
149    /// * `handle` - The handle of the value to remove
150    ///
151    /// # Returns
152    ///
153    /// - `Some(value)` if the handle was valid and the value was removed
154    /// - `None` if the handle was invalid, stale, or already removed
155    ///
156    /// # Example
157    ///
158    /// ```
159    /// use goud_engine::core::handle::HandleMap;
160    ///
161    /// struct Audio;
162    /// struct AudioClip { duration: f32 }
163    ///
164    /// let mut map: HandleMap<Audio, AudioClip> = HandleMap::new();
165    ///
166    /// let handle = map.insert(AudioClip { duration: 5.5 });
167    /// assert_eq!(map.len(), 1);
168    ///
169    /// let removed = map.remove(handle);
170    /// assert!(removed.is_some());
171    /// assert_eq!(removed.unwrap().duration, 5.5);
172    /// assert_eq!(map.len(), 0);
173    ///
174    /// // Handle is now stale
175    /// assert!(map.remove(handle).is_none());
176    /// ```
177    pub fn remove(&mut self, handle: Handle<T>) -> Option<V> {
178        // Check if handle is alive
179        if !self.allocator.is_alive(handle) {
180            return None;
181        }
182
183        let index = handle.index() as usize;
184
185        // Deallocate the handle
186        if !self.allocator.deallocate(handle) {
187            return None;
188        }
189
190        // Take the value from storage
191        self.values.get_mut(index).and_then(|slot| slot.take())
192    }
193
194    /// Returns a reference to the value associated with a handle.
195    ///
196    /// # Arguments
197    ///
198    /// * `handle` - The handle to look up
199    ///
200    /// # Returns
201    ///
202    /// - `Some(&V)` if the handle is valid and points to a value
203    /// - `None` if the handle is invalid, stale, or the value was removed
204    ///
205    /// # Example
206    ///
207    /// ```
208    /// use goud_engine::core::handle::HandleMap;
209    ///
210    /// struct Sprite;
211    /// struct SpriteData { x: f32, y: f32 }
212    ///
213    /// let mut map: HandleMap<Sprite, SpriteData> = HandleMap::new();
214    ///
215    /// let handle = map.insert(SpriteData { x: 100.0, y: 200.0 });
216    ///
217    /// if let Some(sprite) = map.get(handle) {
218    ///     assert_eq!(sprite.x, 100.0);
219    ///     assert_eq!(sprite.y, 200.0);
220    /// }
221    /// ```
222    #[inline]
223    pub fn get(&self, handle: Handle<T>) -> Option<&V> {
224        if !self.allocator.is_alive(handle) {
225            return None;
226        }
227
228        let index = handle.index() as usize;
229        self.values.get(index).and_then(|slot| slot.as_ref())
230    }
231
232    /// Returns a mutable reference to the value associated with a handle.
233    ///
234    /// # Arguments
235    ///
236    /// * `handle` - The handle to look up
237    ///
238    /// # Returns
239    ///
240    /// - `Some(&mut V)` if the handle is valid and points to a value
241    /// - `None` if the handle is invalid, stale, or the value was removed
242    ///
243    /// # Example
244    ///
245    /// ```
246    /// use goud_engine::core::handle::HandleMap;
247    ///
248    /// struct Transform;
249    /// struct TransformData { x: f32, y: f32 }
250    ///
251    /// let mut map: HandleMap<Transform, TransformData> = HandleMap::new();
252    ///
253    /// let handle = map.insert(TransformData { x: 0.0, y: 0.0 });
254    ///
255    /// if let Some(transform) = map.get_mut(handle) {
256    ///     transform.x = 50.0;
257    ///     transform.y = 100.0;
258    /// }
259    ///
260    /// assert_eq!(map.get(handle).unwrap().x, 50.0);
261    /// ```
262    #[inline]
263    pub fn get_mut(&mut self, handle: Handle<T>) -> Option<&mut V> {
264        if !self.allocator.is_alive(handle) {
265            return None;
266        }
267
268        let index = handle.index() as usize;
269        self.values.get_mut(index).and_then(|slot| slot.as_mut())
270    }
271
272    /// Checks if a handle is valid and points to a value in this map.
273    ///
274    /// This is equivalent to `self.get(handle).is_some()` but more efficient.
275    ///
276    /// # Arguments
277    ///
278    /// * `handle` - The handle to check
279    ///
280    /// # Returns
281    ///
282    /// `true` if the handle is valid and the value exists, `false` otherwise.
283    ///
284    /// # Example
285    ///
286    /// ```
287    /// use goud_engine::core::handle::HandleMap;
288    ///
289    /// struct Entity;
290    /// struct EntityData { id: u32 }
291    ///
292    /// let mut map: HandleMap<Entity, EntityData> = HandleMap::new();
293    ///
294    /// let handle = map.insert(EntityData { id: 1 });
295    /// assert!(map.contains(handle));
296    ///
297    /// map.remove(handle);
298    /// assert!(!map.contains(handle));
299    /// ```
300    #[inline]
301    pub fn contains(&self, handle: Handle<T>) -> bool {
302        self.allocator.is_alive(handle)
303    }
304
305    /// Returns the number of values currently stored in the map.
306    ///
307    /// This counts only live entries, not removed slots.
308    ///
309    /// # Example
310    ///
311    /// ```
312    /// use goud_engine::core::handle::HandleMap;
313    ///
314    /// struct Resource;
315    ///
316    /// let mut map: HandleMap<Resource, i32> = HandleMap::new();
317    /// assert_eq!(map.len(), 0);
318    ///
319    /// let h1 = map.insert(10);
320    /// let h2 = map.insert(20);
321    /// assert_eq!(map.len(), 2);
322    ///
323    /// map.remove(h1);
324    /// assert_eq!(map.len(), 1);
325    /// ```
326    #[inline]
327    pub fn len(&self) -> usize {
328        self.allocator.len()
329    }
330
331    /// Returns `true` if the map contains no values.
332    ///
333    /// # Example
334    ///
335    /// ```
336    /// use goud_engine::core::handle::HandleMap;
337    ///
338    /// struct Item;
339    ///
340    /// let mut map: HandleMap<Item, String> = HandleMap::new();
341    /// assert!(map.is_empty());
342    ///
343    /// let handle = map.insert("item".to_string());
344    /// assert!(!map.is_empty());
345    ///
346    /// map.remove(handle);
347    /// assert!(map.is_empty());
348    /// ```
349    #[inline]
350    pub fn is_empty(&self) -> bool {
351        self.allocator.is_empty()
352    }
353
354    /// Returns the total capacity of the map.
355    ///
356    /// This is the number of slots allocated, including both live and removed entries.
357    ///
358    /// # Example
359    ///
360    /// ```
361    /// use goud_engine::core::handle::HandleMap;
362    ///
363    /// struct Data;
364    ///
365    /// let mut map: HandleMap<Data, i32> = HandleMap::new();
366    /// let h1 = map.insert(1);
367    /// let h2 = map.insert(2);
368    ///
369    /// assert_eq!(map.capacity(), 2);
370    ///
371    /// map.remove(h1);
372    /// // Capacity unchanged after removal
373    /// assert_eq!(map.capacity(), 2);
374    /// ```
375    #[inline]
376    pub fn capacity(&self) -> usize {
377        self.allocator.capacity()
378    }
379
380    /// Clears all values from the map, invalidating all handles.
381    ///
382    /// After this operation, all previously returned handles become stale.
383    /// The capacity is retained but `len()` becomes 0.
384    ///
385    /// # Example
386    ///
387    /// ```
388    /// use goud_engine::core::handle::HandleMap;
389    ///
390    /// struct Object;
391    ///
392    /// let mut map: HandleMap<Object, i32> = HandleMap::new();
393    ///
394    /// let h1 = map.insert(100);
395    /// let h2 = map.insert(200);
396    /// let h3 = map.insert(300);
397    ///
398    /// map.clear();
399    ///
400    /// assert!(map.is_empty());
401    /// assert!(!map.contains(h1));
402    /// assert!(!map.contains(h2));
403    /// assert!(!map.contains(h3));
404    ///
405    /// // Capacity retained
406    /// assert_eq!(map.capacity(), 3);
407    /// ```
408    pub fn clear(&mut self) {
409        self.allocator.clear();
410
411        // Clear all values but retain the vec capacity
412        for slot in &mut self.values {
413            *slot = None;
414        }
415    }
416
417    /// Reserves capacity for at least `additional` more elements.
418    ///
419    /// # Arguments
420    ///
421    /// * `additional` - The number of additional entries to reserve space for
422    ///
423    /// # Example
424    ///
425    /// ```
426    /// use goud_engine::core::handle::HandleMap;
427    ///
428    /// struct Component;
429    ///
430    /// let mut map: HandleMap<Component, i32> = HandleMap::new();
431    /// map.reserve(100);
432    /// ```
433    #[inline]
434    pub fn reserve(&mut self, additional: usize) {
435        self.values.reserve(additional);
436    }
437
438    /// Shrinks the capacity of the map as much as possible.
439    ///
440    /// This reduces memory usage by releasing excess capacity in internal
441    /// data structures.
442    ///
443    /// # Example
444    ///
445    /// ```
446    /// use goud_engine::core::handle::HandleMap;
447    ///
448    /// struct Item;
449    ///
450    /// let mut map: HandleMap<Item, i32> = HandleMap::with_capacity(100);
451    ///
452    /// for i in 0..10 {
453    ///     map.insert(i);
454    /// }
455    ///
456    /// map.shrink_to_fit();
457    /// ```
458    #[inline]
459    pub fn shrink_to_fit(&mut self) {
460        self.allocator.shrink_to_fit();
461        self.values.shrink_to_fit();
462    }
463}
464
465impl<T, V> Default for HandleMap<T, V> {
466    /// Creates an empty `HandleMap` (same as `new()`).
467    #[inline]
468    fn default() -> Self {
469        Self::new()
470    }
471}
472
473impl<T, V: std::fmt::Debug> std::fmt::Debug for HandleMap<T, V> {
474    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475        let type_name = std::any::type_name::<T>();
476        let short_name = type_name.rsplit("::").next().unwrap_or(type_name);
477
478        f.debug_struct(&format!("HandleMap<{}, ...>", short_name))
479            .field("len", &self.len())
480            .field("capacity", &self.capacity())
481            .finish()
482    }
483}