lattice_completion/
cache.rs1use std::collections::HashMap;
15use std::sync::Mutex;
16use std::time::{Duration, Instant};
17
18use crate::candidate::{CacheKey, RawCandidate};
19
20#[derive(Debug)]
21struct CachedEntry {
22 candidates: Vec<RawCandidate>,
23 inserted_at: Instant,
24 ttl: Duration,
25}
26
27#[derive(Debug, Default)]
32pub struct GeneratorCache {
33 entries: Mutex<HashMap<CacheKey, CachedEntry>>,
34}
35
36impl GeneratorCache {
37 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn get(&self, key: &CacheKey) -> Option<Vec<RawCandidate>> {
46 #[allow(clippy::unwrap_used)]
50 let entries = self.entries.lock().unwrap();
51 let entry = entries.get(key)?;
52 if entry.inserted_at.elapsed() >= entry.ttl {
53 return None;
54 }
55 Some(entry.candidates.clone())
56 }
57
58 pub fn put(&self, key: CacheKey, candidates: Vec<RawCandidate>, ttl: Duration) {
60 #[allow(clippy::unwrap_used)]
61 let mut entries = self.entries.lock().unwrap();
62 entries.insert(
63 key,
64 CachedEntry {
65 candidates,
66 inserted_at: Instant::now(),
67 ttl,
68 },
69 );
70 }
71
72 pub fn invalidate(&self, key: &CacheKey) {
75 #[allow(clippy::unwrap_used)]
76 self.entries.lock().unwrap().remove(key);
77 }
78
79 pub fn clear(&self) {
82 #[allow(clippy::unwrap_used)]
83 self.entries.lock().unwrap().clear();
84 }
85
86 pub fn len(&self) -> usize {
89 #[allow(clippy::unwrap_used)]
90 self.entries.lock().unwrap().len()
91 }
92
93 pub fn is_empty(&self) -> bool {
94 self.len() == 0
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 #![allow(clippy::unwrap_used, clippy::panic)]
101 use super::*;
102 use crate::candidate::{CandidateData, CandidateKind};
103
104 fn sample(text: &str) -> RawCandidate {
105 RawCandidate {
106 insert_text: None,
107 text: text.into(),
108 display: text.into(),
109 kind: CandidateKind::Plain,
110 data: CandidateData::Plain,
111 source: None,
112 accept_action: None,
113 annotations: Vec::new(),
114 display_spans: Vec::new(),
115 }
116 }
117
118 #[test]
119 fn empty_cache_returns_none() {
120 let c = GeneratorCache::new();
121 assert!(c.get(&CacheKey::new("nope")).is_none());
122 assert!(c.is_empty());
123 }
124
125 #[test]
126 fn put_then_get_round_trips() {
127 let c = GeneratorCache::new();
128 let key = CacheKey::new("k1");
129 c.put(
130 key.clone(),
131 vec![sample("a"), sample("b")],
132 Duration::from_secs(60),
133 );
134 let got = c.get(&key).unwrap();
135 assert_eq!(got.len(), 2);
136 assert_eq!(got[0].text, "a");
137 assert_eq!(c.len(), 1);
138 }
139
140 #[test]
141 fn distinct_keys_are_independent() {
142 let c = GeneratorCache::new();
143 c.put(
144 CacheKey::new("a"),
145 vec![sample("aa")],
146 Duration::from_secs(60),
147 );
148 c.put(
149 CacheKey::new("b"),
150 vec![sample("bb")],
151 Duration::from_secs(60),
152 );
153 assert_eq!(c.get(&CacheKey::new("a")).unwrap()[0].text, "aa");
154 assert_eq!(c.get(&CacheKey::new("b")).unwrap()[0].text, "bb");
155 }
156
157 #[test]
158 fn ttl_zero_evicts_immediately() {
159 let c = GeneratorCache::new();
162 let key = CacheKey::new("ttl0");
163 c.put(key.clone(), vec![sample("x")], Duration::ZERO);
164 std::thread::sleep(Duration::from_micros(100));
166 assert!(c.get(&key).is_none());
167 }
168
169 #[test]
170 fn invalidate_drops_entry() {
171 let c = GeneratorCache::new();
172 let key = CacheKey::new("k");
173 c.put(key.clone(), vec![sample("x")], Duration::from_secs(60));
174 c.invalidate(&key);
175 assert!(c.get(&key).is_none());
176 }
177
178 #[test]
179 fn clear_drops_all_entries() {
180 let c = GeneratorCache::new();
181 c.put(
182 CacheKey::new("a"),
183 vec![sample("a")],
184 Duration::from_secs(60),
185 );
186 c.put(
187 CacheKey::new("b"),
188 vec![sample("b")],
189 Duration::from_secs(60),
190 );
191 c.clear();
192 assert!(c.is_empty());
193 }
194
195 #[test]
196 fn put_overwrites_existing_key() {
197 let c = GeneratorCache::new();
198 let key = CacheKey::new("k");
199 c.put(key.clone(), vec![sample("old")], Duration::from_secs(60));
200 c.put(key.clone(), vec![sample("new")], Duration::from_secs(60));
201 assert_eq!(c.get(&key).unwrap()[0].text, "new");
202 assert_eq!(c.len(), 1);
203 }
204}