Skip to main content

kozan_primitives/arena/
storage.rs

1//! Parallel typed storage — one Vec per data kind, indexed by slot.
2//!
3//! This is the "column" in the `SoA` (Struct of Arrays) layout.
4//! Each `Storage<T>` holds one aspect of all entries: data, styles, etc.
5//!
6//! # Safety model
7//!
8//! Uses `MaybeUninit<T>` for performance (no default initialization of gaps).
9//! Tracks which slots are initialized via a `Vec<bool>` bitmap.
10//! - `get()`/`get_mut()` check initialization and return `Option`.
11//! - `get_unchecked()`/`get_unchecked_mut()` are unsafe — caller ensures initialized.
12//! - `set()` properly drops old values on overwrite.
13//! - `Drop` drops all initialized values.
14
15use core::mem::MaybeUninit;
16
17/// A typed parallel storage indexed by `u32` slot indices.
18///
19/// Managed by [`IdAllocator`](super::IdAllocator) — this storage does not
20/// decide which slots are alive. It only tracks which slots have been initialized.
21///
22/// # Performance
23///
24/// - `set()`: O(1) amortized (may grow the Vec).
25/// - `get()`: O(1) — array index + initialized check.
26/// - Cache-friendly: contiguous memory, linear access patterns.
27pub struct Storage<T> {
28    slots: Vec<MaybeUninit<T>>,
29    initialized: Vec<bool>,
30}
31
32impl<T> Storage<T> {
33    /// Create a new empty storage.
34    #[must_use]
35    pub fn new() -> Self {
36        Self {
37            slots: Vec::new(),
38            initialized: Vec::new(),
39        }
40    }
41
42    /// Number of slots (including uninitialized gaps).
43    #[inline]
44    #[must_use]
45    pub fn len(&self) -> usize {
46        self.slots.len()
47    }
48
49    /// Whether the storage has zero slots.
50    #[inline]
51    #[must_use]
52    pub fn is_empty(&self) -> bool {
53        self.slots.is_empty()
54    }
55
56    /// Grow to fit `index + 1` slots. New slots are uninitialized.
57    pub fn grow(&mut self, index: u32) {
58        let needed = index as usize + 1;
59        if needed > self.slots.len() {
60            self.slots.reserve(needed - self.slots.len());
61            self.initialized.reserve(needed - self.initialized.len());
62            while self.slots.len() < needed {
63                self.slots.push(MaybeUninit::uninit());
64                self.initialized.push(false);
65            }
66        }
67    }
68
69    /// Write a value at the given index. Grows if needed.
70    /// Drops the old value if the slot was already initialized.
71    #[inline]
72    pub fn set(&mut self, index: u32, value: T) {
73        self.grow(index);
74        let i = index as usize;
75        if self.initialized[i] {
76            unsafe {
77                self.slots[i].assume_init_drop();
78            }
79        }
80        self.slots[i] = MaybeUninit::new(value);
81        self.initialized[i] = true;
82    }
83
84    /// Check if a slot has been initialized.
85    #[inline]
86    #[must_use]
87    pub fn is_initialized(&self, index: u32) -> bool {
88        let i = index as usize;
89        i < self.initialized.len() && self.initialized[i]
90    }
91
92    /// Safe read — returns `None` if not initialized or out of bounds.
93    #[inline]
94    #[must_use]
95    pub fn get(&self, index: u32) -> Option<&T> {
96        if !self.is_initialized(index) {
97            return None;
98        }
99        Some(unsafe { self.slots.get_unchecked(index as usize).assume_init_ref() })
100    }
101
102    /// Safe mutable read — returns `None` if not initialized or out of bounds.
103    #[inline]
104    pub fn get_mut(&mut self, index: u32) -> Option<&mut T> {
105        if !self.is_initialized(index) {
106            return None;
107        }
108        Some(unsafe {
109            self.slots
110                .get_unchecked_mut(index as usize)
111                .assume_init_mut()
112        })
113    }
114
115    /// Unchecked read — caller must ensure the slot is initialized.
116    ///
117    /// # Safety
118    ///
119    /// The slot at `index` must have been initialized via `set()`.
120    #[inline]
121    #[must_use]
122    pub unsafe fn get_unchecked(&self, index: u32) -> &T {
123        debug_assert!(
124            self.is_initialized(index),
125            "Storage::get_unchecked on uninitialized slot {index}"
126        );
127        unsafe { self.slots.get_unchecked(index as usize).assume_init_ref() }
128    }
129
130    /// Unchecked mutable read — caller must ensure the slot is initialized.
131    ///
132    /// # Safety
133    ///
134    /// The slot at `index` must have been initialized via `set()`.
135    #[inline]
136    pub unsafe fn get_unchecked_mut(&mut self, index: u32) -> &mut T {
137        debug_assert!(
138            self.is_initialized(index),
139            "Storage::get_unchecked_mut on uninitialized slot {index}"
140        );
141        unsafe {
142            self.slots
143                .get_unchecked_mut(index as usize)
144                .assume_init_mut()
145        }
146    }
147
148    /// Remove the value at the given index and return it.
149    /// Returns `None` if not initialized.
150    pub fn take(&mut self, index: u32) -> Option<T> {
151        if !self.is_initialized(index) {
152            return None;
153        }
154        let i = index as usize;
155        self.initialized[i] = false;
156        Some(unsafe { self.slots.get_unchecked(i).assume_init_read() })
157    }
158
159    /// Drop all initialized values and reset to empty.
160    pub fn clear(&mut self) {
161        for i in 0..self.initialized.len() {
162            if self.initialized[i] {
163                unsafe {
164                    self.slots[i].assume_init_drop();
165                }
166                self.initialized[i] = false;
167            }
168        }
169    }
170
171    /// Iterate over all initialized `(index, &value)` pairs.
172    pub fn iter(&self) -> impl Iterator<Item = (u32, &T)> + '_ {
173        self.initialized
174            .iter()
175            .enumerate()
176            .filter_map(move |(i, &init)| {
177                if init {
178                    // SAFETY: initialized[i] is true, so the slot was written via set().
179                    Some((i as u32, unsafe { self.slots[i].assume_init_ref() }))
180                } else {
181                    None
182                }
183            })
184    }
185
186    /// Mark a slot as uninitialized and drop its value.
187    /// Safe to call on already-uninitialized slots (no-op).
188    pub fn clear_slot(&mut self, index: u32) {
189        let i = index as usize;
190        if i < self.initialized.len() && self.initialized[i] {
191            unsafe {
192                self.slots[i].assume_init_drop();
193            }
194            self.initialized[i] = false;
195        }
196    }
197}
198
199impl<T> Default for Storage<T> {
200    fn default() -> Self {
201        Self::new()
202    }
203}
204
205impl<T: Clone> Clone for Storage<T> {
206    fn clone(&self) -> Self {
207        let mut new = Self::new();
208        for (id, val) in self.iter() {
209            new.set(id, val.clone());
210        }
211        new
212    }
213}
214
215impl<T> Drop for Storage<T> {
216    fn drop(&mut self) {
217        for i in 0..self.initialized.len() {
218            if self.initialized[i] {
219                unsafe {
220                    self.slots[i].assume_init_drop();
221                }
222            }
223        }
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn new_is_empty() {
233        let s = Storage::<u32>::new();
234        assert_eq!(s.len(), 0);
235        assert!(s.is_empty());
236        assert!(!s.is_initialized(0));
237    }
238
239    #[test]
240    fn set_and_get() {
241        let mut s = Storage::new();
242        s.set(0, 42u32);
243        assert!(s.is_initialized(0));
244        assert_eq!(s.get(0), Some(&42));
245    }
246
247    #[test]
248    fn get_uninitialized_returns_none() {
249        let mut s = Storage::<u32>::new();
250        s.grow(5);
251        assert_eq!(s.get(2), None);
252        assert_eq!(s.get_mut(2), None);
253    }
254
255    #[test]
256    fn set_grows_storage() {
257        let mut s = Storage::new();
258        s.set(5, 99u32);
259        assert_eq!(s.len(), 6);
260        assert!(!s.is_initialized(0));
261        assert!(!s.is_initialized(4));
262        assert_eq!(s.get(5), Some(&99));
263    }
264
265    #[test]
266    fn set_overwrites() {
267        let mut s = Storage::new();
268        s.set(0, String::from("old"));
269        s.set(0, String::from("new"));
270        assert_eq!(s.get(0).map(|s| s.as_str()), Some("new"));
271    }
272
273    #[test]
274    fn get_mut_modifies() {
275        let mut s = Storage::new();
276        s.set(0, 10u32);
277        *s.get_mut(0).unwrap() = 20;
278        assert_eq!(s.get(0), Some(&20));
279    }
280
281    #[test]
282    fn take_removes() {
283        let mut s = Storage::new();
284        s.set(0, String::from("hello"));
285        let val = s.take(0);
286        assert_eq!(val.as_deref(), Some("hello"));
287        assert!(!s.is_initialized(0));
288    }
289
290    #[test]
291    fn take_uninitialized_returns_none() {
292        let mut s = Storage::<u32>::new();
293        s.grow(5);
294        assert_eq!(s.take(2), None);
295    }
296
297    #[test]
298    fn clear_slot_drops_value() {
299        use std::sync::atomic::{AtomicU32, Ordering};
300        static DROPS: AtomicU32 = AtomicU32::new(0);
301
302        struct Tracked;
303        impl Drop for Tracked {
304            fn drop(&mut self) {
305                DROPS.fetch_add(1, Ordering::Relaxed);
306            }
307        }
308
309        DROPS.store(0, Ordering::Relaxed);
310        let mut s = Storage::new();
311        s.set(0, Tracked);
312        assert_eq!(DROPS.load(Ordering::Relaxed), 0);
313
314        s.clear_slot(0);
315        assert_eq!(DROPS.load(Ordering::Relaxed), 1);
316        assert!(!s.is_initialized(0));
317    }
318
319    #[test]
320    fn clear_uninitialized_is_noop() {
321        let mut s = Storage::<u32>::new();
322        s.grow(5);
323        s.clear_slot(3);
324    }
325
326    #[test]
327    fn drop_cleans_up_all() {
328        use std::sync::atomic::{AtomicU32, Ordering};
329        static DROPS: AtomicU32 = AtomicU32::new(0);
330
331        struct Tracked;
332        impl Drop for Tracked {
333            fn drop(&mut self) {
334                DROPS.fetch_add(1, Ordering::Relaxed);
335            }
336        }
337
338        DROPS.store(0, Ordering::Relaxed);
339        {
340            let mut s = Storage::new();
341            s.set(0, Tracked);
342            s.set(2, Tracked);
343            s.set(4, Tracked);
344        }
345        assert_eq!(DROPS.load(Ordering::Relaxed), 3);
346    }
347
348    #[test]
349    fn set_overwrite_drops_old() {
350        use std::sync::atomic::{AtomicU32, Ordering};
351        static DROPS: AtomicU32 = AtomicU32::new(0);
352
353        struct Tracked;
354        impl Drop for Tracked {
355            fn drop(&mut self) {
356                DROPS.fetch_add(1, Ordering::Relaxed);
357            }
358        }
359
360        DROPS.store(0, Ordering::Relaxed);
361        let mut s = Storage::new();
362        s.set(0, Tracked);
363        assert_eq!(DROPS.load(Ordering::Relaxed), 0);
364        s.set(0, Tracked);
365        assert_eq!(DROPS.load(Ordering::Relaxed), 1);
366    }
367
368    #[test]
369    fn iter_yields_initialized_slots() {
370        let mut s = Storage::new();
371        s.set(0, 10u32);
372        s.set(2, 20);
373        s.set(4, 30);
374        let pairs: Vec<(u32, u32)> = s.iter().map(|(i, &v)| (i, v)).collect();
375        assert_eq!(pairs, vec![(0, 10), (2, 20), (4, 30)]);
376    }
377
378    #[test]
379    fn iter_empty_storage() {
380        let s = Storage::<u32>::new();
381        assert_eq!(s.iter().count(), 0);
382    }
383
384    #[test]
385    fn clone_preserves_values() {
386        let mut s = Storage::new();
387        s.set(0, 10u32);
388        s.set(3, 30);
389        s.set(5, 50);
390
391        let c = s.clone();
392        assert_eq!(c.get(0), Some(&10));
393        assert_eq!(c.get(1), None);
394        assert_eq!(c.get(3), Some(&30));
395        assert_eq!(c.get(5), Some(&50));
396    }
397
398    #[test]
399    fn clone_is_independent() {
400        let mut s = Storage::new();
401        s.set(0, String::from("hello"));
402        let mut c = s.clone();
403        c.set(0, String::from("world"));
404        assert_eq!(s.get(0).map(String::as_str), Some("hello"));
405        assert_eq!(c.get(0).map(String::as_str), Some("world"));
406    }
407
408    #[test]
409    #[should_panic(expected = "uninitialized slot")]
410    #[cfg(debug_assertions)]
411    fn unchecked_get_panics_in_debug() {
412        let mut s = Storage::<u32>::new();
413        s.grow(5);
414        unsafe {
415            let _ = s.get_unchecked(2);
416        }
417    }
418}