Skip to main content

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}