use std::{
borrow::Borrow,
collections::{HashMap, VecDeque},
hash::Hash,
};
pub(crate) struct Requests<K, V> {
pending: HashMap<K, V>,
order: VecDeque<K>,
handlers: usize,
}
impl<K, V> Default for Requests<K, V> {
fn default() -> Self {
Self {
pending: HashMap::new(),
order: VecDeque::new(),
handlers: 0,
}
}
}
impl<K: Clone + Eq + Hash, V> Requests<K, V> {
pub fn join<Q>(&mut self, key: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
self.pending.get_mut(key)
}
pub fn insert(&mut self, key: K, value: V) -> Result<(), V> {
if self.handlers == 0 {
return Err(value);
}
let prev = self.pending.insert(key.clone(), value);
debug_assert!(prev.is_none(), "insert over a pending request; join it instead");
self.order.push_back(key);
Ok(())
}
pub fn pop(&mut self) -> Option<K> {
self.order.pop_front()
}
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
self.pending.get(key)
}
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
self.pending.remove(key)
}
pub fn remove_if<Q>(&mut self, key: &Q, f: impl FnOnce(&V) -> bool) -> Option<V>
where
K: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
if !self.pending.get(key).is_some_and(f) {
return None;
}
self.pending.remove(key)
}
pub fn has_queued(&self) -> bool {
!self.order.is_empty()
}
pub fn is_empty(&self) -> bool {
self.pending.is_empty()
}
pub fn add_handler(&mut self) {
self.handlers += 1;
}
pub fn remove_handler(&mut self) -> bool {
self.handlers -= 1;
self.handlers == 0
}
pub fn has_handlers(&self) -> bool {
self.handlers > 0
}
pub fn drain_queued(&mut self) -> Vec<V> {
self.order
.drain(..)
.filter_map(|key| self.pending.remove(&key))
.collect()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn insert_gated_on_handlers() {
let mut requests = Requests::<u64, &str>::default();
assert_eq!(requests.insert(1, "a"), Err("a"));
requests.add_handler();
assert!(requests.insert(1, "a").is_ok());
assert_eq!(requests.pop(), Some(1));
assert_eq!(requests.pop(), None);
assert!(requests.remove_handler());
assert_eq!(requests.insert(2, "b"), Err("b"));
}
#[test]
fn popped_stays_joinable_until_removed() {
let mut requests = Requests::<u64, &str>::default();
requests.add_handler();
assert!(requests.insert(1, "a").is_ok());
assert_eq!(requests.pop(), Some(1));
assert_eq!(requests.join(&1), Some(&mut "a"));
assert!(!requests.has_queued());
assert_eq!(requests.remove(&1), Some("a"));
assert!(requests.is_empty());
}
#[test]
fn remove_if_guards_identity() {
let mut requests = Requests::<u64, &str>::default();
requests.add_handler();
assert!(requests.insert(1, "old").is_ok());
*requests.join(&1).unwrap() = "new";
assert_eq!(requests.remove_if(&1, |v| *v == "old"), None);
assert_eq!(requests.remove_if(&1, |v| *v == "new"), Some("new"));
}
#[test]
fn drain_queued_spares_popped() {
let mut requests = Requests::<u64, &str>::default();
requests.add_handler();
assert!(requests.insert(1, "handed out").is_ok());
assert!(requests.insert(2, "queued").is_ok());
assert_eq!(requests.pop(), Some(1));
assert!(requests.remove_handler());
assert_eq!(requests.drain_queued(), vec!["queued"]);
assert_eq!(requests.get(&1), Some(&"handed out"));
assert!(!requests.has_queued());
}
}