use std::{
any::{Any, TypeId},
borrow::Borrow,
collections::HashMap,
num::NonZeroUsize,
sync::{Arc, LazyLock, Mutex},
};
use lru::LruCache;
use crate::{
database::{DatabaseEntry, Db, DbIdExt, Entry, TransactionError},
id::Id,
};
type ItemCache<T> = LruCache<Id<T>, Arc<Entry<T>>>;
static CACHES: LazyLock<Mutex<HashMap<TypeId, Box<dyn Any + Send + Sync>>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub trait Cacheable: DatabaseEntry + Send + Sync {}
pub struct Cache<T: Cacheable>(std::marker::PhantomData<T>);
impl<T: Cacheable> Cache<T> {
pub fn init_cache(size: usize) {
let mut map = CACHES.lock().unwrap();
map.entry(TypeId::of::<T>()).or_insert_with(|| {
Box::new(Mutex::new(ItemCache::<T>::new(
NonZeroUsize::new(size).expect("Cache size must be non-zero"),
)))
});
}
fn cache_fn<R>(f: impl FnOnce(&mut ItemCache<T>) -> R) -> Option<R> {
let map = CACHES.lock().unwrap();
let cache = map.get(&TypeId::of::<T>())?;
let mut lru = cache.downcast_ref::<Mutex<ItemCache<T>>>()?.lock().unwrap();
Some(f(&mut lru))
}
}
impl<T: Cacheable> Id<T> {
#[must_use]
pub fn cache_try_get(&self) -> Option<Arc<Entry<T>>> {
Cache::cache_fn(|lru| lru.get(self).cloned()).flatten()
}
pub fn cache_get(
&self,
db: &Db<T::DbInner>,
) -> Result<Option<Arc<Entry<T>>>, TransactionError> {
if let Some(cached) = Cache::cache_fn(|lru| lru.get(self).cloned()).flatten() {
return Ok(Some(cached));
}
if let Some(entry) = self.db_get(db)? {
let arc = Arc::new(entry);
Cache::cache_fn(|lru| lru.put(*self, arc.clone()));
return Ok(Some(arc));
}
Ok(None)
}
#[must_use]
pub fn cache_invalidate(&self) -> Option<Arc<Entry<T>>> {
Cache::cache_fn(|lru| lru.pop(self)).flatten()
}
}
pub trait IdCacheExt<T>: Borrow<Id<T>> + Sized {}
pub trait IdCacheIterExt<T>
where
Self: IntoIterator + Sized,
Self::Item: Borrow<Id<T>>,
T: Cacheable,
{
fn cache_try_get(self) -> Vec<Arc<Entry<T>>> {
self.into_iter()
.filter_map(|id| id.borrow().cache_try_get())
.collect()
}
fn cache_get(self, db: &Db<T::DbInner>) -> Result<Vec<Arc<Entry<T>>>, TransactionError> {
self.into_iter()
.map(|id| {
id.borrow()
.cache_get(db)?
.ok_or(TransactionError::MissingEntry)
})
.collect()
}
}
impl<T, I> IdCacheIterExt<T> for I
where
Self: IntoIterator + Sized,
Self::Item: Borrow<Id<T>>,
T: Cacheable,
{
}
impl<T: Cacheable> Entry<T> {
pub fn cache_upsert(self) {
Cache::cache_fn(|lru| lru.put(self.id(), Arc::new(self)));
}
}