use std::time::Duration;
use moka::future::Cache;
use tracing::info;
#[cfg(feature = "metrics")]
use crate::metrics::{TILE_CACHE_REQUESTS_TOTAL, ZOOM_LABELS};
pub type OptPmtCache = Option<PmtCache>;
pub const NO_PMT_CACHE: OptPmtCache = None;
#[derive(Clone, Debug)]
pub struct PmtCache(Cache<PmtCacheKey, pmtiles::Directory>);
impl PmtCache {
#[must_use]
pub fn new(
max_size_bytes: u64,
expiry: Option<Duration>,
idle_timeout: Option<Duration>,
) -> Self {
let mut builder = Cache::builder()
.name("pmtiles_directory_cache")
.weigher(|_key: &PmtCacheKey, value: &pmtiles::Directory| -> u32 {
value.get_approx_byte_size().try_into().unwrap_or(u32::MAX)
+ size_of::<PmtCacheKey>().try_into().unwrap_or(u32::MAX)
})
.max_capacity(max_size_bytes);
if let Some(ttl) = expiry {
builder = builder.time_to_live(ttl);
}
if let Some(tti) = idle_timeout {
builder = builder.time_to_idle(tti);
}
Self(builder.build())
}
}
impl Default for PmtCache {
fn default() -> Self {
Self::new(0, None, None)
}
}
#[derive(Clone, Debug)]
pub struct PmtCacheInstance {
id: usize,
cache: PmtCache,
}
impl PmtCacheInstance {
#[must_use]
pub fn new(id: usize, cache: PmtCache) -> Self {
Self { id, cache }
}
#[must_use]
pub fn id(&self) -> usize {
self.id
}
pub fn invalidate_all(&self) {
self.cache.0.invalidate_all();
info!("Invalidated PMTiles directory cache for id={}", self.id);
}
pub async fn sync(&self) {
self.cache.0.run_pending_tasks().await;
}
#[must_use]
pub fn entry_count(&self) -> u64 {
self.cache.0.entry_count()
}
#[must_use]
pub fn weighted_size(&self) -> u64 {
self.cache.0.weighted_size()
}
}
impl pmtiles::DirectoryCache for PmtCacheInstance {
async fn get_dir_entry_or_insert(
&self,
offset: usize,
tile_id: pmtiles::TileId,
fetcher: impl Future<Output = pmtiles::PmtResult<pmtiles::Directory>> + Send,
) -> pmtiles::PmtResult<Option<pmtiles::DirEntry>> {
let key = PmtCacheKey::new(self.id, offset);
let entry = self
.cache
.0
.entry(key)
.or_try_insert_with(fetcher)
.await
.map_err(|e| {
pmtiles::PmtError::DirectoryCacheError(format!("Moka cache fetch error: {e}"))
})?;
#[cfg(feature = "metrics")]
{
let result = if entry.is_fresh() { "miss" } else { "hit" };
let zoom = ZOOM_LABELS[pmtiles::TileCoord::from(tile_id).z() as usize];
TILE_CACHE_REQUESTS_TOTAL
.with_label_values(&["pmtiles_directory", result, zoom])
.inc();
}
Ok(entry.into_value().find_tile_id(tile_id).cloned())
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
struct PmtCacheKey {
id: usize,
offset: usize,
}
impl PmtCacheKey {
fn new(id: usize, offset: usize) -> Self {
Self { id, offset }
}
}