use fnv::FnvHashMap;
use instant::Instant;
use std::collections::hash_map::{
self,
Entry::{Occupied, Vacant},
};
use std::collections::VecDeque;
use std::time::Duration;
struct ExpiringElement<Element> {
element: Element,
expires: Instant,
}
pub(crate) struct TimeCache<Key, Value> {
map: FnvHashMap<Key, ExpiringElement<Value>>,
list: VecDeque<ExpiringElement<Key>>,
ttl: Duration,
}
pub(crate) struct OccupiedEntry<'a, K, V> {
entry: hash_map::OccupiedEntry<'a, K, ExpiringElement<V>>,
}
impl<'a, K, V> OccupiedEntry<'a, K, V>
where
K: Eq + std::hash::Hash + Clone,
{
pub(crate) fn into_mut(self) -> &'a mut V {
&mut self.entry.into_mut().element
}
}
pub(crate) struct VacantEntry<'a, K, V> {
expiration: Instant,
entry: hash_map::VacantEntry<'a, K, ExpiringElement<V>>,
list: &'a mut VecDeque<ExpiringElement<K>>,
}
impl<'a, K, V> VacantEntry<'a, K, V>
where
K: Eq + std::hash::Hash + Clone,
{
pub(crate) fn insert(self, value: V) -> &'a mut V {
self.list.push_back(ExpiringElement {
element: self.entry.key().clone(),
expires: self.expiration,
});
&mut self
.entry
.insert(ExpiringElement {
element: value,
expires: self.expiration,
})
.element
}
}
pub(crate) enum Entry<'a, K: 'a, V: 'a> {
Occupied(OccupiedEntry<'a, K, V>),
Vacant(VacantEntry<'a, K, V>),
}
impl<'a, K: 'a, V: 'a> Entry<'a, K, V>
where
K: Eq + std::hash::Hash + Clone,
{
pub(crate) fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(default()),
}
}
pub(crate) fn or_default(self) -> &'a mut V
where
V: Default,
{
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(V::default()),
}
}
}
impl<Key, Value> TimeCache<Key, Value>
where
Key: Eq + std::hash::Hash + Clone,
{
pub(crate) fn new(ttl: Duration) -> Self {
TimeCache {
map: FnvHashMap::default(),
list: VecDeque::new(),
ttl,
}
}
fn remove_expired_keys(&mut self, now: Instant) {
while let Some(element) = self.list.pop_front() {
if element.expires > now {
self.list.push_front(element);
break;
}
if let Occupied(entry) = self.map.entry(element.element.clone()) {
if entry.get().expires <= now {
entry.remove();
}
}
}
}
pub(crate) fn entry(&mut self, key: Key) -> Entry<Key, Value> {
let now = Instant::now();
self.remove_expired_keys(now);
match self.map.entry(key) {
Occupied(entry) => Entry::Occupied(OccupiedEntry { entry }),
Vacant(entry) => Entry::Vacant(VacantEntry {
expiration: now + self.ttl,
entry,
list: &mut self.list,
}),
}
}
#[cfg(test)]
pub(crate) fn clear(&mut self) {
self.map.clear();
self.list.clear();
}
pub(crate) fn contains_key(&self, key: &Key) -> bool {
self.map.contains_key(key)
}
pub(crate) fn get(&self, key: &Key) -> Option<&Value> {
self.map.get(key).map(|e| &e.element)
}
}
pub(crate) struct DuplicateCache<Key>(TimeCache<Key, ()>);
impl<Key> DuplicateCache<Key>
where
Key: Eq + std::hash::Hash + Clone,
{
pub(crate) fn new(ttl: Duration) -> Self {
Self(TimeCache::new(ttl))
}
pub(crate) fn insert(&mut self, key: Key) -> bool {
if let Entry::Vacant(entry) = self.0.entry(key) {
entry.insert(());
true
} else {
false
}
}
pub(crate) fn contains(&self, key: &Key) -> bool {
self.0.contains_key(key)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn cache_added_entries_exist() {
let mut cache = DuplicateCache::new(Duration::from_secs(10));
cache.insert("t");
cache.insert("e");
assert!(!cache.insert("t"));
assert!(!cache.insert("e"));
}
#[test]
fn cache_entries_expire() {
let mut cache = DuplicateCache::new(Duration::from_millis(100));
cache.insert("t");
assert!(!cache.insert("t"));
cache.insert("e");
assert!(!cache.insert("e"));
std::thread::sleep(Duration::from_millis(101));
cache.insert("s");
assert!(cache.insert("t"));
}
}