Skip to main content

aether_core/
buffer_pool.rs

1//! Pre-allocated audio buffer pool.
2//!
3//! All buffers are allocated at startup. The RT thread only borrows slices —
4//! no allocation, no deallocation, no system calls.
5
6use crate::{BUFFER_SIZE, MAX_BUFFERS};
7
8/// Opaque handle to a buffer in the pool.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct BufferId(pub u32);
11
12impl BufferId {
13    pub const SILENCE: Self = Self(u32::MAX);
14}
15
16/// Pre-allocated audio buffer pool.
17///
18/// Manages a fixed pool of audio buffers (64 samples each) with O(1) acquire/release.
19/// All memory is allocated at startup - the RT thread only borrows slices with
20/// zero allocation overhead.
21///
22/// # Design
23///
24/// - **Structure-of-Arrays:** Flat `Vec<f32>` storage for cache efficiency
25/// - **Free list:** Stack of available buffer IDs
26/// - **Pre-zeroed:** Buffers are zeroed on acquire
27/// - **Real-time safe:** No allocation, no locks, bounded time
28///
29/// # Example
30///
31/// ```
32/// use aether_core::buffer_pool::BufferPool;
33/// use aether_core::BUFFER_SIZE;
34///
35/// let mut pool = BufferPool::new(100);
36///
37/// // Acquire buffers
38/// let buf1 = pool.acquire().unwrap();
39/// let buf2 = pool.acquire().unwrap();
40///
41/// // Use buffers
42/// {
43///     let data = pool.get_mut(buf1);
44///     data[0] = 1.0;
45/// }
46///
47/// // Release buffers
48/// pool.release(buf1);
49/// pool.release(buf2);
50///
51/// // Buffers are recycled
52/// let buf3 = pool.acquire().unwrap();
53/// ```
54///
55/// # Capacity
56///
57/// The pool has a fixed capacity set at creation. When exhausted,
58/// `acquire()` returns `None`. Released buffers are immediately available
59/// for reuse.
60///
61/// # See Also
62///
63/// * [`BufferId`] - Opaque buffer handle
64pub struct BufferPool {
65    /// Flat storage: `buffers[id * BUFFER_SIZE .. (id+1) * BUFFER_SIZE]`
66    storage: Vec<f32>,
67    free_list: Vec<u32>,
68    capacity: usize,
69}
70
71impl BufferPool {
72    /// Creates a new buffer pool with the specified capacity.
73    ///
74    /// Pre-allocates all buffers upfront. No further allocation occurs
75    /// during audio processing.
76    ///
77    /// # Arguments
78    ///
79    /// * `capacity` - Number of buffers to pre-allocate
80    ///
81    /// # Example
82    ///
83    /// ```
84    /// use aether_core::buffer_pool::BufferPool;
85    ///
86    /// let pool = BufferPool::new(100);
87    /// assert_eq!(pool.capacity(), 100);
88    /// assert_eq!(pool.available(), 100);
89    /// ```
90    pub fn new(capacity: usize) -> Self {
91        let storage = vec![0.0f32; capacity * BUFFER_SIZE];
92        let free_list: Vec<u32> = (0..capacity as u32).rev().collect();
93        Self {
94            storage,
95            free_list,
96            capacity,
97        }
98    }
99
100    /// Acquire a zeroed buffer. O(1). Returns None if pool is exhausted.
101    ///
102    /// Pops a buffer ID from the free list and zeros the buffer before
103    /// returning it. The buffer is guaranteed to contain all zeros.
104    ///
105    /// # Returns
106    ///
107    /// * `Some(BufferId)` - Handle to an available buffer
108    /// * `None` - Pool is exhausted (all buffers in use)
109    ///
110    /// # Example
111    ///
112    /// ```
113    /// use aether_core::buffer_pool::BufferPool;
114    ///
115    /// let mut pool = BufferPool::new(10);
116    ///
117    /// let buf = pool.acquire().unwrap();
118    /// let data = pool.get(buf);
119    /// assert_eq!(data[0], 0.0); // Guaranteed zero
120    /// ```
121    ///
122    /// # Performance
123    ///
124    /// - Time: O(1)
125    /// - Zeros 64 samples (256 bytes)
126    /// - Real-time safe
127    pub fn acquire(&mut self) -> Option<BufferId> {
128        let id = self.free_list.pop()?;
129        // Zero the buffer before handing it out.
130        let start = id as usize * BUFFER_SIZE;
131        self.storage[start..start + BUFFER_SIZE].fill(0.0);
132        Some(BufferId(id))
133    }
134
135    /// Release a buffer back to the pool. O(1).
136    ///
137    /// Returns the buffer to the free list for reuse. The buffer's contents
138    /// are not cleared until the next `acquire()`.
139    ///
140    /// # Arguments
141    ///
142    /// * `id` - Buffer ID to release
143    ///
144    /// # Example
145    ///
146    /// ```
147    /// use aether_core::buffer_pool::BufferPool;
148    ///
149    /// let mut pool = BufferPool::new(10);
150    ///
151    /// let buf = pool.acquire().unwrap();
152    /// assert_eq!(pool.available(), 9);
153    ///
154    /// pool.release(buf);
155    /// assert_eq!(pool.available(), 10);
156    /// ```
157    ///
158    /// # Performance
159    ///
160    /// - Time: O(1)
161    /// - No zeroing (deferred to acquire)
162    /// - Real-time safe
163    pub fn release(&mut self, id: BufferId) {
164        debug_assert!((id.0 as usize) < self.capacity, "BufferId out of range");
165        self.free_list.push(id.0);
166    }
167
168    /// Get a read-only slice for a buffer.
169    ///
170    /// Returns a reference to the 64-sample buffer identified by `id`.
171    /// This is a zero-cost operation - just pointer arithmetic.
172    ///
173    /// # Arguments
174    ///
175    /// * `id` - Buffer ID from `acquire()`
176    ///
177    /// # Returns
178    ///
179    /// A reference to a 64-sample audio buffer.
180    ///
181    /// # Example
182    ///
183    /// ```
184    /// use aether_core::buffer_pool::BufferPool;
185    ///
186    /// let mut pool = BufferPool::new(10);
187    /// let buf = pool.acquire().unwrap();
188    ///
189    /// // Write to buffer
190    /// pool.get_mut(buf)[0] = 1.0;
191    ///
192    /// // Read from buffer
193    /// let data = pool.get(buf);
194    /// assert_eq!(data[0], 1.0);
195    /// ```
196    ///
197    /// # Performance
198    ///
199    /// - Time: O(1) - inline pointer arithmetic
200    /// - No bounds checking in release builds
201    /// - Real-time safe
202    ///
203    /// # Panics
204    ///
205    /// Panics in debug builds if `id` is out of range.
206    #[inline(always)]
207    pub fn get(&self, id: BufferId) -> &[f32; BUFFER_SIZE] {
208        let start = id.0 as usize * BUFFER_SIZE;
209        self.storage[start..start + BUFFER_SIZE].try_into().unwrap()
210    }
211
212    /// Get a mutable slice for a buffer.
213    ///
214    /// Returns a mutable reference to the 64-sample buffer identified by `id`.
215    /// Use this to write audio data into the buffer.
216    ///
217    /// # Arguments
218    ///
219    /// * `id` - Buffer ID from `acquire()`
220    ///
221    /// # Returns
222    ///
223    /// A mutable reference to a 64-sample audio buffer.
224    ///
225    /// # Example
226    ///
227    /// ```
228    /// use aether_core::buffer_pool::BufferPool;
229    ///
230    /// let mut pool = BufferPool::new(10);
231    /// let buf = pool.acquire().unwrap();
232    ///
233    /// // Fill buffer with sine wave
234    /// let data = pool.get_mut(buf);
235    /// for (i, sample) in data.iter_mut().enumerate() {
236    ///     let phase = i as f32 / 64.0;
237    ///     *sample = (phase * std::f32::consts::TAU).sin();
238    /// }
239    /// ```
240    ///
241    /// # Performance
242    ///
243    /// - Time: O(1) - inline pointer arithmetic
244    /// - No bounds checking in release builds
245    /// - Real-time safe
246    ///
247    /// # Panics
248    ///
249    /// Panics in debug builds if `id` is out of range.
250    #[inline(always)]
251    pub fn get_mut(&mut self, id: BufferId) -> &mut [f32; BUFFER_SIZE] {
252        let start = id.0 as usize * BUFFER_SIZE;
253        (&mut self.storage[start..start + BUFFER_SIZE])
254            .try_into()
255            .unwrap()
256    }
257
258    /// Return a zeroed silence buffer (static, no allocation).
259    pub fn silence() -> &'static [f32; BUFFER_SIZE] {
260        static SILENCE: [f32; BUFFER_SIZE] = [0.0f32; BUFFER_SIZE];
261        &SILENCE
262    }
263
264    pub fn available(&self) -> usize {
265        self.free_list.len()
266    }
267    pub fn capacity(&self) -> usize {
268        self.capacity
269    }
270}
271
272impl Default for BufferPool {
273    fn default() -> Self {
274        Self::new(MAX_BUFFERS)
275    }
276}