transient_hashmap/
lib.rs

1#![warn(missing_docs)]
2
3//! HashMap with entries living for limited period of time.
4
5use 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
15/// Time provider.
16pub trait Timer {
17	/// Returns current timestamp in seconds.
18	fn get_time(&self) -> i64;
19}
20
21/// Standard time provider returning current time.
22#[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
33/// `HashMap` with entries that will be garbage collected (pruned)
34/// after not being used for specified time.
35///
36/// Pruning does not occur automatically, make sure to call `prune` method
37/// to remove old entries.
38pub 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	/// Creates new `TransientHashMap` with standard timer and specified entries lifetime.
47	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	/// Creates new `TransientHashMap` with given timer and specfied entries lifetime.
54	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	/// Insert new entry to this map overwriting any previous entry.
64	///
65	/// Prolongs lifetime of `key`.
66	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	/// Insert new entry to this map overwriting any previous entry.
72	///
73	/// Always prolongs the lifetime of `key`.
74	/// TODO [ToDr] Should only prolong if new item is inserted or entry is occupied.
75	pub fn entry(&mut self, key: K) -> Entry<K, V> {
76		// TODO [ToDr] note used only if occupied or inserted!
77		self.note_used_if(true, &key);
78		self.backing.entry(key)
79	}
80
81	/// Gets reference to stored value.
82	///
83	/// Prolongs lifetime of `key` if is in the map.
84	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	/// Gets mutable reference to stored value.
91	///
92	/// Prolongs lifetime of `key` if is in the map.
93	pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
94		// This will invoke `note_used` if the key exists
95		self.contains_key(key);
96		self.backing.get_mut(key)
97	}
98
99	/// Checks if `key` is contained.
100	///
101	/// Prolongs lifetime of `key` if is in the map.
102	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	/// Removes `key` from the map if present.
109	///
110	/// Also removes associated lifetime.
111	pub fn remove(&mut self, k: &K) -> Option<V> {
112		self.timestamps.remove(k);
113		self.backing.remove(k)
114	}
115
116	/// Returns remaining lifetime of `key` without altering it.
117	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	/// Clear overdue entries from the `TransientHashMap`.
132	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	/// Get a reference to backing `HashMap`.
150	pub fn direct(&self) -> &HashMap<K, V> {
151		&self.backing
152	}
153
154	/// Get the mutable reference to backing `HashMap`.
155	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		// given
192		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		// when
201		t_map.remove(&2);
202
203		// then
204		assert_eq!(t_map.remaining_lifetime(&2), None);
205	}
206
207	#[test]
208	fn should_not_track_lifetime_if_key_is_not_present() {
209		// given
210		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		// when
217		t_map.contains_key(&2);
218
219		// then
220		assert_eq!(t_map.remaining_lifetime(&2), None);
221	}
222
223	#[test]
224	fn should_return_correct_lifetime_when_negative() {
225		// given
226		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		// when
234		time.set(10);
235
236		// then
237		assert_eq!(t_map.remaining_lifetime(&1), Some(0));
238	}
239
240	#[test]
241	fn should_return_pruned_keys() {
242		// given
243		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		// when
257		time.set(2);
258		let keys = t_map.prune();
259
260		// then
261		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}