#![doc = include_str!("../README.md")]
use core::{marker::PhantomData, mem, ptr};
use std::{cell::RefCell, collections::HashMap};
use bumpalo::Bump;
use parking_lot::RwLock;
use type_key::TypeKey;
#[derive(Debug)]
pub struct RawFnStore<'a> {
map: HashMap<TypeKey, ManuallyDealloc>,
bump: Bump,
_phantom: PhantomData<&'a ()>,
}
impl<'a> RawFnStore<'a> {
pub fn new() -> Self {
Self {
map: HashMap::new(),
bump: Bump::new(),
_phantom: PhantomData,
}
}
pub fn get_ptr<T: 'a>(&self, key: &impl FnOnce() -> T) -> Option<*const T> {
Some(self.map.get(&TypeKey::of_val(key))?.ptr().cast::<T>())
}
pub fn insert_ptr<F: FnOnce() -> T, T: 'a>(&mut self, value: T) -> *const T {
let value = unsafe { ManuallyDealloc::new(self.bump.alloc(value)) };
let ptr = value.ptr();
self.map.insert(TypeKey::of::<F>(), value);
ptr.cast::<T>()
}
pub fn reset(&mut self) {
self.map.clear();
self.bump.reset();
}
}
impl Default for RawFnStore<'_> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct LocalFnStore<'a>(RefCell<RawFnStore<'a>>);
impl<'a> LocalFnStore<'a> {
pub fn new() -> Self {
Self(RefCell::new(RawFnStore::new()))
}
pub fn get<T: 'a + Send, F: FnOnce() -> T>(&self, key: F) -> &T {
if let Some(ptr) = self.0.borrow().get_ptr(&key) {
return unsafe { &*ptr };
}
let value = (key)();
let ptr = self.0.borrow_mut().insert_ptr::<F, T>(value);
unsafe { &*ptr }
}
pub fn reset(&mut self) {
self.0.get_mut().reset();
}
}
impl Default for LocalFnStore<'_> {
fn default() -> Self {
Self::new()
}
}
unsafe impl Send for LocalFnStore<'_> {}
#[derive(Debug)]
pub struct AtomicFnStore<'a>(RwLock<RawFnStore<'a>>);
impl<'a> AtomicFnStore<'a> {
pub fn new() -> Self {
Self(RwLock::new(RawFnStore::new()))
}
pub fn get<T: 'a + Send + Sync, F: FnOnce() -> T>(&self, key: F) -> &T {
if let Some(ptr) = self.0.read().get_ptr(&key) {
return unsafe { &*ptr };
}
let value = (key)();
let ptr = self.0.write().insert_ptr::<F, T>(value);
unsafe { &*ptr }
}
pub fn reset(&mut self) {
self.0.get_mut().reset();
}
}
impl Default for AtomicFnStore<'_> {
fn default() -> Self {
Self::new()
}
}
unsafe impl Send for AtomicFnStore<'_> {}
unsafe impl Sync for AtomicFnStore<'_> {}
trait Erased {}
impl<T> Erased for T {}
#[derive(Debug)]
#[repr(transparent)]
struct ManuallyDealloc(*mut dyn Erased);
impl ManuallyDealloc {
pub unsafe fn new<T>(reference: &mut T) -> Self {
Self(mem::transmute::<&mut dyn Erased, &mut dyn Erased>(reference) as *mut _)
}
pub const fn ptr(&self) -> *const dyn Erased {
self.0.cast_const()
}
}
impl Drop for ManuallyDealloc {
fn drop(&mut self) {
unsafe { ptr::drop_in_place(self.0) }
}
}
#[cfg(test)]
mod tests {
use crate::{AtomicFnStore, LocalFnStore};
#[test]
fn test_trait() {
const fn is_send<T: Send>() {}
const fn is_sync<T: Sync>() {}
is_send::<LocalFnStore>();
is_send::<AtomicFnStore>();
is_sync::<AtomicFnStore>();
}
#[test]
fn test_local() {
let store = LocalFnStore::new();
fn one() -> i32 {
1
}
let b = store.get(|| store.get(one) + 1);
let a = store.get(one);
assert_eq!(*b, 2);
assert_eq!(*a, 1);
}
#[test]
fn test_atomic() {
let store = AtomicFnStore::new();
fn one() -> i32 {
1
}
let b = store.get(|| store.get(one) + 1);
let a = store.get(one);
assert_eq!(*b, 2);
assert_eq!(*a, 1);
}
}