Skip to main content

aether_core/
arena.rs

1//! Generational arena allocator.
2//!
3//! Provides O(1) alloc/dealloc with use-after-free detection via generation counters.
4//! All memory is pre-allocated at startup — zero heap activity in the RT thread.
5
6use crate::MAX_NODES;
7
8/// A typed, generational index into the arena.
9///
10/// Combines an index with a generation counter to prevent use-after-free bugs
11/// and ABA problems. When a slot is reused, its generation is incremented,
12/// invalidating all old `NodeId`s pointing to that slot.
13///
14/// # Example
15///
16/// ```
17/// use aether_core::arena::{Arena, NodeId};
18///
19/// let mut arena: Arena<i32> = Arena::with_capacity(10);
20///
21/// let id1 = arena.insert(42).unwrap();
22/// arena.remove(id1);
23///
24/// let id2 = arena.insert(99).unwrap();
25///
26/// // Same slot, different generation
27/// assert_eq!(id1.index, id2.index);
28/// assert_ne!(id1.generation, id2.generation);
29///
30/// // Old ID is invalid
31/// assert!(arena.get(id1).is_none());
32/// // New ID is valid
33/// assert_eq!(*arena.get(id2).unwrap(), 99);
34/// ```
35///
36/// # Safety
37///
38/// The generation counter prevents:
39/// - **Use-after-free:** Old IDs become invalid when slots are reused
40/// - **ABA problem:** Can't confuse old and new values in the same slot
41/// - **Dangling references:** Stale IDs return `None` instead of wrong data
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub struct NodeId {
44    pub index: u32,
45    pub generation: u32,
46}
47
48impl NodeId {
49    pub const INVALID: Self = Self {
50        index: u32::MAX,
51        generation: u32::MAX,
52    };
53}
54
55struct Entry<T> {
56    /// The stored value, valid only when `occupied` is true.
57    value: Option<T>,
58    /// Current generation. Incremented on each removal.
59    generation: u32,
60    /// Next free slot index, valid only when `value` is None.
61    next_free: Option<u32>,
62}
63
64/// Fixed-capacity generational arena.
65///
66/// A pre-allocated, generational arena allocator that provides O(1) insert/remove
67/// with use-after-free detection. All memory is allocated upfront - zero heap
68/// activity during audio processing.
69///
70/// # Features
71///
72/// - **O(1) insert/remove:** Constant-time operations
73/// - **Generational indices:** Prevents use-after-free bugs
74/// - **Pre-allocated:** No runtime allocation
75/// - **Real-time safe:** No locks, no allocation, bounded time
76///
77/// # Example
78///
79/// ```
80/// use aether_core::arena::Arena;
81///
82/// let mut arena: Arena<String> = Arena::with_capacity(100);
83///
84/// // Insert values
85/// let id1 = arena.insert("Hello".to_string()).unwrap();
86/// let id2 = arena.insert("World".to_string()).unwrap();
87///
88/// // Access values
89/// assert_eq!(arena.get(id1).unwrap(), "Hello");
90/// assert_eq!(arena.get(id2).unwrap(), "World");
91///
92/// // Remove and reuse slots
93/// arena.remove(id1);
94/// let id3 = arena.insert("Rust".to_string()).unwrap();
95///
96/// // Old ID is invalid, new ID is valid
97/// assert!(arena.get(id1).is_none());
98/// assert_eq!(arena.get(id3).unwrap(), "Rust");
99/// ```
100///
101/// # Capacity
102///
103/// The arena has a fixed capacity set at creation. When full, `insert()`
104/// returns `None`. Removed slots are recycled via a free list.
105///
106/// # Use Case
107///
108/// Perfect for managing DSP nodes in an audio graph where:
109/// - Nodes are added/removed dynamically
110/// - Need to prevent dangling references
111/// - Must avoid runtime allocation
112/// - Require O(1) operations
113///
114/// # See Also
115///
116/// * [`NodeId`] - Generational index type
117pub struct Arena<T> {
118    entries: Vec<Entry<T>>,
119    free_head: Option<u32>,
120    len: usize,
121}
122
123impl<T> Arena<T> {
124    /// Allocate a new arena with `capacity` pre-reserved slots.
125    ///
126    /// All memory is allocated upfront. The arena can hold up to `capacity`
127    /// items simultaneously. Removed slots are recycled.
128    ///
129    /// # Arguments
130    ///
131    /// * `capacity` - Maximum number of items the arena can hold
132    ///
133    /// # Example
134    ///
135    /// ```
136    /// use aether_core::arena::Arena;
137    ///
138    /// let arena: Arena<i32> = Arena::with_capacity(1000);
139    /// assert_eq!(arena.len(), 0);
140    /// assert!(arena.is_empty());
141    /// ```
142    ///
143    /// # Performance
144    ///
145    /// - Time: O(capacity) - initializes free list
146    /// - Space: O(capacity) - pre-allocates all slots
147    /// - No further allocation after construction
148    pub fn with_capacity(capacity: usize) -> Self {
149        let mut entries = Vec::with_capacity(capacity);
150        for i in 0..capacity {
151            let next = if i + 1 < capacity {
152                Some((i + 1) as u32)
153            } else {
154                None
155            };
156            entries.push(Entry {
157                value: None,
158                generation: 0,
159                next_free: next,
160            });
161        }
162        Self {
163            entries,
164            free_head: if capacity > 0 { Some(0) } else { None },
165            len: 0,
166        }
167    }
168
169    /// Insert a value, returning its generational id.
170    ///
171    /// Allocates a slot from the free list and stores the value.
172    /// Returns a `NodeId` that can be used to access the value later.
173    ///
174    /// # Arguments
175    ///
176    /// * `value` - Value to insert
177    ///
178    /// # Returns
179    ///
180    /// * `Some(NodeId)` - Generational ID for the inserted value
181    /// * `None` - Arena is full (all slots occupied)
182    ///
183    /// # Example
184    ///
185    /// ```
186    /// use aether_core::arena::Arena;
187    ///
188    /// let mut arena: Arena<String> = Arena::with_capacity(10);
189    ///
190    /// let id = arena.insert("Hello".to_string()).unwrap();
191    /// assert_eq!(arena.get(id).unwrap(), "Hello");
192    /// assert_eq!(arena.len(), 1);
193    /// ```
194    ///
195    /// # Capacity Exhaustion
196    ///
197    /// ```
198    /// use aether_core::arena::Arena;
199    ///
200    /// let mut arena: Arena<i32> = Arena::with_capacity(2);
201    ///
202    /// let id1 = arena.insert(1).unwrap();
203    /// let id2 = arena.insert(2).unwrap();
204    /// let id3 = arena.insert(3); // None - arena is full
205    ///
206    /// assert!(id3.is_none());
207    ///
208    /// // Remove a slot to make space
209    /// arena.remove(id1);
210    /// let id4 = arena.insert(4).unwrap(); // Now succeeds
211    /// ```
212    ///
213    /// # Performance
214    ///
215    /// - Time: O(1)
216    /// - Space: O(0) - no allocation
217    /// - Real-time safe
218    pub fn insert(&mut self, value: T) -> Option<NodeId> {
219        let index = self.free_head?;
220        let entry = &mut self.entries[index as usize];
221        self.free_head = entry.next_free;
222        let generation = entry.generation;
223        entry.value = Some(value);
224        entry.next_free = None;
225        self.len += 1;
226        Some(NodeId { index, generation })
227    }
228
229    /// Remove a value by id. Returns the value if the id was valid.
230    ///
231    /// Removes the value from the arena, increments the slot's generation
232    /// counter (invalidating all existing IDs), and returns the slot to
233    /// the free list for reuse.
234    ///
235    /// # Arguments
236    ///
237    /// * `id` - Generational ID of the value to remove
238    ///
239    /// # Returns
240    ///
241    /// * `Some(T)` - The removed value (if ID was valid)
242    /// * `None` - ID was invalid (wrong generation or already removed)
243    ///
244    /// # Example
245    ///
246    /// ```
247    /// use aether_core::arena::Arena;
248    ///
249    /// let mut arena: Arena<i32> = Arena::with_capacity(10);
250    ///
251    /// let id = arena.insert(42).unwrap();
252    /// assert_eq!(arena.len(), 1);
253    ///
254    /// let value = arena.remove(id).unwrap();
255    /// assert_eq!(value, 42);
256    /// assert_eq!(arena.len(), 0);
257    ///
258    /// // ID is now invalid
259    /// assert!(arena.get(id).is_none());
260    /// assert!(arena.remove(id).is_none());
261    /// ```
262    ///
263    /// # Generation Bump
264    ///
265    /// ```
266    /// use aether_core::arena::Arena;
267    ///
268    /// let mut arena: Arena<i32> = Arena::with_capacity(10);
269    ///
270    /// let id1 = arena.insert(1).unwrap();
271    /// let gen1 = id1.generation;
272    ///
273    /// arena.remove(id1);
274    ///
275    /// let id2 = arena.insert(2).unwrap();
276    /// let gen2 = id2.generation;
277    ///
278    /// // Same slot, different generation
279    /// assert_eq!(id1.index, id2.index);
280    /// assert_eq!(gen2, gen1 + 1);
281    /// ```
282    ///
283    /// # Performance
284    ///
285    /// - Time: O(1)
286    /// - Space: O(0) - no allocation
287    /// - Real-time safe
288    pub fn remove(&mut self, id: NodeId) -> Option<T> {
289        let entry = self.entries.get_mut(id.index as usize)?;
290        if entry.generation != id.generation || entry.value.is_none() {
291            return None;
292        }
293        let value = entry.value.take();
294        // Bump generation to invalidate all existing ids pointing here.
295        entry.generation = entry.generation.wrapping_add(1);
296        entry.next_free = self.free_head;
297        self.free_head = Some(id.index);
298        self.len -= 1;
299        value
300    }
301
302    /// Get a shared reference. Returns `None` for stale ids.
303    ///
304    /// Returns a reference to the value if the ID is valid (correct generation
305    /// and slot is occupied). Returns `None` if the ID is stale or invalid.
306    ///
307    /// # Arguments
308    ///
309    /// * `id` - Generational ID to look up
310    ///
311    /// # Returns
312    ///
313    /// * `Some(&T)` - Reference to the value (if ID is valid)
314    /// * `None` - ID is invalid or stale
315    ///
316    /// # Example
317    ///
318    /// ```
319    /// use aether_core::arena::Arena;
320    ///
321    /// let mut arena: Arena<String> = Arena::with_capacity(10);
322    ///
323    /// let id = arena.insert("Hello".to_string()).unwrap();
324    ///
325    /// // Valid ID
326    /// assert_eq!(arena.get(id).unwrap(), "Hello");
327    ///
328    /// arena.remove(id);
329    ///
330    /// // Stale ID
331    /// assert!(arena.get(id).is_none());
332    /// ```
333    ///
334    /// # Performance
335    ///
336    /// - Time: O(1)
337    /// - Inlined for zero call overhead
338    /// - Real-time safe
339    #[inline]
340    pub fn get(&self, id: NodeId) -> Option<&T> {
341        let entry = self.entries.get(id.index as usize)?;
342        if entry.generation == id.generation {
343            entry.value.as_ref()
344        } else {
345            None
346        }
347    }
348
349    /// Get a mutable reference. Returns `None` for stale ids.
350    ///
351    /// Returns a mutable reference to the value if the ID is valid.
352    /// Returns `None` if the ID is stale or invalid.
353    ///
354    /// # Arguments
355    ///
356    /// * `id` - Generational ID to look up
357    ///
358    /// # Returns
359    ///
360    /// * `Some(&mut T)` - Mutable reference to the value (if ID is valid)
361    /// * `None` - ID is invalid or stale
362    ///
363    /// # Example
364    ///
365    /// ```
366    /// use aether_core::arena::Arena;
367    ///
368    /// let mut arena: Arena<i32> = Arena::with_capacity(10);
369    ///
370    /// let id = arena.insert(42).unwrap();
371    ///
372    /// // Modify the value
373    /// if let Some(value) = arena.get_mut(id) {
374    ///     *value = 99;
375    /// }
376    ///
377    /// assert_eq!(*arena.get(id).unwrap(), 99);
378    /// ```
379    ///
380    /// # Performance
381    ///
382    /// - Time: O(1)
383    /// - Inlined for zero call overhead
384    /// - Real-time safe
385    #[inline]
386    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> {
387        let entry = self.entries.get_mut(id.index as usize)?;
388        if entry.generation == id.generation {
389            entry.value.as_mut()
390        } else {
391            None
392        }
393    }
394
395    pub fn len(&self) -> usize {
396        self.len
397    }
398    pub fn is_empty(&self) -> bool {
399        self.len == 0
400    }
401}
402
403/// Default arena sized for the project's node limit.
404pub fn default_node_arena<T>() -> Arena<T> {
405    Arena::with_capacity(MAX_NODES)
406}
407
408#[cfg(test)]
409mod tests {
410    use super::*;
411
412    #[test]
413    fn insert_get_remove() {
414        let mut arena: Arena<i32> = Arena::with_capacity(4);
415        let id = arena.insert(42).unwrap();
416        assert_eq!(*arena.get(id).unwrap(), 42);
417        let val = arena.remove(id).unwrap();
418        assert_eq!(val, 42);
419        assert!(arena.get(id).is_none());
420    }
421
422    #[test]
423    fn generation_prevents_aba() {
424        let mut arena: Arena<i32> = Arena::with_capacity(4);
425        let id1 = arena.insert(1).unwrap();
426        arena.remove(id1).unwrap();
427        let id2 = arena.insert(2).unwrap();
428        // Same slot index, bumped generation.
429        assert_eq!(id1.index, id2.index);
430        assert_ne!(id1.generation, id2.generation);
431        assert!(arena.get(id1).is_none());
432        assert_eq!(*arena.get(id2).unwrap(), 2);
433    }
434
435    #[test]
436    fn capacity_exhaustion() {
437        let mut arena: Arena<i32> = Arena::with_capacity(2);
438        let a = arena.insert(1).unwrap();
439        let _b = arena.insert(2).unwrap();
440        assert!(arena.insert(3).is_none()); // full
441        arena.remove(a).unwrap();
442        assert!(arena.insert(3).is_some()); // slot recycled
443    }
444}