lunar-lib 0.11.0

Common utilities for lunar applications
Documentation
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)));
    }
}