use bytes::Bytes;
use std::time::{Duration, Instant};
pub type CacheKey = String;
#[derive(Debug, Clone)]
pub struct CacheEntry {
pub data: Bytes,
pub size: usize,
pub access_count: u64,
pub last_access: Instant,
pub created_at: Instant,
pub expires_at: Option<Instant>,
pub compressed: bool,
pub spatial_info: Option<SpatialInfo>,
}
impl CacheEntry {
#[must_use]
pub fn new(data: Bytes, compressed: bool) -> Self {
let size = data.len();
let now = Instant::now();
Self {
data,
size,
access_count: 1,
last_access: now,
created_at: now,
expires_at: None,
compressed,
spatial_info: None,
}
}
#[must_use]
pub fn with_ttl(data: Bytes, compressed: bool, ttl: Duration) -> Self {
let mut entry = Self::new(data, compressed);
entry.expires_at = Some(Instant::now() + ttl);
entry
}
#[must_use]
pub fn with_spatial_info(data: Bytes, compressed: bool, spatial_info: SpatialInfo) -> Self {
let mut entry = Self::new(data, compressed);
entry.spatial_info = Some(spatial_info);
entry
}
pub fn record_access(&mut self) {
self.access_count += 1;
self.last_access = Instant::now();
}
#[must_use]
pub fn age(&self) -> Duration {
self.created_at.elapsed()
}
#[must_use]
pub fn is_expired(&self) -> bool {
if let Some(expires_at) = self.expires_at {
Instant::now() >= expires_at
} else {
false
}
}
#[must_use]
pub fn remaining_ttl(&self) -> Option<Duration> {
self.expires_at.map(|expires_at| {
let now = Instant::now();
if now >= expires_at {
Duration::ZERO
} else {
expires_at - now
}
})
}
}
#[derive(Debug, Clone)]
pub struct SpatialInfo {
pub bounds: (f64, f64, f64, f64),
pub crs: Option<u32>,
pub resolution: Option<(f64, f64)>,
pub zoom_level: Option<u8>,
}
impl SpatialInfo {
#[must_use]
pub fn new(bounds: (f64, f64, f64, f64)) -> Self {
Self {
bounds,
crs: None,
resolution: None,
zoom_level: None,
}
}
#[must_use]
pub fn with_crs(mut self, crs: u32) -> Self {
self.crs = Some(crs);
self
}
#[must_use]
pub fn with_resolution(mut self, res_x: f64, res_y: f64) -> Self {
self.resolution = Some((res_x, res_y));
self
}
#[must_use]
pub fn with_zoom_level(mut self, zoom: u8) -> Self {
self.zoom_level = Some(zoom);
self
}
#[must_use]
pub fn intersects(&self, other: &SpatialInfo) -> bool {
let (min_x1, min_y1, max_x1, max_y1) = self.bounds;
let (min_x2, min_y2, max_x2, max_y2) = other.bounds;
min_x1 <= max_x2 && max_x1 >= min_x2 && min_y1 <= max_y2 && max_y1 >= min_y2
}
#[must_use]
pub fn contains_point(&self, x: f64, y: f64) -> bool {
let (min_x, min_y, max_x, max_y) = self.bounds;
x >= min_x && x <= max_x && y >= min_y && y <= max_y
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct TileCoord {
pub z: u8,
pub x: u32,
pub y: u32,
}
impl TileCoord {
#[must_use]
pub const fn new(z: u8, x: u32, y: u32) -> Self {
Self { z, x, y }
}
#[must_use]
pub fn to_cache_key(&self, prefix: &str) -> String {
format!("{prefix}/tiles/{}/{}/{}", self.z, self.x, self.y)
}
#[must_use]
pub fn parent(&self) -> Option<Self> {
if self.z == 0 {
return None;
}
Some(Self {
z: self.z - 1,
x: self.x / 2,
y: self.y / 2,
})
}
#[must_use]
pub fn children(&self) -> [Self; 4] {
let z = self.z + 1;
let x = self.x * 2;
let y = self.y * 2;
[
Self::new(z, x, y),
Self::new(z, x + 1, y),
Self::new(z, x, y + 1),
Self::new(z, x + 1, y + 1),
]
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DiskCacheMetadata {
pub path: String,
pub size: usize,
pub created_at_ms: u64,
pub expires_at_ms: Option<u64>,
pub access_count: u64,
pub compressed: bool,
}
#[derive(Debug, Default)]
pub struct CacheStats {
pub hits: std::sync::atomic::AtomicU64,
pub misses: std::sync::atomic::AtomicU64,
pub writes: std::sync::atomic::AtomicU64,
pub evictions: std::sync::atomic::AtomicU64,
}
impl CacheStats {
#[must_use]
pub fn hit_ratio(&self) -> f64 {
use std::sync::atomic::Ordering;
let hits = self.hits.load(Ordering::Relaxed) as f64;
let misses = self.misses.load(Ordering::Relaxed) as f64;
if hits + misses == 0.0 {
0.0
} else {
hits / (hits + misses)
}
}
pub fn reset(&self) {
use std::sync::atomic::Ordering;
self.hits.store(0, Ordering::Relaxed);
self.misses.store(0, Ordering::Relaxed);
self.writes.store(0, Ordering::Relaxed);
self.evictions.store(0, Ordering::Relaxed);
}
}
#[derive(Debug, Default)]
pub struct LevelStats {
pub tile_count: std::sync::atomic::AtomicUsize,
pub total_size: std::sync::atomic::AtomicUsize,
pub hits: std::sync::atomic::AtomicU64,
}