use std::collections::HashMap;
use std::future::Future;
use std::sync::{Arc, RwLock};
use crate::{DirEntry, Directory, PmtResult, TileId};
pub enum DirCacheResult {
NotCached,
NotFound,
Found(DirEntry),
}
impl From<Option<&DirEntry>> for DirCacheResult {
fn from(entry: Option<&DirEntry>) -> Self {
match entry {
Some(entry) => DirCacheResult::Found(entry.clone()),
None => DirCacheResult::NotFound,
}
}
}
pub trait DirectoryCache {
fn get_dir_entry_or_insert(
&self,
offset: usize,
tile_id: TileId,
fetcher: impl Future<Output = PmtResult<Directory>> + Send,
) -> impl Future<Output = PmtResult<Option<DirEntry>>> + Send;
}
pub struct NoCache;
impl DirectoryCache for NoCache {
#[inline]
async fn get_dir_entry_or_insert(
&self,
_: usize,
tile_id: TileId,
fetcher: impl Future<Output = PmtResult<Directory>> + Send,
) -> PmtResult<Option<DirEntry>> {
let dir = fetcher.await?;
Ok(dir.find_tile_id(tile_id).cloned())
}
}
#[derive(Default)]
pub struct HashMapCache {
pub cache: Arc<RwLock<HashMap<usize, Directory>>>,
}
impl HashMapCache {
fn get_dir_entry(&self, offset: usize, tile_id: TileId) -> DirCacheResult {
#[expect(clippy::unwrap_used)]
if let Some(dir) = self.cache.read().unwrap().get(&offset) {
return dir.find_tile_id(tile_id).into();
}
DirCacheResult::NotCached
}
fn insert_dir(&self, offset: usize, directory: Directory) {
#[expect(clippy::unwrap_used)]
self.cache.write().unwrap().insert(offset, directory);
}
}
impl DirectoryCache for HashMapCache {
async fn get_dir_entry_or_insert(
&self,
offset: usize,
tile_id: TileId,
fetcher: impl Future<Output = PmtResult<Directory>> + Send,
) -> PmtResult<Option<DirEntry>> {
let dir_entry = self.get_dir_entry(offset, tile_id);
match dir_entry {
DirCacheResult::Found(entry) => Ok(Some(entry)),
DirCacheResult::NotFound => Ok(None),
DirCacheResult::NotCached => {
let directory = fetcher.await?;
let dir_entry = directory.find_tile_id(tile_id).cloned();
self.insert_dir(offset, directory);
Ok(dir_entry)
}
}
}
}
#[cfg(feature = "moka")]
pub struct MokaCache {
pub cache: moka::future::Cache<usize, Directory>,
}
#[cfg(feature = "moka")]
impl DirectoryCache for MokaCache {
async fn get_dir_entry_or_insert(
&self,
offset: usize,
tile_id: TileId,
fetcher: impl Future<Output = PmtResult<Directory>> + Send,
) -> PmtResult<Option<DirEntry>> {
let directory = self.cache.try_get_with(offset, fetcher).await;
let directory = directory.map_err(|e| {
crate::PmtError::DirectoryCacheError(format!("Moka cache fetch error: {e}"))
})?;
Ok(directory.find_tile_id(tile_id).cloned())
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "moka")]
use crate::MokaCache;
use crate::{DirEntry, Directory, DirectoryCache, HashMapCache};
#[tokio::test]
async fn test_hash_map_cache() {
let cache = HashMapCache::default();
let offset = 0;
let tile_id = crate::TileId::new(0);
let mut dir_to_cache = Directory::default();
dir_to_cache.entries.push(DirEntry::default());
let get_result = cache.get_dir_entry(offset, tile_id.unwrap());
assert!(matches!(
get_result,
crate::cache::DirCacheResult::NotCached
));
cache.insert_dir(offset, dir_to_cache);
let get_result = cache.get_dir_entry(offset, tile_id.unwrap());
assert!(matches!(get_result, crate::cache::DirCacheResult::Found(_)));
let get_result = cache
.get_dir_entry_or_insert(offset, tile_id.unwrap(), async {
Err(crate::PmtError::InvalidEntry)
})
.await
.unwrap();
assert!(get_result.is_some());
let get_result = cache
.get_dir_entry_or_insert(offset + 10, tile_id.unwrap(), async {
Err(crate::PmtError::InvalidEntry)
})
.await;
assert!(get_result.is_err());
let get_result = cache
.get_dir_entry_or_insert(offset + 10, tile_id.unwrap(), async {
let mut dir = Directory::default();
let dir_entry = DirEntry {
offset: (offset + 10) as u64,
..Default::default()
};
dir.entries.push(dir_entry);
Ok(dir)
})
.await;
assert!(get_result.is_ok());
assert!(get_result.unwrap().is_some());
}
#[cfg(feature = "moka")]
#[tokio::test]
async fn test_moka_cache() {
let cache = MokaCache {
cache: moka::future::Cache::new(100),
};
let offset = 0;
let tile_id = crate::TileId::new(0);
let mut dir_to_cache = Directory::default();
dir_to_cache.entries.push(DirEntry::default());
let get_result = cache
.get_dir_entry_or_insert(offset, tile_id.unwrap(), async {
Err(crate::PmtError::InvalidEntry)
})
.await;
assert!(get_result.is_err());
let get_result = cache
.get_dir_entry_or_insert(offset, tile_id.unwrap(), async {
let mut dir = Directory::default();
let dir_entry = DirEntry {
offset: (offset + 10) as u64,
..Default::default()
};
dir.entries.push(dir_entry);
Ok(dir)
})
.await;
assert!(get_result.is_ok());
assert!(get_result.unwrap().is_some());
let get_result = cache
.get_dir_entry_or_insert(offset, tile_id.unwrap(), async {
Err(crate::PmtError::InvalidEntry)
})
.await;
assert!(get_result.is_ok());
assert!(get_result.unwrap().is_some());
}
}