use crate::utils::box_ext::BoxExt;
use crate::utils::reset::Reset;
use crate::utils::stack::Stack;
use derivative::Derivative;
use std::mem;
use std::mem::ManuallyDrop;
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::ops::DerefMut;
use std::rc::Rc;
pub trait BoxCacheMethod<T> {
type Cached;
fn into_cached(boxed: Box<T>) -> Box<Self::Cached>;
}
pub struct BoxUninit;
impl<T> BoxCacheMethod<T> for BoxUninit {
type Cached = MaybeUninit<T>;
fn into_cached(boxed: Box<T>) -> Box<Self::Cached> {
Box::into_uninit(boxed)
}
}
pub struct BoxReset;
impl<T> BoxCacheMethod<T> for BoxReset
where
T: Reset,
{
type Cached = T;
fn into_cached(mut boxed: Box<T>) -> Box<Self::Cached> {
boxed.reset();
boxed
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct BoxCache<T, M>
where
M: BoxCacheMethod<T>,
{
boxes: Stack<Box<M::Cached>>,
}
pub struct CachedBox<T, M>
where
M: BoxCacheMethod<T>,
{
cache: Rc<BoxCache<T, M>>,
boxed: ManuallyDrop<Box<T>>,
}
impl<T, M> Deref for CachedBox<T, M>
where
M: BoxCacheMethod<T>,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.boxed
}
}
impl<T, M> DerefMut for CachedBox<T, M>
where
M: BoxCacheMethod<T>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.boxed
}
}
impl<T> BoxCache<T, BoxUninit> {
pub fn get(self: &Rc<Self>, unboxed: T) -> CachedBox<T, BoxUninit> {
let c = self
.boxes
.pop()
.unwrap_or_else(|| Box::new(MaybeUninit::uninit()));
let c = Box::write(c, unboxed);
CachedBox {
cache: self.clone(),
boxed: ManuallyDrop::new(c),
}
}
}
impl<T> BoxCache<T, BoxReset>
where
T: Reset + Default,
{
pub fn get(self: &Rc<Self>) -> CachedBox<T, BoxReset> {
let c = self.boxes.pop().unwrap_or_default();
CachedBox {
cache: self.clone(),
boxed: ManuallyDrop::new(c),
}
}
}
impl<T> CachedBox<T, BoxReset>
where
T: Reset + Default,
{
pub fn take(self: &mut CachedBox<T, BoxReset>) -> CachedBox<T, BoxReset> {
mem::replace(self, self.cache.get())
}
}
impl<T, M> Drop for CachedBox<T, M>
where
M: BoxCacheMethod<T>,
{
fn drop(&mut self) {
let boxed = unsafe { ManuallyDrop::take(&mut self.boxed) };
let boxed = M::into_cached(boxed);
self.cache.boxes.push(boxed);
}
}