microcad_lang/render/
cache.rs1use crate::render::{GeometryOutput, HashId};
7
8pub struct RenderCacheItem<T> {
10 content: T,
12 hits: u64,
14 millis: f64,
16 last_access: u64,
18}
19
20impl<T> RenderCacheItem<T> {
21 pub fn new(content: impl Into<T>, millis: f64, last_access: u64) -> Self {
23 Self {
24 content: content.into(),
25 hits: 1,
26 millis,
27 last_access,
28 }
29 }
30
31 fn cost(&self, current_time_stamp: u64) -> f64 {
32 let recency = 1.0 / (1.0 + (current_time_stamp - self.last_access) as f64);
38 let frequency = self.hits as f64;
39 let computation_cost = self.millis;
40
41 let weight_recency = 2.3;
43 let weight_frequency = 0.5;
44 let weight_computation = 0.2;
45
46 (weight_recency * recency)
47 + (weight_frequency * frequency)
48 + (weight_computation * computation_cost)
49 }
50}
51
52pub struct RenderCache<T = GeometryOutput> {
54 current_time_stamp: u64,
56 hits: u64,
58 max_cost: f64,
60 items: rustc_hash::FxHashMap<HashId, RenderCacheItem<T>>,
62}
63
64impl<T> RenderCache<T> {
65 pub fn new() -> Self {
67 Self {
68 current_time_stamp: 0,
69 hits: 0,
70 items: rustc_hash::FxHashMap::default(),
71 max_cost: std::env::var("MICROCAD_CACHE_MAX_COST")
72 .ok()
73 .and_then(|s| s.parse::<f64>().ok())
74 .unwrap_or(1.2),
75 }
76 }
77
78 pub fn garbage_collection(&mut self) {
80 let old_count = self.items.len();
81 self.items.retain(|hash, item| {
82 let cost = item.cost(self.current_time_stamp);
83 let keep = cost > self.max_cost;
84 log::trace!(
85 "Item {hash:X} cost = {cost}: {keep}",
86 keep = if keep { "🔄" } else { "🗑" }
87 );
88 keep
89 });
90
91 let removed = old_count - self.items.len();
92 log::info!(
93 "Removed {removed} items from cache. Cache contains {n} items. {hits} cache hits in this cycle.",
94 n = self.items.len(),
95 hits = self.hits,
96 );
97 self.current_time_stamp += 1;
98 self.hits = 0;
99 }
100
101 pub fn clear(&mut self) {
103 self.items.clear();
104 }
105
106 pub fn get(&mut self, hash: &HashId) -> Option<&T> {
108 match self.items.get_mut(hash) {
109 Some(item) => {
110 item.hits += 1;
111 self.hits += 1;
112 item.last_access = self.current_time_stamp;
113 log::trace!(
114 "Cache hit: {hash:X}. Cost: {}",
115 item.cost(self.current_time_stamp)
116 );
117 Some(&item.content)
118 }
119 _ => None,
120 }
121 }
122
123 pub fn insert_with_cost(
125 &mut self,
126 hash: impl Into<HashId>,
127 geo: impl Into<T>,
128 cost: f64,
129 ) -> &T {
130 let hash: HashId = hash.into();
131 self.items.insert(
132 hash,
133 RenderCacheItem::new(geo, cost, self.current_time_stamp),
134 );
135 &self.items.get(&hash).expect("Hash").content
136 }
137}
138
139impl Default for RenderCache {
140 fn default() -> Self {
141 Self::new()
142 }
143}