Skip to main content

goud_engine/ecs/sparse_set/
core.rs

1//! Core sparse set data structure for component storage.
2//!
3//! A sparse set provides O(1) insertion, deletion, and lookup while maintaining
4//! cache-friendly iteration over dense data. This is the foundation for ECS
5//! component storage.
6//!
7//! # Design Pattern: Sparse Set
8//!
9//! The sparse set uses two arrays:
10//!
11//! - **Sparse**: Maps entity indices to dense array indices (may have gaps)
12//! - **Dense**: Packed array of entities for cache-friendly iteration
13//! - **Values**: Packed array of component values, parallel to dense
14//!
15//! ```text
16//! Entity(3) has component "A"
17//! Entity(7) has component "B"
18//! Entity(1) has component "C"
19//!
20//! Sparse:  [_, Some(2), _, Some(0), _, _, _, Some(1)]
21//!           0    1      2    3      4  5  6    7
22//!
23//! Dense:   [Entity(3), Entity(7), Entity(1)]
24//!              0          1          2
25//!
26//! Values:  ["A", "B", "C"]
27//!            0     1    2
28//! ```
29//!
30//! # Performance Characteristics
31//!
32//! | Operation | Time Complexity | Notes |
33//! |-----------|-----------------|-------|
34//! | insert    | O(1) amortized  | May grow sparse vec |
35//! | remove    | O(1)            | Uses swap-remove |
36//! | get       | O(1)            | |
37//! | contains  | O(1)            | |
38//! | iterate   | O(n)            | Cache-friendly, contiguous |
39//!
40//! # Example
41//!
42//! ```
43//! use goud_engine::ecs::{Entity, SparseSet};
44//!
45//! let mut set: SparseSet<String> = SparseSet::new();
46//!
47//! let e1 = Entity::new(0, 1);
48//! let e2 = Entity::new(5, 1);
49//!
50//! set.insert(e1, "Hello".to_string());
51//! set.insert(e2, "World".to_string());
52//!
53//! assert_eq!(set.get(e1), Some(&"Hello".to_string()));
54//! assert!(set.contains(e2));
55//!
56//! // Cache-friendly iteration
57//! for (entity, value) in set.iter() {
58//!     println!("{}: {}", entity, value);
59//! }
60//! ```
61//!
62//! # Thread Safety
63//!
64//! `SparseSet<T>` is `Send` if `T: Send` and `Sync` if `T: Sync`.
65//! The sparse set itself is not internally synchronized - wrap in
66//! `RwLock` or similar for concurrent access.
67
68use super::super::Entity;
69
70/// A sparse set storing values of type `T` indexed by [`Entity`].
71///
72/// Provides O(1) access operations while maintaining cache-friendly iteration.
73/// This is the primary storage backend for ECS components.
74///
75/// # Type Parameters
76///
77/// - `T`: The value type stored in the set
78///
79/// # Memory Layout
80///
81/// The sparse set trades memory for performance:
82///
83/// - Sparse vec grows to max entity index seen (sparse memory usage)
84/// - Dense and values vecs are tightly packed (no gaps)
85///
86/// For entities with indices 0, 100, 1000, the sparse vec will have 1001 entries,
87/// but dense/values will only have 3 entries.
88#[derive(Debug)]
89pub struct SparseSet<T> {
90    /// Maps entity index to position in dense array.
91    /// `sparse[entity.index()]` = Some(dense_index) if entity has a value.
92    pub(super) sparse: Vec<Option<usize>>,
93
94    /// Packed array of entities that have values.
95    /// Enables O(1) removal via swap-remove.
96    pub(super) dense: Vec<Entity>,
97
98    /// Packed array of values, parallel to `dense`.
99    /// `values[i]` is the value for `dense[i]`.
100    pub(super) values: Vec<T>,
101
102    /// Tick at which each component was added, parallel to `dense`/`values`.
103    pub(super) added_ticks: Vec<u32>,
104
105    /// Tick at which each component was last changed, parallel to `dense`/`values`.
106    pub(super) changed_ticks: Vec<u32>,
107}
108
109impl<T> SparseSet<T> {
110    /// Creates a new, empty sparse set.
111    ///
112    /// # Example
113    ///
114    /// ```
115    /// use goud_engine::ecs::SparseSet;
116    ///
117    /// let set: SparseSet<i32> = SparseSet::new();
118    /// assert!(set.is_empty());
119    /// assert_eq!(set.len(), 0);
120    /// ```
121    #[inline]
122    pub fn new() -> Self {
123        Self {
124            sparse: Vec::new(),
125            dense: Vec::new(),
126            values: Vec::new(),
127            added_ticks: Vec::new(),
128            changed_ticks: Vec::new(),
129        }
130    }
131
132    /// Creates a sparse set with pre-allocated capacity.
133    ///
134    /// This pre-allocates the dense and values vectors, but not the sparse
135    /// vector (which grows on demand based on entity indices).
136    ///
137    /// # Arguments
138    ///
139    /// * `capacity` - Number of elements to pre-allocate
140    ///
141    /// # Example
142    ///
143    /// ```
144    /// use goud_engine::ecs::SparseSet;
145    ///
146    /// let set: SparseSet<String> = SparseSet::with_capacity(1000);
147    /// assert!(set.is_empty());
148    /// ```
149    #[inline]
150    pub fn with_capacity(capacity: usize) -> Self {
151        Self {
152            sparse: Vec::new(),
153            dense: Vec::with_capacity(capacity),
154            values: Vec::with_capacity(capacity),
155            added_ticks: Vec::with_capacity(capacity),
156            changed_ticks: Vec::with_capacity(capacity),
157        }
158    }
159
160    /// Inserts a value for the given entity.
161    ///
162    /// If the entity already has a value, it is replaced and the old value
163    /// is returned. Otherwise, `None` is returned.
164    ///
165    /// # Arguments
166    ///
167    /// * `entity` - The entity to associate with the value
168    /// * `value` - The value to store
169    ///
170    /// # Returns
171    ///
172    /// The previous value if one existed, or `None`.
173    ///
174    /// # Panics
175    ///
176    /// Panics if the entity is a placeholder (`Entity::PLACEHOLDER`).
177    ///
178    /// # Example
179    ///
180    /// ```
181    /// use goud_engine::ecs::{Entity, SparseSet};
182    ///
183    /// let mut set = SparseSet::new();
184    /// let entity = Entity::new(0, 1);
185    ///
186    /// assert_eq!(set.insert(entity, 42), None);
187    /// assert_eq!(set.insert(entity, 99), Some(42)); // Returns old value
188    /// assert_eq!(set.get(entity), Some(&99));
189    /// ```
190    pub fn insert(&mut self, entity: Entity, value: T) -> Option<T> {
191        self.insert_with_tick(entity, value, 0)
192    }
193
194    /// Removes the value for the given entity.
195    ///
196    /// Uses swap-remove to maintain dense packing: the last element is
197    /// moved to fill the gap, keeping iteration cache-friendly.
198    ///
199    /// # Arguments
200    ///
201    /// * `entity` - The entity whose value to remove
202    ///
203    /// # Returns
204    ///
205    /// The removed value if one existed, or `None`.
206    ///
207    /// # Example
208    ///
209    /// ```
210    /// use goud_engine::ecs::{Entity, SparseSet};
211    ///
212    /// let mut set = SparseSet::new();
213    /// let entity = Entity::new(0, 1);
214    ///
215    /// set.insert(entity, "hello");
216    /// assert_eq!(set.remove(entity), Some("hello"));
217    /// assert_eq!(set.remove(entity), None); // Already removed
218    /// ```
219    pub fn remove(&mut self, entity: Entity) -> Option<T> {
220        if entity.is_placeholder() {
221            return None;
222        }
223
224        let index = entity.index() as usize;
225
226        // Check bounds
227        if index >= self.sparse.len() {
228            return None;
229        }
230
231        // Get dense index
232        let dense_index = self.sparse[index]?;
233        self.sparse[index] = None;
234
235        // Get the last entity in the dense array
236        let last_index = self.dense.len() - 1;
237
238        if dense_index != last_index {
239            // Swap with last element
240            let last_entity = self.dense[last_index];
241            self.dense.swap(dense_index, last_index);
242            self.values.swap(dense_index, last_index);
243            self.added_ticks.swap(dense_index, last_index);
244            self.changed_ticks.swap(dense_index, last_index);
245
246            // Update sparse pointer for swapped entity
247            self.sparse[last_entity.index() as usize] = Some(dense_index);
248        }
249
250        // Remove last element (which is now our removed entity)
251        self.dense.pop();
252        self.added_ticks.pop();
253        self.changed_ticks.pop();
254        self.values.pop()
255    }
256
257    /// Returns a reference to the value for the given entity.
258    ///
259    /// # Arguments
260    ///
261    /// * `entity` - The entity to look up
262    ///
263    /// # Returns
264    ///
265    /// A reference to the value if the entity has one, or `None`.
266    ///
267    /// # Example
268    ///
269    /// ```
270    /// use goud_engine::ecs::{Entity, SparseSet};
271    ///
272    /// let mut set = SparseSet::new();
273    /// let entity = Entity::new(0, 1);
274    ///
275    /// set.insert(entity, 42);
276    /// assert_eq!(set.get(entity), Some(&42));
277    ///
278    /// let missing = Entity::new(999, 1);
279    /// assert_eq!(set.get(missing), None);
280    /// ```
281    #[inline]
282    pub fn get(&self, entity: Entity) -> Option<&T> {
283        if entity.is_placeholder() {
284            return None;
285        }
286
287        let index = entity.index() as usize;
288
289        if index >= self.sparse.len() {
290            return None;
291        }
292
293        let dense_index = self.sparse[index]?;
294        Some(&self.values[dense_index])
295    }
296
297    /// Returns a mutable reference to the value for the given entity.
298    ///
299    /// # Arguments
300    ///
301    /// * `entity` - The entity to look up
302    ///
303    /// # Returns
304    ///
305    /// A mutable reference to the value if the entity has one, or `None`.
306    ///
307    /// # Example
308    ///
309    /// ```
310    /// use goud_engine::ecs::{Entity, SparseSet};
311    ///
312    /// let mut set = SparseSet::new();
313    /// let entity = Entity::new(0, 1);
314    ///
315    /// set.insert(entity, 42);
316    ///
317    /// if let Some(value) = set.get_mut(entity) {
318    ///     *value = 100;
319    /// }
320    ///
321    /// assert_eq!(set.get(entity), Some(&100));
322    /// ```
323    #[inline]
324    pub fn get_mut(&mut self, entity: Entity) -> Option<&mut T> {
325        if entity.is_placeholder() {
326            return None;
327        }
328
329        let index = entity.index() as usize;
330
331        if index >= self.sparse.len() {
332            return None;
333        }
334
335        let dense_index = self.sparse[index]?;
336        Some(&mut self.values[dense_index])
337    }
338
339    /// Returns `true` if the entity has a value in this set.
340    ///
341    /// # Arguments
342    ///
343    /// * `entity` - The entity to check
344    ///
345    /// # Example
346    ///
347    /// ```
348    /// use goud_engine::ecs::{Entity, SparseSet};
349    ///
350    /// let mut set = SparseSet::new();
351    /// let entity = Entity::new(0, 1);
352    ///
353    /// assert!(!set.contains(entity));
354    /// set.insert(entity, "value");
355    /// assert!(set.contains(entity));
356    /// ```
357    #[inline]
358    pub fn contains(&self, entity: Entity) -> bool {
359        if entity.is_placeholder() {
360            return false;
361        }
362
363        let index = entity.index() as usize;
364
365        if index >= self.sparse.len() {
366            return false;
367        }
368
369        self.sparse[index].is_some()
370    }
371
372    /// Returns the number of entities with values in this set.
373    ///
374    /// # Example
375    ///
376    /// ```
377    /// use goud_engine::ecs::{Entity, SparseSet};
378    ///
379    /// let mut set = SparseSet::new();
380    /// assert_eq!(set.len(), 0);
381    ///
382    /// set.insert(Entity::new(0, 1), "a");
383    /// set.insert(Entity::new(5, 1), "b");
384    /// assert_eq!(set.len(), 2);
385    /// ```
386    #[inline]
387    pub fn len(&self) -> usize {
388        self.dense.len()
389    }
390
391    /// Returns `true` if the set contains no values.
392    ///
393    /// # Example
394    ///
395    /// ```
396    /// use goud_engine::ecs::{Entity, SparseSet};
397    ///
398    /// let mut set: SparseSet<i32> = SparseSet::new();
399    /// assert!(set.is_empty());
400    ///
401    /// set.insert(Entity::new(0, 1), 42);
402    /// assert!(!set.is_empty());
403    /// ```
404    #[inline]
405    pub fn is_empty(&self) -> bool {
406        self.dense.is_empty()
407    }
408
409    /// Removes all values from the set.
410    ///
411    /// This clears all three internal arrays. After calling `clear()`,
412    /// `len()` will return 0.
413    ///
414    /// # Example
415    ///
416    /// ```
417    /// use goud_engine::ecs::{Entity, SparseSet};
418    ///
419    /// let mut set = SparseSet::new();
420    /// set.insert(Entity::new(0, 1), "a");
421    /// set.insert(Entity::new(1, 1), "b");
422    /// assert_eq!(set.len(), 2);
423    ///
424    /// set.clear();
425    /// assert!(set.is_empty());
426    /// ```
427    pub fn clear(&mut self) {
428        self.sparse.clear();
429        self.dense.clear();
430        self.values.clear();
431        self.added_ticks.clear();
432        self.changed_ticks.clear();
433    }
434
435    /// Reserves capacity for at least `additional` more elements.
436    ///
437    /// This affects the dense and values arrays, not the sparse array
438    /// (which grows based on entity indices).
439    ///
440    /// # Arguments
441    ///
442    /// * `additional` - Number of additional elements to reserve
443    ///
444    /// # Example
445    ///
446    /// ```
447    /// use goud_engine::ecs::SparseSet;
448    ///
449    /// let mut set: SparseSet<i32> = SparseSet::new();
450    /// set.reserve(1000);
451    /// ```
452    pub fn reserve(&mut self, additional: usize) {
453        self.dense.reserve(additional);
454        self.values.reserve(additional);
455        self.added_ticks.reserve(additional);
456        self.changed_ticks.reserve(additional);
457    }
458}
459
460impl<T> Default for SparseSet<T> {
461    fn default() -> Self {
462        Self::new()
463    }
464}
465
466impl<T: Clone> Clone for SparseSet<T> {
467    fn clone(&self) -> Self {
468        Self {
469            sparse: self.sparse.clone(),
470            dense: self.dense.clone(),
471            values: self.values.clone(),
472            added_ticks: self.added_ticks.clone(),
473            changed_ticks: self.changed_ticks.clone(),
474        }
475    }
476}