ff_common/pool.rs
1//! Frame buffer pooling abstractions.
2//!
3//! This module provides the [`FramePool`] trait and [`PooledBuffer`] type
4//! which enable memory pooling for decoded frames, reducing allocation
5//! overhead during video playback.
6
7use std::sync::Weak;
8
9/// A trait for frame buffer pooling.
10///
11/// Implementing this trait allows custom memory management strategies
12/// for decoded video frames. This is useful for reducing allocation
13/// pressure during real-time video playback.
14///
15/// # Thread Safety
16///
17/// Implementations must be `Send + Sync` to allow sharing across threads.
18///
19/// # Example Implementation
20///
21/// ```ignore
22/// use ff_common::{FramePool, PooledBuffer};
23/// use std::sync::{Arc, Mutex};
24///
25/// struct SimplePool {
26/// buffers: Mutex<Vec<Vec<u8>>>,
27/// buffer_size: usize,
28///}
29///
30/// impl FramePool for SimplePool {
31/// fn acquire(&self, size: usize) -> Option<PooledBuffer> {
32/// // Implementation...
33/// None
34/// }
35/// }
36/// ```
37pub trait FramePool: Send + Sync + std::fmt::Debug {
38 /// Acquires a buffer of at least the specified size from the pool.
39 ///
40 /// # Arguments
41 ///
42 /// * `size` - The minimum required buffer size in bytes.
43 ///
44 /// # Returns
45 ///
46 /// Returns `Some(PooledBuffer)` if a buffer is available, or `None` if
47 /// the pool is exhausted. When `None` is returned, the decoder will
48 /// allocate a new buffer directly.
49 ///
50 /// # Thread Safety
51 ///
52 /// This method may be called from multiple threads concurrently.
53 fn acquire(&self, size: usize) -> Option<PooledBuffer>;
54
55 /// Returns a buffer to the pool.
56 ///
57 /// This method is called automatically when a [`PooledBuffer`] is dropped.
58 /// The default implementation does nothing (the buffer is simply dropped).
59 ///
60 /// # Arguments
61 ///
62 /// * `buffer` - The buffer data to return to the pool.
63 fn release(&self, _buffer: Vec<u8>) {
64 // Default: just drop the buffer
65 }
66}
67
68/// A buffer acquired from a [`FramePool`].
69///
70/// When this buffer is dropped, it is automatically returned to its
71/// parent pool if the pool still exists. This enables zero-overhead
72/// buffer reuse during video decoding.
73///
74/// # Memory Management
75///
76/// The buffer holds a weak reference to its parent pool. If the pool
77/// is dropped before the buffer, the buffer's memory is simply freed
78/// rather than being returned to the pool.
79///
80/// # Cloning
81///
82/// When cloned, the new buffer becomes a standalone buffer (no pool reference).
83/// This prevents double-free issues where both the original and cloned buffer
84/// would attempt to return the same memory to the pool.
85#[derive(Debug)]
86pub struct PooledBuffer {
87 /// The actual buffer data
88 data: Vec<u8>,
89 /// Weak reference to the parent pool for returning the buffer
90 pool: Option<Weak<dyn FramePool>>,
91}
92
93impl PooledBuffer {
94 /// Creates a new pooled buffer with a reference to its parent pool.
95 ///
96 /// # Arguments
97 ///
98 /// * `data` - The buffer data.
99 /// * `pool` - A weak reference to the parent pool.
100 #[must_use]
101 pub fn new(data: Vec<u8>, pool: Weak<dyn FramePool>) -> Self {
102 Self {
103 data,
104 pool: Some(pool),
105 }
106 }
107
108 /// Creates a new pooled buffer without a parent pool.
109 ///
110 /// This is useful for buffers allocated outside of a pool context.
111 /// When dropped, the buffer's memory is simply freed.
112 #[must_use]
113 pub fn standalone(data: Vec<u8>) -> Self {
114 Self { data, pool: None }
115 }
116
117 /// Returns a reference to the buffer data.
118 #[must_use]
119 #[inline]
120 pub fn data(&self) -> &[u8] {
121 &self.data
122 }
123
124 /// Returns a mutable reference to the buffer data.
125 #[must_use]
126 #[inline]
127 pub fn data_mut(&mut self) -> &mut [u8] {
128 &mut self.data
129 }
130
131 /// Returns the length of the buffer in bytes.
132 #[must_use]
133 #[inline]
134 pub fn len(&self) -> usize {
135 self.data.len()
136 }
137
138 /// Returns `true` if the buffer is empty.
139 #[must_use]
140 #[inline]
141 pub fn is_empty(&self) -> bool {
142 self.data.is_empty()
143 }
144
145 /// Consumes the buffer and returns the underlying data.
146 ///
147 /// After calling this, the buffer will not be returned to the pool.
148 #[must_use]
149 pub fn into_inner(mut self) -> Vec<u8> {
150 // Take ownership to prevent Drop from returning to pool
151 self.pool = None;
152 std::mem::take(&mut self.data)
153 }
154}
155
156impl Clone for PooledBuffer {
157 /// Clones the buffer data, but the cloned buffer becomes standalone.
158 ///
159 /// The cloned buffer will NOT be returned to the pool when dropped.
160 /// This prevents double-free issues where both buffers would attempt
161 /// to return the same memory to the pool.
162 ///
163 /// Only the original buffer retains its pool reference.
164 fn clone(&self) -> Self {
165 Self {
166 data: self.data.clone(),
167 pool: None, // Cloned buffer is standalone
168 }
169 }
170}
171
172impl Drop for PooledBuffer {
173 fn drop(&mut self) {
174 if let Some(ref weak_pool) = self.pool
175 && let Some(pool) = weak_pool.upgrade()
176 {
177 // Return the buffer to the pool
178 let data = std::mem::take(&mut self.data);
179 pool.release(data);
180 }
181 // If pool is None or has been dropped, data is simply freed
182 }
183}
184
185impl AsRef<[u8]> for PooledBuffer {
186 fn as_ref(&self) -> &[u8] {
187 &self.data
188 }
189}
190
191impl AsMut<[u8]> for PooledBuffer {
192 fn as_mut(&mut self) -> &mut [u8] {
193 &mut self.data
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use std::sync::Arc;
201
202 #[test]
203 fn test_pooled_buffer_standalone() {
204 let data = vec![1u8, 2, 3, 4, 5];
205 let buffer = PooledBuffer::standalone(data.clone());
206
207 assert_eq!(buffer.len(), 5);
208 assert!(!buffer.is_empty());
209 assert_eq!(buffer.data(), &[1, 2, 3, 4, 5]);
210 }
211
212 #[test]
213 fn test_pooled_buffer_data_mut() {
214 let mut buffer = PooledBuffer::standalone(vec![0u8; 4]);
215 buffer.data_mut()[0] = 42;
216 assert_eq!(buffer.data()[0], 42);
217 }
218
219 #[test]
220 fn test_pooled_buffer_into_inner() {
221 let buffer = PooledBuffer::standalone(vec![1, 2, 3]);
222 let inner = buffer.into_inner();
223 assert_eq!(inner, vec![1, 2, 3]);
224 }
225
226 #[test]
227 fn test_pooled_buffer_as_ref() {
228 let buffer = PooledBuffer::standalone(vec![1, 2, 3]);
229 let slice: &[u8] = buffer.as_ref();
230 assert_eq!(slice, &[1, 2, 3]);
231 }
232
233 #[test]
234 fn test_pooled_buffer_as_mut() {
235 let mut buffer = PooledBuffer::standalone(vec![1, 2, 3]);
236 let slice: &mut [u8] = buffer.as_mut();
237 slice[0] = 99;
238 assert_eq!(buffer.data(), &[99, 2, 3]);
239 }
240
241 #[test]
242 fn test_empty_buffer() {
243 let buffer = PooledBuffer::standalone(vec![]);
244 assert!(buffer.is_empty());
245 assert_eq!(buffer.len(), 0);
246 }
247
248 #[test]
249 fn test_pool_with_arc_release() {
250 use std::sync::Mutex;
251 use std::sync::atomic::{AtomicUsize, Ordering};
252
253 #[derive(Debug)]
254 struct ArcPool {
255 buffers: Mutex<Vec<Vec<u8>>>,
256 release_count: AtomicUsize,
257 }
258
259 impl FramePool for ArcPool {
260 fn acquire(&self, _size: usize) -> Option<PooledBuffer> {
261 None // Not used in this test
262 }
263
264 fn release(&self, buffer: Vec<u8>) {
265 if let Ok(mut buffers) = self.buffers.lock() {
266 buffers.push(buffer);
267 self.release_count.fetch_add(1, Ordering::SeqCst);
268 }
269 }
270 }
271
272 let pool = Arc::new(ArcPool {
273 buffers: Mutex::new(vec![]),
274 release_count: AtomicUsize::new(0),
275 });
276
277 // Create a buffer with pool reference
278 {
279 let _buffer =
280 PooledBuffer::new(vec![1, 2, 3], Arc::downgrade(&pool) as Weak<dyn FramePool>);
281 // Buffer is dropped here
282 }
283
284 // Verify the buffer was returned to the pool
285 assert_eq!(pool.release_count.load(Ordering::SeqCst), 1);
286 assert!(pool.buffers.lock().map(|b| b.len() == 1).unwrap_or(false));
287 }
288
289 #[test]
290 fn test_pool_dropped_before_buffer() {
291 #[derive(Debug)]
292 struct DroppablePool;
293
294 impl FramePool for DroppablePool {
295 fn acquire(&self, _size: usize) -> Option<PooledBuffer> {
296 None
297 }
298
299 fn release(&self, _buffer: Vec<u8>) {
300 // This should NOT be called if pool is dropped
301 panic!("release should not be called on dropped pool");
302 }
303 }
304
305 let buffer;
306 {
307 let pool = Arc::new(DroppablePool);
308 buffer = PooledBuffer::new(vec![1, 2, 3], Arc::downgrade(&pool) as Weak<dyn FramePool>);
309 // Pool is dropped here
310 }
311
312 // Buffer can still be used
313 assert_eq!(buffer.data(), &[1, 2, 3]);
314
315 // Dropping buffer should not panic (pool is already gone)
316 drop(buffer);
317 }
318
319 #[test]
320 fn test_pooled_buffer_clone_becomes_standalone() {
321 use std::sync::atomic::{AtomicUsize, Ordering};
322
323 #[derive(Debug)]
324 struct CountingPool {
325 release_count: AtomicUsize,
326 }
327
328 impl FramePool for CountingPool {
329 fn acquire(&self, _size: usize) -> Option<PooledBuffer> {
330 None
331 }
332
333 fn release(&self, _buffer: Vec<u8>) {
334 self.release_count.fetch_add(1, Ordering::SeqCst);
335 }
336 }
337
338 let pool = Arc::new(CountingPool {
339 release_count: AtomicUsize::new(0),
340 });
341
342 // Create pooled buffer
343 let buffer1 =
344 PooledBuffer::new(vec![1, 2, 3], Arc::downgrade(&pool) as Weak<dyn FramePool>);
345
346 // Clone it
347 let buffer2 = buffer1.clone();
348
349 // Both buffers have the same data
350 assert_eq!(buffer1.data(), &[1, 2, 3]);
351 assert_eq!(buffer2.data(), &[1, 2, 3]);
352
353 // Drop both buffers
354 drop(buffer1);
355 drop(buffer2);
356
357 // Only the original buffer should have been returned to pool (count = 1)
358 // The cloned buffer is standalone and should NOT return to pool
359 assert_eq!(pool.release_count.load(Ordering::SeqCst), 1);
360 }
361
362 #[test]
363 fn test_pooled_buffer_clone_data_independence() {
364 let buffer1 = PooledBuffer::standalone(vec![1, 2, 3]);
365 let mut buffer2 = buffer1.clone();
366
367 // Modify buffer2
368 buffer2.data_mut()[0] = 99;
369
370 // buffer1 should be unaffected (deep copy)
371 assert_eq!(buffer1.data(), &[1, 2, 3]);
372 assert_eq!(buffer2.data(), &[99, 2, 3]);
373 }
374}