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}