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}