//! Object pool API //! //! # Example usage //! //! ``` //! use heapless::{object_pool, pool::object::{Object, ObjectBlock}}; //! //! object_pool!(P: [u8; 128]); //! //! // cannot request objects without first giving object blocks to the pool //! assert!(P.request().is_none()); //! //! // (some `no_std` runtimes have safe APIs to create `&'static mut` references) //! let block: &'static mut ObjectBlock<[u8; 128]> = unsafe { //! // unlike the memory pool APIs, an initial value must be specified here //! static mut B: ObjectBlock<[u8; 128]>= ObjectBlock::new([0; 128]); //! &mut B //! }; //! //! // give object block to the pool //! P.manage(block); //! //! // it's now possible to request objects //! // unlike the memory pool APIs, no initial value is required here //! let mut object = P.request().unwrap(); //! //! // mutation is possible //! object.iter_mut().for_each(|byte| *byte = byte.wrapping_add(1)); //! //! // the number of live objects is limited to the number of blocks managed by the pool //! let res = P.request(); //! assert!(res.is_none()); //! //! // `object`'s destructor returns the object to the pool //! drop(object); //! //! // it's possible to request an `Object` again //! let res = P.request(); //! //! assert!(res.is_some()); //! ``` //! //! # Array block initialization //! //! You can create a static variable that contains an array of memory blocks and give all the blocks //! to the `ObjectPool`. This requires an intermediate `const` value as shown below: //! //! ``` //! use heapless::{object_pool, pool::object::ObjectBlock}; //! //! object_pool!(P: [u8; 128]); //! //! const POOL_CAPACITY: usize = 8; //! //! let blocks: &'static mut [ObjectBlock<[u8; 128]>] = { //! const BLOCK: ObjectBlock<[u8; 128]> = ObjectBlock::new([0; 128]); // <= //! static mut BLOCKS: [ObjectBlock<[u8; 128]>; POOL_CAPACITY] = [BLOCK; POOL_CAPACITY]; //! unsafe { &mut BLOCKS } //! }; //! //! for block in blocks { //! P.manage(block); //! } //! ``` use core::{ cmp::Ordering, fmt, hash::{Hash, Hasher}, mem::ManuallyDrop, ops, ptr, }; use stable_deref_trait::StableDeref; use super::treiber::{AtomicPtr, NonNullPtr, Stack, StructNode}; /// Creates a new `ObjectPool` singleton with the given `$name` that manages the specified /// `$data_type` /// /// For more extensive documentation see the [module level documentation](crate::pool::object) #[macro_export] macro_rules! object_pool { ($name:ident: $data_type:ty) => { pub struct $name; impl $crate::pool::object::ObjectPool for $name { type Data = $data_type; fn singleton() -> &'static $crate::pool::object::ObjectPoolImpl<$data_type> { static $name: $crate::pool::object::ObjectPoolImpl<$data_type> = $crate::pool::object::ObjectPoolImpl::new(); &$name } } impl $name { /// Inherent method version of `ObjectPool::request` #[allow(dead_code)] pub fn request(&self) -> Option<$crate::pool::object::Object<$name>> { <$name as $crate::pool::object::ObjectPool>::request() } /// Inherent method version of `ObjectPool::manage` #[allow(dead_code)] pub fn manage( &self, block: &'static mut $crate::pool::object::ObjectBlock<$data_type>, ) { <$name as $crate::pool::object::ObjectPool>::manage(block) } } }; } /// A singleton that manages `pool::object::Object`s pub trait ObjectPool: Sized { /// The data type of the objects managed by the object pool type Data: 'static; /// `object_pool!` implementation detail #[doc(hidden)] fn singleton() -> &'static ObjectPoolImpl; /// Request a new object from the pool fn request() -> Option> { Self::singleton() .request() .map(|node_ptr| Object { node_ptr }) } /// Adds a statically allocate object to the pool fn manage(block: &'static mut ObjectBlock) { Self::singleton().manage(block) } } /// `object_pool!` implementation detail #[doc(hidden)] pub struct ObjectPoolImpl { stack: Stack>, } impl ObjectPoolImpl { /// `object_pool!` implementation detail #[doc(hidden)] pub const fn new() -> Self { Self { stack: Stack::new(), } } fn request(&self) -> Option>> { self.stack.try_pop() } fn manage(&self, block: &'static mut ObjectBlock) { let node: &'static mut _ = &mut block.node; unsafe { self.stack.push(NonNullPtr::from_static_mut_ref(node)) } } } // `T needs` to be Send because returning an object from a thread and then // requesting it from another is effectively a cross-thread 'send' operation unsafe impl Sync for ObjectPoolImpl where T: Send {} /// An object managed by object pool `P` pub struct Object

where P: ObjectPool, { node_ptr: NonNullPtr>, } impl AsMut<[T]> for Object where A: ObjectPool, { fn as_mut(&mut self) -> &mut [T] { &mut **self } } impl AsRef<[T]> for Object where A: ObjectPool, { fn as_ref(&self) -> &[T] { &**self } } impl fmt::Debug for Object where A: ObjectPool, A::Data: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { A::Data::fmt(self, f) } } impl ops::Deref for Object where A: ObjectPool, { type Target = A::Data; fn deref(&self) -> &Self::Target { unsafe { &*ptr::addr_of!((*self.node_ptr.as_ptr()).data) } } } impl ops::DerefMut for Object where A: ObjectPool, { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { &mut *ptr::addr_of_mut!((*self.node_ptr.as_ptr()).data) } } } unsafe impl StableDeref for Object where A: ObjectPool {} impl fmt::Display for Object where A: ObjectPool, A::Data: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { A::Data::fmt(self, f) } } impl

Drop for Object

where P: ObjectPool, { fn drop(&mut self) { unsafe { P::singleton().stack.push(self.node_ptr) } } } impl Eq for Object where A: ObjectPool, A::Data: Eq, { } impl Hash for Object where A: ObjectPool, A::Data: Hash, { fn hash(&self, state: &mut H) where H: Hasher, { (**self).hash(state) } } impl Ord for Object where A: ObjectPool, A::Data: Ord, { fn cmp(&self, other: &Self) -> Ordering { A::Data::cmp(self, other) } } impl PartialEq> for Object where A: ObjectPool, B: ObjectPool, A::Data: PartialEq, { fn eq(&self, other: &Object) -> bool { A::Data::eq(self, other) } } impl PartialOrd> for Object where A: ObjectPool, B: ObjectPool, A::Data: PartialOrd, { fn partial_cmp(&self, other: &Object) -> Option { A::Data::partial_cmp(self, other) } } unsafe impl

Send for Object

where P: ObjectPool, P::Data: Send, { } unsafe impl

Sync for Object

where P: ObjectPool, P::Data: Sync, { } /// An object "block" of data type `T` that has not yet been associated to an `ObjectPool` pub struct ObjectBlock { node: StructNode, } impl ObjectBlock { /// Creates a new object block with the given `initial_value` pub const fn new(initial_value: T) -> Self { Self { node: StructNode { next: ManuallyDrop::new(AtomicPtr::null()), data: ManuallyDrop::new(initial_value), }, } } } #[cfg(test)] mod tests { use core::sync::atomic::{self, AtomicUsize}; use super::*; #[test] fn cannot_request_if_empty() { object_pool!(P: i32); assert_eq!(None, P.request()); } #[test] fn can_request_if_manages_one_block() { object_pool!(P: i32); let block = unsafe { static mut B: ObjectBlock = ObjectBlock::new(1); &mut B }; P.manage(block); assert_eq!(1, *P.request().unwrap()); } #[test] fn request_drop_request() { object_pool!(P: i32); let block = unsafe { static mut B: ObjectBlock = ObjectBlock::new(1); &mut B }; P.manage(block); let mut object = P.request().unwrap(); *object = 2; drop(object); assert_eq!(2, *P.request().unwrap()); } #[test] fn destructor_does_not_run_on_drop() { static COUNT: AtomicUsize = AtomicUsize::new(0); pub struct S; impl Drop for S { fn drop(&mut self) { COUNT.fetch_add(1, atomic::Ordering::Relaxed); } } object_pool!(P: S); let block = unsafe { static mut B: ObjectBlock = ObjectBlock::new(S); &mut B }; P.manage(block); let object = P.request().unwrap(); assert_eq!(0, COUNT.load(atomic::Ordering::Relaxed)); drop(object); assert_eq!(0, COUNT.load(atomic::Ordering::Relaxed)); } #[test] fn zst_is_well_aligned() { #[repr(align(4096))] pub struct Zst4096; object_pool!(P: Zst4096); let block = unsafe { static mut B: ObjectBlock = ObjectBlock::new(Zst4096); &mut B }; P.manage(block); let object = P.request().unwrap(); let raw = &*object as *const Zst4096; assert_eq!(0, raw as usize % 4096); } }