1use linked_hash_map::LinkedHashMap;
2use std::ops::{Div, Sub};
3use std::time::{Duration, Instant};
4
5pub trait CacheIFace<K: std::cmp::Eq, O> {
6 fn invalidate_all(&mut self);
7 fn invalidate_stale(&mut self);
8 fn opt(&mut self, key: &K) -> Option<&O>;
9 fn opt_mut(&mut self, key: &K) -> Option<&mut O>;
10 fn opt_no_touch(&self, key: &K) -> Option<&O>;
11 fn get(&mut self, key: &K) -> Option<&O>;
12 fn get_mut(&mut self, key: &K) -> Option<&mut O>;
13 fn contains_key(&self, key: &K) -> bool;
14 fn invalidate(&mut self, key: &K);
15 fn insert(&mut self, key: K, item: O);
16}
17
18struct CacheEntry<O> {
19 item: O,
20 last_used: Instant,
21}
22
23pub struct Cache<K: std::cmp::Eq + std::hash::Hash, O> {
24 entries: LinkedHashMap<K, CacheEntry<O>>,
26 #[allow(clippy::type_complexity)]
27 producer: Box<dyn Fn(&K) -> Option<O> + Send>,
28 max_inactive_time: Duration,
29 inactive_resolution: Duration,
30 max_size: usize,
31}
32
33impl<K: std::cmp::Eq + std::hash::Hash, O> Cache<K, O> {
34 pub fn new<P>(producer: P, max_inactive_time: Duration, max_size: usize) -> Self
35 where
36 P: Fn(&K) -> Option<O> + Send + 'static,
37 {
38 let inactive_resolution = max_inactive_time.div(10);
39 Cache {
40 entries: LinkedHashMap::new(),
41 producer: Box::new(producer),
42 max_inactive_time,
43 inactive_resolution,
44 max_size,
45 }
46 }
47 pub fn len(&self) -> usize {
48 self.entries.len()
49 }
50 pub fn is_empty(&self) -> bool {
51 self.entries.is_empty()
52 }
53}
54
55impl<K: std::cmp::Eq + std::hash::Hash + Clone, O> CacheIFace<K, O> for Cache<K, O> {
56 fn invalidate_all(&mut self) {
57 self.entries.clear();
58 }
59
60 fn invalidate_stale(&mut self) {
61 let now = Instant::now();
62 let max_age = now.sub(self.max_inactive_time);
63
64 loop {
65 let front_opt: Option<(&K, &CacheEntry<O>)> = self.entries.front();
66 if let Some(entry) = front_opt {
67 let e = entry.1;
68 if e.last_used.lt(&max_age) {
69 let _drop_entry = self.entries.pop_front();
70 } else {
71 break;
72 }
73 } else {
74 break;
75 }
76 }
77 }
78
79 fn opt(&mut self, key: &K) -> Option<&O> {
80 let entry_opt = self.entries.get_mut(key);
81 if let Some(e) = entry_opt {
82 let now = Instant::now();
83 if e.last_used.lt(&now.sub(self.inactive_resolution)) {
85 let mut removed_entry = self.entries.remove(key).unwrap();
86 removed_entry.last_used = now;
87 self.entries.insert(key.clone(), removed_entry);
88 }
89 self.entries.get(key).map(|i| &i.item)
90 } else {
91 None
92 }
93 }
94
95 fn opt_mut(&mut self, key: &K) -> Option<&mut O> {
96 let entry_opt = self.entries.get_mut(key);
97 if let Some(e) = entry_opt {
98 let now = Instant::now();
99 if e.last_used.lt(&now.sub(self.inactive_resolution)) {
101 let mut removed_entry = self.entries.remove(key).unwrap();
102 removed_entry.last_used = now;
103 self.entries.insert(key.clone(), removed_entry);
104 }
105 self.entries.get_mut(key).map(|i| &mut i.item)
106 } else {
107 None
108 }
109 }
110
111 fn opt_no_touch(&self, key: &K) -> Option<&O> {
112 self.entries.get(key).map(|e| &e.item)
113 }
114
115 fn get(&mut self, key: &K) -> Option<&O> {
116 self.invalidate_stale();
117 if !self.contains_key(key) {
118 let item_opt = (*self.producer)(key);
119 if let Some(item) = item_opt {
120 self.insert(key.clone(), item);
121 }
122 }
123 self.opt(key)
124 }
125
126 fn get_mut(&mut self, key: &K) -> Option<&mut O> {
127 self.invalidate_stale();
128 if !self.contains_key(key) {
129 let item_opt = (*self.producer)(key);
130 if let Some(item) = item_opt {
131 self.insert(key.clone(), item);
132 }
133 }
134 self.opt_mut(key)
135 }
136 fn contains_key(&self, key: &K) -> bool {
137 self.entries.contains_key(key)
138 }
139
140 fn invalidate(&mut self, key: &K) {
141 self.entries.remove(key);
142 }
143
144 fn insert(&mut self, key: K, item: O) {
145 let entry = CacheEntry {
146 item,
147 last_used: Instant::now(),
148 };
149 self.entries.insert(key, entry);
150 while self.entries.len() > self.max_size {
151 let _drop_entry = self.entries.pop_front();
152 }
153 }
154}
155
156#[cfg(test)]
157pub mod tests {
158 use crate::cache::{Cache, CacheIFace};
159 use std::time::Duration;
160
161 fn test_send<S: Send>(_sendable: &S) {
162 }
164
165 #[test]
166 fn test_cache() {
167 let producer = |key: &&str| Some(format!("entry: {key}"));
168 let mut cache: Cache<&str, String> = Cache::new(producer, Duration::from_secs(2), 10);
169
170 test_send(&cache);
171
172 let _one = cache.get(&"a");
173 let _two = cache.get(&"b");
174 let three = cache.get(&"c");
175 assert_eq!(three.expect("c not found").as_str(), "entry: c");
176
177 assert_eq!(3, cache.len());
178
179 std::thread::sleep(Duration::from_secs(3));
180 cache.invalidate_stale();
181
182 assert_eq!(0, cache.len());
183
184 for x in vec![
185 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
186 ] {
187 let _ = cache.get(&x);
188 }
189
190 assert_eq!(10, cache.len());
191 }
192}