saluki_core/pooling/
helpers.rs

1//! Helpers for creating and working with poolable objects.
2
3use std::{marker::PhantomData, sync::Arc};
4
5use super::{Poolable, ReclaimStrategy};
6
7/// Creates a struct that can be stored in an object pool, based on an inline struct definition.
8///
9/// In order to store a value in an object pool, the item must implement the [`Poolable`] trait. This trait, and overall
10/// design of [`ObjectPool`][super::ObjectPool], dictates that a pooled data type actually holds an inner value, which
11/// is the value that is actually pooled, while the outer struct is simply a wrapper around the data that ensures it is
12/// returned to the object pool when no longer in use.
13///
14/// In practice, this means that if you wanted to create some struct that could be pooled (for example,
15/// `SimpleBuffer`), you would need to create that struct and bake in all of the boilerplate logic and
16/// implementations for `Poolable`/`Clearable`. Instead, `pooled!` can be used to define the desired struct inline,
17/// while the wrapper type that contains the relevant pooling logic and trait implementations is generated
18/// automatically.
19///
20/// ## Limitations
21///
22/// This macro is most appropriate when the desired struct is simple, such as only requiring control over the fields and
23/// not the presence of any methods or trait implementations. If more control is required, consider using
24/// [`pooled_newtype!`] which can wrap over an existing struct defined outside of the macro.
25///
26/// ## Clearing
27///
28/// All poolable types must provide logic for "clearing" the pooled item before it is returned to the object pool.
29/// This is passed in as the `clear` parameter to the macro, and must be a closure that takes a mutable reference to
30/// the inner struct.
31///
32/// Note that due to macro hygiene, the closure's only parameter _cannot_ be named `self`. In the usage example below, you can
33/// see how this is named `this` instead to avoid conflicts.
34///
35/// ## Usage
36///
37/// ```rust
38/// use saluki_core::pooling::helpers::pooled;
39///
40/// pooled! {
41///    /// A simple Poolable struct.
42///   struct SimpleBuffer {
43///      value: u32,
44///   }
45///
46///   clear => |this| this.value = 0
47/// }
48///
49/// // This creates a new struct called `SimpleBufferInner` based on the definition of `SimpleBuffer`,
50/// // and `SimpleBuffer` contains the necessary logic/pointers to be stored in an object pool.
51/// //
52/// // Two helper methods are provided on the wrapper struct (`SimpleBuffer`, in this case), for accessing
53/// // the inner data: `data` and `data_mut`. We can see them in use below:
54/// impl SimpleBuffer {
55///     pub fn value(&self) -> u32 {
56///         self.data().value
57///     }
58///
59///     pub fn multiply_by_two(&mut self) {
60///         self.data_mut().value *= 2;
61///     }
62/// }
63///
64/// fn use_simple_buffer(mut buf: SimpleBuffer) {
65///     let original_value = buf.value();
66///     buf.multiply_by_two();
67///
68///     let doubled_value = buf.value();
69///     assert_eq!(doubled_value, original_value * 2);
70/// }
71/// ```
72#[macro_export]
73macro_rules! pooled {
74    ($(#[$outer:meta])* struct $name:ident {
75        $($field_name:ident: $field_type:ty,)*
76    }$(,)?
77    clear => $clear:expr) => {
78        $crate::reexport::paste! {
79            #[doc = "Inner representation of " $name "."]
80            #[derive(Default)]
81            pub struct [<$name Inner>] {
82                $($field_name: $field_type,)*
83            }
84
85            impl $crate::pooling::Clearable for [<$name Inner>] {
86                fn clear(&mut self) {
87                    let clear_fn: &dyn Fn(&mut Self) = &$clear;
88                    clear_fn(self)
89                }
90            }
91
92            $(#[$outer])*
93            pub struct $name {
94                strategy_ref: ::std::sync::Arc<dyn $crate::pooling::ReclaimStrategy<$name> + Send + Sync>,
95                data: ::std::mem::ManuallyDrop<[<$name Inner>]>,
96            }
97
98            impl $name {
99                /// Gets a reference to the inner data.
100                #[allow(dead_code)]
101                pub fn data(&self) -> &[<$name Inner>] {
102                    &self.data
103                }
104
105                /// Gets a mutable reference to the inner data.
106                #[allow(dead_code)]
107                pub fn data_mut(&mut self) -> &mut [<$name Inner>] {
108                    &mut self.data
109                }
110            }
111
112            impl $crate::pooling::Poolable for $name {
113                type Data = [<$name Inner>];
114
115                fn from_data(strategy_ref: ::std::sync::Arc<dyn $crate::pooling::ReclaimStrategy<Self> + Send + Sync>, data: Self::Data) -> Self {
116                    Self {
117                        strategy_ref,
118                        data: ::std::mem::ManuallyDrop::new(data),
119                    }
120                }
121            }
122        }
123
124        impl Drop for $name {
125            fn drop(&mut self) {
126                // SAFETY: We never use `self.data` again since we're already dropping `self`.
127                let data = unsafe { ::std::mem::ManuallyDrop::take(&mut self.data) };
128                self.strategy_ref.reclaim(data);
129            }
130        }
131    }
132}
133
134/// Creates a struct that can be stored in an object pool, based on an existing struct definition.
135///
136/// In order to store a value in an object pool, the item must implement the [`Poolable`] trait. This trait, and overall
137/// design of [`ObjectPool`][super::ObjectPool], dictates that a pooled data type actually holds an inner value, which
138/// is the value that is actually pooled, while the outer struct is simply a wrapper around the data that ensures it is
139/// returned to the object pool when no longer in use.
140///
141/// In many cases, the "inner value" might be either an existing type that cannot be modified, or there might be a need
142/// to define that struct further, such as defining struct methods or trait implementations which would be
143/// cumbersome/confusing to do on the auto-generated inner type from [`pooled!`]. In these cases, `pooled_newtype!`
144/// provides the simplest possible wrapper over an existing struct definition to create a Poolable version.
145///
146/// Implementors are required to define their data struct, including an implementation of
147/// [`Clearable`][super::Clearable], and then use `pooled_newtype!` to wrap it.
148///
149/// ## Usage
150///
151/// ```rust
152/// use saluki_core::{pooled_newtype, pooling::Clearable};
153///
154/// pub struct PreallocatedByteBuffer {
155///     data: Vec<u8>,
156/// }
157///
158/// impl PreallocatedByteBuffer {
159///     pub fn new() -> Self {
160///         Self {
161///             data: Vec::with_capacity(1024)
162///         }
163///     }
164/// }
165///
166/// impl Clearable for PreallocatedByteBuffer {
167///    fn clear(&mut self) {
168///       self.data.clear();
169///    }
170/// }
171///
172/// pooled_newtype! {
173///    outer => ByteBuffer,
174///    inner => PreallocatedByteBuffer,
175/// }
176///
177/// // This creates a new struct called `ByteBuffer` which simply wraps over `PreallocatedByteBuffer`. We can
178/// // define some helper methods on `ByteBuffer` to make it easier to work with:
179/// impl ByteBuffer {
180///     pub fn len(&self) -> usize {
181///         self.data().data.len()
182///     }
183///
184///     pub fn write_buf(&mut self, buf: &[u8]) {
185///         self.data_mut().data.extend_from_slice(buf);
186///     }
187/// }
188///
189/// fn use_byte_buffer(mut buf: ByteBuffer) {
190///     assert_eq!(buf.len(), 0);
191///
192///     buf.write_buf(b"Hello, world!");
193///     assert_eq!(buf.len(), 13);
194/// }
195/// ```
196#[macro_export]
197macro_rules! pooled_newtype {
198    (outer => $name:ident, inner => $inner_ty:ty $(,)?) => {
199        $crate::reexport::paste! {
200            #[doc = "Poolable version of `" $inner_ty "`."]
201            pub struct $name {
202                strategy_ref: ::std::sync::Arc<dyn $crate::pooling::ReclaimStrategy<$name> + Send + Sync>,
203                data: ::std::option::Option<$inner_ty>,
204            }
205        }
206
207        impl $name {
208            /// Gets a reference to the inner data.
209            #[allow(dead_code)]
210            pub fn data(&self) -> &$inner_ty {
211                self.data.as_ref().unwrap()
212            }
213
214            /// Gets a mutable reference to the inner data.
215            #[allow(dead_code)]
216            pub fn data_mut(&mut self) -> &mut $inner_ty {
217                self.data.as_mut().unwrap()
218            }
219        }
220
221        impl $crate::pooling::Poolable for $name {
222            type Data = $inner_ty;
223
224            fn from_data(
225                strategy_ref: ::std::sync::Arc<dyn $crate::pooling::ReclaimStrategy<Self> + Send + Sync>,
226                data: Self::Data,
227            ) -> Self {
228                Self {
229                    strategy_ref,
230                    data: ::std::option::Option::Some(data),
231                }
232            }
233        }
234
235        impl Drop for $name {
236            fn drop(&mut self) {
237                // SAFETY: We never use `self.data` again since we're already dropping `self`.
238
239                if let ::std::option::Option::Some(data) = self.data.take() {
240                    self.strategy_ref.reclaim(data);
241                }
242            }
243        }
244    };
245}
246
247pub use pooled;
248pub use pooled_newtype;
249
250/// An object pool strategy that performs no pooling.
251struct NoopStrategy<T> {
252    _t: PhantomData<T>,
253}
254
255impl<T> NoopStrategy<T> {
256    const fn new() -> Self {
257        Self { _t: PhantomData }
258    }
259}
260
261impl<T> ReclaimStrategy<T> for NoopStrategy<T>
262where
263    T: Poolable,
264{
265    fn reclaim(&self, _: T::Data) {}
266}
267
268/// Creates an poolable object (of type `T`) when `T::Data` implements `Default`.
269#[allow(dead_code)]
270pub fn get_pooled_object_via_default<T>() -> T
271where
272    T: Poolable + Send + Sync + 'static,
273    T::Data: Default + Sync,
274{
275    T::from_data(Arc::new(NoopStrategy::<_>::new()), T::Data::default())
276}
277
278/// Creates an poolable object (of type `T`) when `T::Data` implements `Default`.
279#[allow(dead_code)]
280pub fn get_pooled_object_via_builder<F, T>(f: F) -> T
281where
282    F: FnOnce() -> T::Data,
283    T: Poolable + Send + Sync + 'static,
284    T::Data: Sync,
285{
286    T::from_data(Arc::new(NoopStrategy::<_>::new()), f())
287}