use crate::render::GeometryOutput;
use microcad_core::hash::{HashId, HashMap};
pub struct RenderCacheItem<T> {
content: T,
hits: u64,
millis: f64,
last_access: u64,
}
impl<T> RenderCacheItem<T> {
pub fn new(content: impl Into<T>, millis: f64, last_access: u64) -> Self {
Self {
content: content.into(),
hits: 1,
millis,
last_access,
}
}
fn cost(&self, current_time_stamp: u64) -> f64 {
let recency = 1.0 / (1.0 + (current_time_stamp - self.last_access) as f64);
let frequency = self.hits as f64;
let computation_cost = self.millis;
let weight_recency = 2.3;
let weight_frequency = 0.5;
let weight_computation = 0.2;
(weight_recency * recency)
+ (weight_frequency * frequency)
+ (weight_computation * computation_cost)
}
}
pub struct RenderCache<T = GeometryOutput> {
current_time_stamp: u64,
hits: u64,
max_cost: f64,
items: HashMap<HashId, RenderCacheItem<T>>,
}
impl<T> RenderCache<T> {
pub fn new() -> Self {
Self {
current_time_stamp: 0,
hits: 0,
items: HashMap::default(),
max_cost: std::env::var("MICROCAD_CACHE_MAX_COST")
.ok()
.and_then(|s| s.parse::<f64>().ok())
.unwrap_or(1.2),
}
}
pub fn garbage_collection(&mut self) {
let old_count = self.items.len();
self.items.retain(|hash, item| {
let cost = item.cost(self.current_time_stamp);
let keep = cost > self.max_cost;
log::trace!(
"Item {hash:X} cost = {cost}: {keep}",
keep = if keep { "🔄" } else { "🗑" }
);
keep
});
let removed = old_count - self.items.len();
log::info!(
"Removed {removed} items from cache. Cache contains {n} items. {hits} cache hits in this cycle.",
n = self.items.len(),
hits = self.hits,
);
self.current_time_stamp += 1;
self.hits = 0;
}
pub fn clear(&mut self) {
self.items.clear();
}
pub fn get(&mut self, hash: &HashId) -> Option<&T> {
match self.items.get_mut(hash) {
Some(item) => {
item.hits += 1;
self.hits += 1;
item.last_access = self.current_time_stamp;
log::trace!(
"Cache hit: {hash:X}. Cost: {}",
item.cost(self.current_time_stamp)
);
Some(&item.content)
}
_ => None,
}
}
pub fn insert_with_cost(
&mut self,
hash: impl Into<HashId>,
geo: impl Into<T>,
cost: f64,
) -> &T {
let hash: HashId = hash.into();
self.items.insert(
hash,
RenderCacheItem::new(geo, cost, self.current_time_stamp),
);
&self.items.get(&hash).expect("Hash").content
}
}
impl Default for RenderCache {
fn default() -> Self {
Self::new()
}
}