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}