1use core::cell::{RefCell, UnsafeCell};
14use core::ffi::c_void;
15use core::fmt;
16use core::future::Future;
17use core::ops::{Deref, DerefMut};
18use core::pin::Pin;
19use core::sync::atomic::{AtomicBool, Ordering};
20use core::task::{Context, Poll, Waker};
21
22use crate::memory::SystemSlice;
23
24#[derive(Debug)]
25pub struct StaticBufferPool<T, const N: usize> {
26 inner: RefCell<Inner<T, N>>,
27}
28
29#[derive(Debug)]
30struct Inner<T, const N: usize> {
31 slots: [Option<T>; N],
32 waker: Option<Waker>,
33}
34
35impl<T, const N: usize> StaticBufferPool<T, N> {
36 pub fn new(buffers: [T; N]) -> Self {
38 Self {
39 inner: RefCell::new(Inner {
40 slots: buffers.map(Some),
41 waker: None,
42 }),
43 }
44 }
45
46 pub fn capacity(&self) -> usize {
48 N
49 }
50
51 pub fn available(&self) -> usize {
53 self.inner
54 .borrow()
55 .slots
56 .iter()
57 .filter(|s| s.is_some())
58 .count()
59 }
60
61 pub fn outstanding(&self) -> usize {
63 N - self.available()
64 }
65
66 pub fn try_acquire(&self) -> Option<StaticPooled<'_, T, N>> {
68 let mut inner = self.inner.borrow_mut();
69 for slot in inner.slots.iter_mut() {
70 if let Some(value) = slot.take() {
71 return Some(StaticPooled {
72 pool: self,
73 value: Some(value),
74 });
75 }
76 }
77 None
78 }
79
80 pub fn acquire(&self) -> StaticAcquire<'_, T, N> {
84 StaticAcquire { pool: self }
85 }
86
87 fn release(&self, value: T) {
89 let waker = {
92 let mut inner = self.inner.borrow_mut();
93 for slot in inner.slots.iter_mut() {
94 if slot.is_none() {
95 *slot = Some(value);
96 break;
97 }
98 }
99 inner.waker.take()
100 };
101 if let Some(w) = waker {
102 w.wake();
103 }
104 }
105}
106
107#[derive(Debug)]
108pub struct StaticPooled<'a, T, const N: usize> {
109 pool: &'a StaticBufferPool<T, N>,
110 value: Option<T>,
111}
112
113impl<T, const N: usize> Deref for StaticPooled<'_, T, N> {
114 type Target = T;
115 fn deref(&self) -> &T {
116 self.value
117 .as_ref()
118 .expect("StaticPooled accessed after drop")
119 }
120}
121
122impl<T, const N: usize> DerefMut for StaticPooled<'_, T, N> {
123 fn deref_mut(&mut self) -> &mut T {
124 self.value
125 .as_mut()
126 .expect("StaticPooled accessed after drop")
127 }
128}
129
130impl<T: AsRef<[u8]>, const N: usize> AsRef<[u8]> for StaticPooled<'_, T, N> {
131 fn as_ref(&self) -> &[u8] {
132 self.deref().as_ref()
133 }
134}
135
136impl<T: AsMut<[u8]>, const N: usize> AsMut<[u8]> for StaticPooled<'_, T, N> {
137 fn as_mut(&mut self) -> &mut [u8] {
138 self.deref_mut().as_mut()
139 }
140}
141
142impl<T, const N: usize> Drop for StaticPooled<'_, T, N> {
143 fn drop(&mut self) {
144 if let Some(v) = self.value.take() {
145 self.pool.release(v);
146 }
147 }
148}
149
150#[allow(missing_debug_implementations)]
151pub struct StaticAcquire<'a, T, const N: usize> {
152 pool: &'a StaticBufferPool<T, N>,
153}
154
155impl<'a, T, const N: usize> Future for StaticAcquire<'a, T, N> {
156 type Output = StaticPooled<'a, T, N>;
157
158 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
159 match self.pool.try_acquire() {
160 Some(buf) => Poll::Ready(buf),
161 None => {
162 self.pool.inner.borrow_mut().waker = Some(cx.waker().clone());
163 Poll::Pending
164 }
165 }
166 }
167}
168
169pub struct StaticLendRing<const N: usize, const BYTES: usize> {
186 slots: [UnsafeCell<[u8; BYTES]>; N],
187 leased: [AtomicBool; N],
188}
189
190unsafe impl<const N: usize, const BYTES: usize> Sync for StaticLendRing<N, BYTES> {}
201
202impl<const N: usize, const BYTES: usize> StaticLendRing<N, BYTES> {
203 #[allow(clippy::declare_interior_mutable_const)]
208 const EMPTY_SLOT: UnsafeCell<[u8; BYTES]> = UnsafeCell::new([0u8; BYTES]);
209 #[allow(clippy::declare_interior_mutable_const)]
210 const UNLEASED: AtomicBool = AtomicBool::new(false);
211
212 pub const fn new() -> Self {
218 Self {
219 slots: [Self::EMPTY_SLOT; N],
220 leased: [Self::UNLEASED; N],
221 }
222 }
223
224 pub const fn capacity(&self) -> usize {
226 N
227 }
228
229 pub const fn slot_bytes(&self) -> usize {
231 BYTES
232 }
233
234 pub fn leased_count(&self) -> usize {
236 self.leased
237 .iter()
238 .filter(|f| f.load(Ordering::Acquire))
239 .count()
240 }
241
242 pub fn acquire(&self) -> Option<RingSlot<'_, N, BYTES>> {
246 for idx in 0..N {
252 if !self.leased[idx].load(Ordering::Acquire) {
253 self.leased[idx].store(true, Ordering::Release);
254 return Some(RingSlot {
255 slot: &self.slots[idx],
256 lease: &self.leased[idx],
257 });
258 }
259 }
260 None
261 }
262
263 pub fn contains(&self, ptr: *const u8) -> bool {
266 let p = ptr as usize;
267 self.slots.iter().any(|s| {
268 let base = s.get() as usize;
269 p >= base && p < base + BYTES
270 })
271 }
272}
273
274impl<const N: usize, const BYTES: usize> Default for StaticLendRing<N, BYTES> {
275 fn default() -> Self {
276 Self::new()
277 }
278}
279
280impl<const N: usize, const BYTES: usize> fmt::Debug for StaticLendRing<N, BYTES> {
281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282 f.debug_struct("StaticLendRing")
283 .field("capacity", &N)
284 .field("slot_bytes", &BYTES)
285 .field("leased", &self.leased_count())
286 .finish()
287 }
288}
289
290pub struct RingSlot<'r, const N: usize, const BYTES: usize> {
295 slot: &'r UnsafeCell<[u8; BYTES]>,
296 lease: &'r AtomicBool,
297}
298
299impl<const N: usize, const BYTES: usize> RingSlot<'_, N, BYTES> {
300 pub fn buf_mut(&mut self) -> &mut [u8] {
302 let arr: &mut [u8; BYTES] = unsafe { &mut *self.slot.get() };
305 arr.as_mut_slice()
306 }
307
308 pub unsafe fn publish(self, len: usize) -> SystemSlice {
317 debug_assert!(len <= BYTES, "published len exceeds slot capacity");
318 let ptr = self.slot.get() as *const u8;
319 let flag = self.lease as *const AtomicBool as *mut c_void;
320 core::mem::forget(self);
323 unsafe { SystemSlice::from_foreign(ptr, len, Some(release_slot), flag) }
327 }
328}
329
330impl<const N: usize, const BYTES: usize> fmt::Debug for RingSlot<'_, N, BYTES> {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 f.debug_struct("RingSlot")
333 .field("slot", &self.slot.get())
334 .finish()
335 }
336}
337
338impl<const N: usize, const BYTES: usize> Drop for RingSlot<'_, N, BYTES> {
339 fn drop(&mut self) {
340 self.lease.store(false, Ordering::Release);
342 }
343}
344
345unsafe extern "C" fn release_slot(user: *mut c_void) {
348 unsafe { (*(user as *const AtomicBool)).store(false, Ordering::Release) };
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use core::task::{RawWaker, RawWakerVTable};
357
358 fn noop_waker() -> Waker {
359 fn clone(_: *const ()) -> RawWaker {
360 RawWaker::new(core::ptr::null(), &VTABLE)
361 }
362 fn no_op(_: *const ()) {}
363 static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, no_op, no_op, no_op);
364 unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }
367 }
368
369 fn poll_once<F: Future + Unpin>(fut: &mut F) -> Poll<F::Output> {
370 let waker = noop_waker();
371 let mut cx = Context::from_waker(&waker);
372 Pin::new(fut).poll(&mut cx)
373 }
374
375 #[test]
376 fn capacity_and_available_match_on_construction() {
377 let pool: StaticBufferPool<[u8; 4], 3> = StaticBufferPool::new([[0u8; 4]; 3]);
378 assert_eq!(pool.capacity(), 3);
379 assert_eq!(pool.available(), 3);
380 assert_eq!(pool.outstanding(), 0);
381 }
382
383 #[test]
384 fn acquire_decrements_available_drop_returns() {
385 let pool: StaticBufferPool<[u8; 4], 3> = StaticBufferPool::new([[0u8; 4]; 3]);
386 {
387 let _a = pool.try_acquire().expect("a");
388 let _b = pool.try_acquire().expect("b");
389 assert_eq!(pool.available(), 1);
390 assert_eq!(pool.outstanding(), 2);
391 }
392 assert_eq!(pool.available(), 3, "dropping handles returns buffers");
393 }
394
395 #[test]
396 fn exhausted_pool_returns_none() {
397 let pool: StaticBufferPool<u32, 2> = StaticBufferPool::new([0; 2]);
398 let _a = pool.try_acquire().unwrap();
399 let _b = pool.try_acquire().unwrap();
400 assert!(pool.try_acquire().is_none());
401 }
402
403 #[test]
404 fn handle_derefs_to_buffer_and_writes_through() {
405 let pool: StaticBufferPool<[u8; 4], 1> = StaticBufferPool::new([[0u8; 4]; 1]);
406 let mut buf = pool.try_acquire().unwrap();
407 buf[0] = 0xAB;
408 assert_eq!(buf.as_ref(), &[0xAB, 0, 0, 0]);
409 }
410
411 #[test]
412 fn async_acquire_parks_then_resolves_when_a_buffer_is_freed() {
413 let pool: StaticBufferPool<u32, 1> = StaticBufferPool::new([7; 1]);
414 let held = pool.try_acquire().unwrap();
415 let mut fut = pool.acquire();
417 assert!(matches!(poll_once(&mut fut), Poll::Pending));
418 drop(held);
420 let Poll::Ready(buf) = poll_once(&mut fut) else {
421 panic!("acquire must resolve once a buffer is free");
422 };
423 assert_eq!(pool.available(), 0, "the resolved acquire holds the buffer");
424 drop(buf);
425 assert_eq!(pool.available(), 1, "dropping it returns the buffer");
426 }
427
428 #[test]
431 fn lend_ring_publish_borrows_slot_and_drop_reclaims() {
432 let ring: StaticLendRing<2, 8> = StaticLendRing::new();
433 assert_eq!(
434 (ring.capacity(), ring.slot_bytes(), ring.leased_count()),
435 (2, 8, 0)
436 );
437
438 let mut slot = ring.acquire().expect("free slot");
439 slot.buf_mut()[..3].copy_from_slice(&[1, 2, 3]);
440 assert_eq!(ring.leased_count(), 1, "acquire leases the slot");
441 let frame = unsafe { slot.publish(3) };
443 assert_eq!(
444 ring.leased_count(),
445 1,
446 "publish keeps the lease until the frame drops"
447 );
448 assert_eq!(frame.as_slice(), &[1, 2, 3]);
450 assert!(
451 ring.contains(frame.as_slice().as_ptr()),
452 "frame bytes live in the ring"
453 );
454
455 drop(frame);
456 assert_eq!(
457 ring.leased_count(),
458 0,
459 "dropping the frame reclaims the slot"
460 );
461 }
462
463 #[test]
464 fn lend_ring_acquired_but_unpublished_slot_is_released_on_drop() {
465 let ring: StaticLendRing<1, 4> = StaticLendRing::new();
466 {
467 let _slot = ring.acquire().expect("free slot");
468 assert!(
469 ring.acquire().is_none(),
470 "ring full while the lease is held"
471 );
472 }
473 assert_eq!(
474 ring.leased_count(),
475 0,
476 "dropping an unpublished lease frees the slot"
477 );
478 assert!(ring.acquire().is_some(), "slot reusable again");
479 }
480
481 #[test]
482 fn lend_ring_full_when_all_slots_in_flight_then_recycles() {
483 let ring: StaticLendRing<2, 4> = StaticLendRing::new();
484 let f0 = unsafe { ring.acquire().unwrap().publish(1) };
487 let f1 = unsafe { ring.acquire().unwrap().publish(1) };
489 let p0 = f0.as_slice().as_ptr();
490 assert!(
491 ring.acquire().is_none(),
492 "both slots lent: ring is full (back-pressure)"
493 );
494
495 drop(f0); let f2 = unsafe { ring.acquire().expect("slot freed by the drop").publish(1) };
498 assert_eq!(
500 f2.as_slice().as_ptr(),
501 p0,
502 "the freed slot's buffer is recycled"
503 );
504 drop(f1);
505 drop(f2);
506 assert_eq!(ring.leased_count(), 0);
507 }
508}