use std::collections::BTreeMap;
use uzor_urx_core::region::{CacheKey, RegionId};
use uzor_urx_cpu::Pixmap;
pub const DEFAULT_CACHE_BUDGET_BYTES: u64 = 64 * 1024 * 1024;
#[derive(Debug)]
pub(crate) struct CachedEntry {
#[allow(dead_code)]
pub key: CacheKey,
pub pixmap: Pixmap,
pub bytes: u64,
pub last_used_us: u64,
}
#[derive(Debug)]
pub(crate) struct CacheStore {
entries: BTreeMap<RegionId, CachedEntry>,
total_bytes: u64,
budget_bytes: u64,
}
impl CacheStore {
pub fn new() -> Self {
Self {
entries: BTreeMap::new(),
total_bytes: 0,
budget_bytes: DEFAULT_CACHE_BUDGET_BYTES,
}
}
pub fn set_budget(&mut self, bytes: u64) { self.budget_bytes = bytes; }
#[allow(dead_code)]
pub fn budget(&self) -> u64 { self.budget_bytes }
pub fn total_bytes(&self) -> u64 { self.total_bytes }
pub fn count(&self) -> usize { self.entries.len() }
pub fn get(&self, id: RegionId) -> Option<&CachedEntry> {
self.entries.get(&id)
}
pub fn touch(&mut self, id: RegionId, now_us: u64) {
if let Some(e) = self.entries.get_mut(&id) {
e.last_used_us = now_us;
}
}
pub fn insert(&mut self, id: RegionId, key: CacheKey, pixmap: Pixmap, now_us: u64) {
let bytes = (pixmap.width() as u64) * (pixmap.height() as u64) * 4;
if let Some(old) = self.entries.remove(&id) {
self.total_bytes = self.total_bytes.saturating_sub(old.bytes);
}
self.entries.insert(id, CachedEntry {
key, pixmap, bytes, last_used_us: now_us,
});
self.total_bytes = self.total_bytes.saturating_add(bytes);
self.evict_if_over_budget();
emit_metrics(self);
}
pub fn remove(&mut self, id: RegionId) {
if let Some(e) = self.entries.remove(&id) {
self.total_bytes = self.total_bytes.saturating_sub(e.bytes);
metrics::counter!(uzor_urx_core::metrics_keys::KEY_CACHE_EVICT).increment(1);
}
emit_metrics(self);
}
pub fn clear(&mut self) {
let n = self.entries.len() as u64;
self.entries.clear();
self.total_bytes = 0;
if n > 0 {
metrics::counter!(uzor_urx_core::metrics_keys::KEY_CACHE_EVICT).increment(n);
}
emit_metrics(self);
}
fn evict_if_over_budget(&mut self) {
while self.total_bytes > self.budget_bytes && !self.entries.is_empty() {
let oldest_id = self.entries.iter()
.min_by_key(|(_, e)| e.last_used_us)
.map(|(id, _)| *id);
let Some(id) = oldest_id else { break };
if let Some(e) = self.entries.remove(&id) {
self.total_bytes = self.total_bytes.saturating_sub(e.bytes);
metrics::counter!(uzor_urx_core::metrics_keys::KEY_CACHE_EVICT).increment(1);
}
}
}
}
fn emit_metrics(store: &CacheStore) {
use uzor_urx_core::metrics_keys::{KEY_CACHE_BYTES, KEY_CACHE_COUNT};
metrics::gauge!(KEY_CACHE_BYTES).set(store.total_bytes as f64);
metrics::gauge!(KEY_CACHE_COUNT).set(store.entries.len() as f64);
}
pub(crate) fn blit_cached(
src: &Pixmap,
dst: &mut Pixmap,
dst_x: i64,
dst_y: i64,
) {
let sw = src.width() as i64;
let sh = src.height() as i64;
let dw = dst.width() as i64;
let dh = dst.height() as i64;
let x0 = dst_x.max(0);
let y0 = dst_y.max(0);
let x1 = (dst_x + sw).min(dw);
let y1 = (dst_y + sh).min(dh);
if x0 >= x1 || y0 >= y1 { return; }
for py in y0 .. y1 {
for px in x0 .. x1 {
let sx = (px - dst_x) as u32;
let sy = (py - dst_y) as u32;
let p = src.get_pixel(sx, sy);
if p[3] == 0 { continue; }
dst.blend_pixel(px as u32, py as u32, p);
}
}
}