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}