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}