1#![warn(missing_docs)]
2
3use std::mem;
6use std::cmp;
7use std::hash::Hash;
8use std::collections::HashMap;
9use std::collections::hash_map::Entry;
10use std::ops::{Deref, DerefMut};
11use std::time::{SystemTime, UNIX_EPOCH};
12
13type LifetimeSec = u32;
14
15pub trait Timer {
17 fn get_time(&self) -> i64;
19}
20
21#[derive(Default)]
23pub struct StandardTimer;
24impl Timer for StandardTimer {
25 fn get_time(&self) -> i64 {
26 match SystemTime::now().duration_since(UNIX_EPOCH) {
27 Ok(d) => d.as_secs() as i64,
28 Err(err) => -(err.duration().as_secs() as i64)
29 }
30 }
31}
32
33pub struct TransientHashMap<K, V, T = StandardTimer> where T: Timer {
39 backing: HashMap<K, V>,
40 timestamps: HashMap<K, i64>,
41 lifetime: LifetimeSec,
42 timer: T
43}
44
45impl<K, V> TransientHashMap<K, V, StandardTimer> where K: Eq + Hash + Clone {
46 pub fn new(lifetime: LifetimeSec) -> Self {
48 TransientHashMap::new_with_timer(lifetime, Default::default())
49 }
50}
51
52impl<K, V, T> TransientHashMap<K, V, T> where K: Eq + Hash + Clone, T: Timer {
53 pub fn new_with_timer(lifetime: LifetimeSec, t: T) -> Self {
55 TransientHashMap {
56 backing: HashMap::new(),
57 timestamps: HashMap::new(),
58 lifetime: lifetime,
59 timer: t
60 }
61 }
62
63 pub fn insert(&mut self, key: K, value: V) -> Option<V> {
67 self.note_used_if(true, &key);
68 self.backing.insert(key, value)
69 }
70
71 pub fn entry(&mut self, key: K) -> Entry<K, V> {
76 self.note_used_if(true, &key);
78 self.backing.entry(key)
79 }
80
81 pub fn get(&mut self, key: &K) -> Option<&V> {
85 let has_key = self.backing.contains_key(key);
86 self.note_used_if(has_key, key);
87 self.backing.get(key)
88 }
89
90 pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
94 self.contains_key(key);
96 self.backing.get_mut(key)
97 }
98
99 pub fn contains_key(&mut self, key: &K) -> bool {
103 let contains = self.backing.contains_key(key);
104 self.note_used_if(contains, key);
105 contains
106 }
107
108 pub fn remove(&mut self, k: &K) -> Option<V> {
112 self.timestamps.remove(k);
113 self.backing.remove(k)
114 }
115
116 pub fn remaining_lifetime(&mut self, key: &K) -> Option<LifetimeSec> {
118 self.timestamps.get(key).map(|time| {
119 let time = self.timer.get_time() - time;
120 cmp::max(0, self.lifetime as i64 - time) as LifetimeSec
121 })
122 }
123
124 #[inline]
125 fn note_used_if(&mut self, condition: bool, key: &K) {
126 if condition {
127 self.timestamps.insert(key.clone(), self.timer.get_time());
128 }
129 }
130
131 pub fn prune(&mut self) -> Vec<K> {
133 let now = self.timer.get_time();
134
135 let timestamps = mem::replace(&mut self.timestamps, HashMap::new());
136 let (ok, removed) = timestamps.into_iter()
137 .partition(|entry| now - entry.1 < self.lifetime as i64);
138 self.timestamps = ok;
139
140 removed
141 .into_iter()
142 .map(|entry| {
143 self.backing.remove(&entry.0);
144 entry.0
145 })
146 .collect()
147 }
148
149 pub fn direct(&self) -> &HashMap<K, V> {
151 &self.backing
152 }
153
154 pub fn direct_mut(&mut self) -> &mut HashMap<K, V> {
156 &mut self.backing
157 }
158}
159
160impl<K, V, T> Deref for TransientHashMap<K, V, T> where T: Timer {
161 type Target = HashMap<K, V>;
162
163 fn deref(&self) -> &Self::Target {
164 &self.backing
165 }
166}
167
168impl<K, V, T> DerefMut for TransientHashMap<K, V, T> where T: Timer {
169 fn deref_mut(&mut self) -> &mut Self::Target {
170 &mut self.backing
171 }
172}
173
174#[cfg(test)]
175mod test {
176 use std::cell::Cell;
177 use super::{TransientHashMap, Timer};
178
179 struct TestTimer<'a> {
180 time: &'a Cell<i64>
181 }
182
183 impl<'a> Timer for TestTimer<'a> {
184 fn get_time(&self) -> i64 {
185 self.time.get()
186 }
187 }
188
189 #[test]
190 fn should_remove_lifetime_when_calling_remove() {
191 let time = Cell::new(0);
193 let timer = TestTimer {
194 time: &time
195 };
196 let mut t_map: TransientHashMap<u64, (), _> = TransientHashMap::new_with_timer(2, timer);
197 t_map.insert(2, ());
198 assert_eq!(t_map.remaining_lifetime(&2), Some(2));
199
200 t_map.remove(&2);
202
203 assert_eq!(t_map.remaining_lifetime(&2), None);
205 }
206
207 #[test]
208 fn should_not_track_lifetime_if_key_is_not_present() {
209 let time = Cell::new(0);
211 let timer = TestTimer {
212 time: &time
213 };
214 let mut t_map: TransientHashMap<u64, (), _> = TransientHashMap::new_with_timer(2, timer);
215
216 t_map.contains_key(&2);
218
219 assert_eq!(t_map.remaining_lifetime(&2), None);
221 }
222
223 #[test]
224 fn should_return_correct_lifetime_when_negative() {
225 let time = Cell::new(0);
227 let timer = TestTimer {
228 time: &time
229 };
230 let mut t_map = TransientHashMap::new_with_timer(2, timer);
231 t_map.insert(1, 0);
232
233 time.set(10);
235
236 assert_eq!(t_map.remaining_lifetime(&1), Some(0));
238 }
239
240 #[test]
241 fn should_return_pruned_keys() {
242 let time = Cell::new(0);
244 let timer = TestTimer {
245 time: &time
246 };
247
248 let mut t_map = TransientHashMap::new_with_timer(2, timer);
249 t_map.insert(1, 0);
250 t_map.insert(2, 0);
251 t_map.insert(3, 0);
252 time.set(1);
253 t_map.insert(4, 0);
254 assert_eq!(t_map.direct().len(), 4);
255
256 time.set(2);
258 let keys = t_map.prune();
259
260 assert_eq!(t_map.direct().len(), 1);
262 assert_eq!(keys.len(), 3);
263 assert!(keys.contains(&1));
264 assert!(keys.contains(&2));
265 assert!(keys.contains(&3));
266 }
267
268 #[test]
269 fn it_works() {
270 let time = Cell::new(0);
271 let timer = TestTimer {
272 time: &time
273 };
274
275 let mut t_map = TransientHashMap::new_with_timer(2, timer);
276 assert_eq!(t_map.remaining_lifetime(&1), None);
277
278 t_map.insert(1, 1);
279 assert_eq!(t_map.remaining_lifetime(&1), Some(2));
280
281 time.set(1);
282 assert_eq!(t_map.remaining_lifetime(&1), Some(1));
283
284 time.set(2);
285 assert_eq!(t_map.remaining_lifetime(&1), Some(0));
286
287 time.set(1);
288 assert_eq!(t_map.remaining_lifetime(&1), Some(1));
289
290 t_map.prune();
291 assert_eq!(t_map.remaining_lifetime(&1), Some(1));
292
293 time.set(2);
294 assert_eq!(t_map.remaining_lifetime(&1), Some(0));
295
296 t_map.prune();
297 assert_eq!(t_map.remaining_lifetime(&1), None);
298
299 time.set(1);
300 assert_eq!(t_map.remaining_lifetime(&1), None);
301 }
302}