use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::time::Duration;
use plaza::agent::AgentId;
#[derive(Debug, Clone, Default)]
pub struct SeatReservations<ID: AgentId> {
held: HashMap<ID, Duration>,
elapsed: Duration,
window: Option<Duration>,
}
impl<ID: AgentId> SeatReservations<ID> {
pub fn new() -> Self {
Self {
held: HashMap::new(),
elapsed: Duration::ZERO,
window: None,
}
}
pub fn with_expiry(window: Duration) -> Self {
Self {
window: Some(window),
..Self::new()
}
}
pub fn tick(&mut self, delta: Duration) -> Vec<ID> {
self.elapsed = self.elapsed.saturating_add(delta);
let Some(window) = self.window else {
return Vec::new();
};
let lapsed: Vec<ID> = self
.held
.iter()
.filter(|(_, made)| self.elapsed.saturating_sub(**made) >= window)
.map(|(player, _)| player.clone())
.collect();
for player in &lapsed {
self.held.remove(player);
}
lapsed
}
pub fn expiry(&self) -> Option<Duration> {
self.window
}
pub fn reserve(&mut self, player: ID) -> bool {
match self.held.entry(player) {
Entry::Occupied(_) => false,
Entry::Vacant(slot) => {
slot.insert(self.elapsed);
true
}
}
}
pub fn consume(&mut self, player: &ID) -> bool {
self.held.remove(player).is_some()
}
pub fn withdraw(&mut self, player: &ID) -> bool {
self.held.remove(player).is_some()
}
pub fn holds(&self, player: &ID) -> bool {
self.held.contains_key(player)
}
pub fn count(&self) -> usize {
self.held.len()
}
pub fn is_empty(&self) -> bool {
self.held.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &ID> {
self.held.keys()
}
pub fn clear(&mut self) {
self.held.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_reservation_is_spent_once() {
let mut seats = SeatReservations::new();
assert!(seats.reserve(1));
assert!(seats.consume(&1));
assert!(!seats.consume(&1), "a second connection is not a second seat");
}
#[test]
fn an_unreserved_arrival_is_not_admitted() {
let mut seats: SeatReservations<u32> = SeatReservations::new();
assert!(!seats.consume(&1));
}
#[test]
fn reserving_twice_reports_the_duplicate() {
let mut seats = SeatReservations::new();
assert!(seats.reserve(1));
assert!(!seats.reserve(1));
assert_eq!(seats.count(), 1);
}
#[test]
fn the_lobby_can_withdraw() {
let mut seats = SeatReservations::new();
seats.reserve(1);
assert!(seats.withdraw(&1));
assert!(!seats.consume(&1));
}
#[test]
fn a_reservation_survives_everything_except_a_withdrawal() {
let mut seats = SeatReservations::new();
seats.reserve(1);
assert!(seats.holds(&1));
assert!(seats.consume(&1));
}
#[test]
fn without_a_window_nothing_ever_lapses() {
let mut seats = SeatReservations::new();
seats.reserve(1);
assert!(seats.tick(Duration::from_secs(86_400)).is_empty());
assert!(seats.holds(&1), "today's behaviour");
}
#[test]
fn a_reservation_lapses_once_its_window_has_passed() {
let mut seats = SeatReservations::with_expiry(Duration::from_secs(45));
seats.reserve(1);
assert!(seats.tick(Duration::from_secs(44)).is_empty(), "not yet");
assert!(seats.holds(&1));
assert_eq!(seats.tick(Duration::from_secs(1)), vec![1], "the lapsed are handed back");
assert!(!seats.holds(&1));
assert!(!seats.consume(&1), "and the seat is gone, not merely unreported");
}
#[test]
fn the_window_runs_from_each_reservation_rather_than_from_the_clock() {
let mut seats = SeatReservations::with_expiry(Duration::from_secs(45));
seats.reserve(1);
seats.tick(Duration::from_secs(30));
seats.reserve(2);
assert_eq!(seats.tick(Duration::from_secs(15)), vec![1], "only the older one is due");
assert!(seats.holds(&2));
}
#[test]
fn re_reserving_does_not_buy_a_fresh_window() {
let mut seats = SeatReservations::with_expiry(Duration::from_secs(45));
seats.reserve(1);
seats.tick(Duration::from_secs(40));
assert!(!seats.reserve(1), "already held");
assert_eq!(seats.tick(Duration::from_secs(5)), vec![1]);
}
#[test]
fn a_player_who_arrived_never_lapses() {
let mut seats = SeatReservations::with_expiry(Duration::from_secs(45));
seats.reserve(1);
assert!(seats.consume(&1));
assert!(seats.tick(Duration::from_secs(90)).is_empty());
}
#[test]
fn a_lapse_reports_every_player_it_dropped() {
let mut seats = SeatReservations::with_expiry(Duration::from_secs(45));
seats.reserve(1);
seats.reserve(2);
seats.reserve(3);
seats.consume(&2);
let mut lapsed = seats.tick(Duration::from_secs(46));
lapsed.sort();
assert_eq!(lapsed, vec![1, 3]);
assert!(seats.is_empty());
}
#[test]
fn outstanding_counts_what_was_never_dialled() {
let mut seats = SeatReservations::new();
seats.reserve(1);
seats.reserve(2);
seats.consume(&1);
assert_eq!(seats.count(), 1);
assert_eq!(seats.iter().collect::<Vec<_>>(), vec![&2]);
}
}