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