Skip to main content

laminar_core/alloc/
object_pool.rs

1//! Fixed-size object pool for zero-allocation acquire/release.
2//!
3//! Pre-allocates objects at creation time, providing O(1) allocation-free
4//! acquire and release operations for use in Ring 0 hot path.
5
6use arrayvec::ArrayVec;
7
8/// Fixed-size object pool for frequently allocated types.
9///
10/// Pre-allocates objects at creation, provides O(1) acquire/release.
11/// Used in Ring 0 to avoid heap allocation during event processing.
12///
13/// # Type Parameters
14///
15/// * `T` - The type of objects in the pool. Must implement `Default`.
16/// * `N` - Maximum capacity of the pool (compile-time constant).
17///
18/// # Example
19///
20/// ```
21/// use laminar_core::alloc::ObjectPool;
22///
23/// // Create a pool of 16 buffers
24/// let mut pool: ObjectPool<Vec<u8>, 16> = ObjectPool::with_init(|| Vec::with_capacity(1024));
25///
26/// // Acquire from pool (O(1), no allocation)
27/// let mut buf = pool.acquire().expect("pool not empty");
28/// buf.extend_from_slice(b"hello");
29///
30/// // Return to pool (O(1), no allocation)
31/// buf.clear();
32/// pool.release(buf);
33/// ```
34///
35/// # Thread Safety
36///
37/// This pool is NOT thread-safe. Each core in a thread-per-core architecture
38/// should have its own pool.
39#[derive(Debug)]
40pub struct ObjectPool<T, const N: usize> {
41    /// Stack of available objects
42    free_list: ArrayVec<T, N>,
43    /// Number of objects currently in use
44    in_use: usize,
45}
46
47impl<T, const N: usize> ObjectPool<T, N> {
48    /// Create a new empty pool.
49    ///
50    /// Objects must be added via `release()` or use `with_init()` for pre-population.
51    #[must_use]
52    pub fn new() -> Self {
53        Self {
54            free_list: ArrayVec::new(),
55            in_use: 0,
56        }
57    }
58
59    /// Create a pool pre-populated with objects using a factory function.
60    ///
61    /// The factory is called `N` times to create the initial pool contents.
62    /// This allocation happens at startup, not on the hot path.
63    ///
64    /// # Arguments
65    ///
66    /// * `factory` - Function to create each object
67    ///
68    /// # Example
69    ///
70    /// ```
71    /// use laminar_core::alloc::ObjectPool;
72    ///
73    /// let pool: ObjectPool<Vec<u8>, 8> = ObjectPool::with_init(|| Vec::with_capacity(256));
74    /// assert_eq!(pool.available(), 8);
75    /// ```
76    pub fn with_init<F>(factory: F) -> Self
77    where
78        F: Fn() -> T,
79    {
80        let mut free_list = ArrayVec::new();
81        for _ in 0..N {
82            // This will always succeed since we iterate exactly N times
83            // and ArrayVec<T, N> has capacity N
84            let _ = free_list.try_push(factory());
85        }
86        Self {
87            free_list,
88            in_use: 0,
89        }
90    }
91
92    /// Acquire an object from the pool.
93    ///
94    /// Returns `None` if the pool is exhausted.
95    ///
96    /// # Performance
97    ///
98    /// O(1), no allocation.
99    ///
100    /// # Example
101    ///
102    /// ```
103    /// use laminar_core::alloc::ObjectPool;
104    ///
105    /// let mut pool: ObjectPool<u64, 4> = ObjectPool::with_init(|| 0);
106    /// let obj = pool.acquire().unwrap();
107    /// assert_eq!(pool.available(), 3);
108    /// ```
109    #[inline]
110    pub fn acquire(&mut self) -> Option<T> {
111        let obj = self.free_list.pop();
112        if obj.is_some() {
113            self.in_use += 1;
114        }
115        obj
116    }
117
118    /// Release an object back to the pool.
119    ///
120    /// If the pool is full, the object is dropped instead of stored.
121    ///
122    /// # Performance
123    ///
124    /// O(1), no allocation.
125    ///
126    /// # Returns
127    ///
128    /// `true` if the object was added to the pool, `false` if it was dropped.
129    ///
130    /// # Example
131    ///
132    /// ```
133    /// use laminar_core::alloc::ObjectPool;
134    ///
135    /// let mut pool: ObjectPool<u64, 4> = ObjectPool::new();
136    /// assert!(pool.release(42)); // Added to pool
137    /// assert_eq!(pool.available(), 1);
138    /// ```
139    #[inline]
140    pub fn release(&mut self, obj: T) -> bool {
141        if self.free_list.try_push(obj).is_ok() {
142            if self.in_use > 0 {
143                self.in_use -= 1;
144            }
145            true
146        } else {
147            // Pool is full, object is dropped
148            false
149        }
150    }
151
152    /// Get the number of available objects in the pool.
153    #[inline]
154    #[must_use]
155    pub fn available(&self) -> usize {
156        self.free_list.len()
157    }
158
159    /// Get the number of objects currently in use.
160    #[inline]
161    #[must_use]
162    pub fn in_use(&self) -> usize {
163        self.in_use
164    }
165
166    /// Get the maximum capacity of the pool.
167    #[inline]
168    #[must_use]
169    pub const fn capacity(&self) -> usize {
170        N
171    }
172
173    /// Check if the pool is empty (no objects available).
174    #[inline]
175    #[must_use]
176    pub fn is_empty(&self) -> bool {
177        self.free_list.is_empty()
178    }
179
180    /// Check if the pool is full (no more room for released objects).
181    #[inline]
182    #[must_use]
183    pub fn is_full(&self) -> bool {
184        self.free_list.is_full()
185    }
186
187    /// Clear all objects from the pool.
188    ///
189    /// Drops all pooled objects and resets counters.
190    pub fn clear(&mut self) {
191        self.free_list.clear();
192        self.in_use = 0;
193    }
194}
195
196impl<T: Default, const N: usize> Default for ObjectPool<T, N> {
197    /// Create a pool pre-populated with `N` default objects.
198    fn default() -> Self {
199        Self::with_init(T::default)
200    }
201}
202
203impl<T: Clone, const N: usize> Clone for ObjectPool<T, N> {
204    fn clone(&self) -> Self {
205        Self {
206            free_list: self.free_list.clone(),
207            in_use: self.in_use,
208        }
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_new_pool_empty() {
218        let pool: ObjectPool<u64, 10> = ObjectPool::new();
219        assert_eq!(pool.available(), 0);
220        assert_eq!(pool.capacity(), 10);
221        assert!(pool.is_empty());
222    }
223
224    #[test]
225    fn test_with_init() {
226        let pool: ObjectPool<u64, 10> = ObjectPool::with_init(|| 42);
227        assert_eq!(pool.available(), 10);
228        assert!(!pool.is_empty());
229    }
230
231    #[test]
232    fn test_acquire_release() {
233        let mut pool: ObjectPool<u64, 4> = ObjectPool::with_init(|| 0);
234
235        // Acquire all
236        let a = pool.acquire().unwrap();
237        let b = pool.acquire().unwrap();
238        let c = pool.acquire().unwrap();
239        let d = pool.acquire().unwrap();
240
241        assert_eq!(pool.available(), 0);
242        assert_eq!(pool.in_use(), 4);
243        assert!(pool.acquire().is_none()); // Pool exhausted
244
245        // Release some
246        pool.release(a);
247        pool.release(b);
248        assert_eq!(pool.available(), 2);
249        assert_eq!(pool.in_use(), 2);
250
251        // Release rest
252        pool.release(c);
253        pool.release(d);
254        assert_eq!(pool.available(), 4);
255        assert_eq!(pool.in_use(), 0);
256    }
257
258    #[test]
259    fn test_release_to_full_pool() {
260        let mut pool: ObjectPool<u64, 2> = ObjectPool::with_init(|| 0);
261
262        // Pool is already full
263        assert!(pool.is_full());
264
265        // Try to add another object - should be dropped
266        let added = pool.release(99);
267        assert!(!added);
268        assert_eq!(pool.available(), 2);
269    }
270
271    #[test]
272    fn test_default_trait() {
273        let pool: ObjectPool<u64, 5> = ObjectPool::default();
274        assert_eq!(pool.available(), 5);
275
276        // All should be 0 (u64::default())
277        let mut pool = pool;
278        for _ in 0..5 {
279            assert_eq!(pool.acquire(), Some(0));
280        }
281    }
282
283    #[test]
284    fn test_clear() {
285        let mut pool: ObjectPool<u64, 10> = ObjectPool::with_init(|| 42);
286        pool.acquire();
287        pool.acquire();
288
289        pool.clear();
290
291        assert_eq!(pool.available(), 0);
292        assert_eq!(pool.in_use(), 0);
293    }
294
295    #[test]
296    fn test_with_vec_buffers() {
297        // Common use case: pool of pre-sized vectors
298        let mut pool: ObjectPool<Vec<u8>, 4> = ObjectPool::with_init(|| Vec::with_capacity(1024));
299
300        let mut buf = pool.acquire().unwrap();
301        assert!(buf.capacity() >= 1024);
302
303        buf.extend_from_slice(b"test data");
304        buf.clear(); // Reset for reuse
305
306        pool.release(buf);
307        assert_eq!(pool.available(), 4);
308    }
309
310    #[test]
311    fn test_clone() {
312        let pool: ObjectPool<u64, 4> = ObjectPool::with_init(|| 42);
313        let cloned = pool.clone();
314
315        assert_eq!(cloned.available(), pool.available());
316        assert_eq!(cloned.capacity(), pool.capacity());
317    }
318}