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}