use std::collections::HashMap;
use std::hash::Hash;
pub mod crew;
pub mod queue;
pub mod slots;
pub use crew::Crew;
pub use queue::RankedQueue;
pub use slots::SeatSlots;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Seating {
Fresh(usize),
Existing(usize),
Full,
}
impl Seating {
pub fn index(self) -> Option<usize> {
match self {
Seating::Fresh(index) | Seating::Existing(index) => Some(index),
Seating::Full => None,
}
}
pub fn is_fresh(self) -> bool {
matches!(self, Seating::Fresh(_))
}
}
#[derive(Clone, Debug, Default)]
pub struct SeatTable<Key: Eq + Hash + Clone> {
occupied: HashMap<Key, usize>,
free: Vec<usize>,
capacity: usize,
}
impl<Key: Eq + Hash + Clone> SeatTable<Key> {
pub fn new(capacity: usize) -> Self {
Self {
occupied: HashMap::new(),
free: (0..capacity).collect(),
capacity,
}
}
pub fn seat(&mut self, key: Key) -> Seating {
if let Some(index) = self.occupied.get(&key) {
return Seating::Existing(*index);
}
match self.free.pop() {
Some(index) => {
self.occupied.insert(key, index);
Seating::Fresh(index)
}
None => Seating::Full,
}
}
pub fn unseat(&mut self, key: &Key) -> Option<usize> {
let index = self.occupied.remove(key)?;
self.free.push(index);
Some(index)
}
pub fn seat_of(&self, key: &Key) -> Option<usize> {
self.occupied.get(key).copied()
}
pub fn occupants(&self) -> impl Iterator<Item = (&Key, usize)> + '_ {
self.occupied.iter().map(|(key, seat)| (key, *seat))
}
pub fn keys(&self) -> impl Iterator<Item = &Key> + '_ {
self.occupied.keys()
}
pub fn by_seat(&self) -> HashMap<usize, Key> {
self.occupied.iter().map(|(key, seat)| (*seat, key.clone())).collect()
}
pub fn occupied_count(&self) -> usize {
self.occupied.len()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn is_full(&self) -> bool {
self.free.is_empty()
}
pub fn clear(&mut self) {
self.occupied.clear();
self.free = (0..self.capacity).collect();
}
pub fn reseat_all(&mut self, capacity: usize) -> Vec<(Key, usize)> {
let previous: Vec<Key> = self.occupied.keys().cloned().collect();
self.capacity = capacity;
self.occupied.clear();
self.free = (0..capacity).collect();
let mut reseated = Vec::new();
for key in previous {
if let Some(index) = self.free.pop() {
self.occupied.insert(key.clone(), index);
reseated.push((key, index));
}
}
reseated
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SeatState<'a, Key> {
Human(&'a Key),
Held(&'a Key),
Open,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Admission {
Seated { seat: usize, fresh: bool },
Resumed { seat: usize },
Waitlisted { position: usize },
Turned(Turnaway),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Turnaway {
Full,
Locked,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Departure {
Freed { seat: usize },
Held { seat: usize },
Unwaitlisted,
NotPresent,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Shuffle<Key> {
Promoted { key: Key, seat: usize },
Displaced { key: Key, seat: usize },
}
#[derive(Clone, Debug, Default)]
pub struct Roster<Key: Eq + Hash + Clone> {
slots: SeatSlots<Key>,
queue: RankedQueue<Key>,
ranks: HashMap<Key, u32>,
waitlist_on: bool,
hold_on: bool,
locked: bool,
}
impl<Key: Eq + Hash + Clone> Roster<Key> {
pub fn new(capacity: usize) -> Self {
Self {
slots: SeatSlots::new(capacity),
queue: RankedQueue::new(),
ranks: HashMap::new(),
waitlist_on: false,
hold_on: false,
locked: false,
}
}
pub fn with_waitlist(mut self) -> Self {
self.waitlist_on = true;
self
}
pub fn holding_seats(mut self) -> Self {
self.hold_on = true;
self
}
pub fn lock(&mut self) {
self.locked = true;
}
pub fn unlock(&mut self) {
self.locked = false;
}
pub fn is_locked(&self) -> bool {
self.locked
}
pub fn admit(&mut self, key: Key) -> Admission {
self.admit_ranked(key, 0)
}
pub fn admit_ranked(&mut self, key: Key, rank: u32) -> Admission {
if let Some(seat) = self.slots.seat_of(&key) {
if self.slots.is_held(&key) {
self.slots.resume(&key);
return Admission::Resumed { seat };
}
return Admission::Seated { seat, fresh: false };
}
if let Some(position) = self.queue.position(&key) {
return Admission::Waitlisted { position };
}
if !self.locked
&& let Some(seat) = self.slots.first_open() {
self.slots.seat(key.clone(), seat);
self.ranks.insert(key, rank);
return Admission::Seated { seat, fresh: true };
}
if self.waitlist_on {
let position = self.queue.push(key, rank);
return Admission::Waitlisted { position };
}
Admission::Turned(if self.locked { Turnaway::Locked } else { Turnaway::Full })
}
pub fn depart(&mut self, key: &Key) -> Departure {
if let Some(seat) = self.slots.seat_of(key) {
if self.slots.is_held(key) {
return Departure::Held { seat };
}
if self.hold_on {
self.slots.hold(key);
return Departure::Held { seat };
}
self.slots.open(key);
self.ranks.remove(key);
return Departure::Freed { seat };
}
if self.queue.remove(key) {
return Departure::Unwaitlisted;
}
Departure::NotPresent
}
pub fn expire(&mut self, key: &Key) -> Option<usize> {
if !self.slots.is_held(key) {
return None;
}
self.ranks.remove(key);
self.slots.open(key)
}
pub fn resolve(&mut self) -> Vec<Shuffle<Key>> {
let mut shuffles = Vec::new();
if self.locked {
return shuffles;
}
while let Some(seat) = self.slots.first_open() {
let Some((key, rank)) = self.queue.pop_best() else {
break;
};
self.slots.seat(key.clone(), seat);
self.ranks.insert(key.clone(), rank);
shuffles.push(Shuffle::Promoted { key, seat });
}
loop {
let Some((_, best_rank)) = self.queue.best() else {
break;
};
let Some((seat, worst_rank)) = self.worst_seated() else {
break;
};
if best_rank >= worst_rank {
break;
}
let SeatState::Human(displaced) = self.slots.state(seat) else {
break;
};
let displaced = displaced.clone();
self.slots.open(&displaced);
self.ranks.remove(&displaced);
self.queue.push(displaced.clone(), worst_rank);
shuffles.push(Shuffle::Displaced { key: displaced, seat });
let (key, rank) = self.queue.pop_best().expect("best was just observed");
self.slots.seat(key.clone(), seat);
self.ranks.insert(key.clone(), rank);
shuffles.push(Shuffle::Promoted { key, seat });
}
shuffles
}
fn worst_seated(&self) -> Option<(usize, u32)> {
let mut worst: Option<(usize, u32)> = None;
for seat in 0..self.slots.capacity() {
if let SeatState::Human(key) = self.slots.state(seat) {
let rank = self.ranks.get(key).copied().unwrap_or(0);
if worst.is_none_or(|(_, worst_rank)| rank >= worst_rank) {
worst = Some((seat, rank));
}
}
}
worst
}
pub fn seat_of(&self, key: &Key) -> Option<usize> {
self.slots.seat_of(key)
}
pub fn seat_state(&self, seat: usize) -> SeatState<'_, Key> {
self.slots.state(seat)
}
pub fn seats(&self) -> impl Iterator<Item = SeatState<'_, Key>> + '_ {
(0..self.slots.capacity()).map(|seat| self.slots.state(seat))
}
pub fn waiting(&self) -> impl Iterator<Item = &Key> + '_ {
self.queue.iter()
}
pub fn capacity(&self) -> usize {
self.slots.capacity()
}
pub fn occupied_count(&self) -> usize {
self.slots.occupied_count()
}
pub fn is_full(&self) -> bool {
self.occupied_count() == self.capacity()
}
}
#[cfg(test)]
mod tests {
use super::*;
mod reading_the_table {
use super::*;
#[test]
fn occupants_pairs_every_key_with_the_seat_it_holds() {
let mut table: SeatTable<u32> = SeatTable::new(4);
table.seat(10);
table.seat(20);
let mut pairs: Vec<(u32, usize)> = table.occupants().map(|(key, seat)| (*key, seat)).collect();
pairs.sort_unstable();
assert_eq!(pairs.len(), 2);
for (key, seat) in pairs {
assert_eq!(table.seat_of(&key), Some(seat), "the pair agrees with the lookup");
}
}
#[test]
fn occupants_forgets_a_key_that_left() {
let mut table: SeatTable<u32> = SeatTable::new(4);
table.seat(10);
table.seat(20);
table.unseat(&10);
let held: Vec<u32> = table.occupants().map(|(key, _)| *key).collect();
assert_eq!(held, vec![20]);
}
#[test]
fn a_locked_roster_says_so() {
let mut roster: Roster<u32> = Roster::new(4);
assert!(!roster.is_locked());
roster.lock();
assert!(roster.is_locked());
roster.unlock();
assert!(!roster.is_locked());
}
}
#[test]
fn a_joiner_gets_a_seat_and_a_rejoin_gets_the_same_one() {
let mut seats: SeatTable<u64> = SeatTable::new(4);
let first = seats.seat(1);
assert!(first.is_fresh());
let again = seats.seat(1);
assert_eq!(again, Seating::Existing(first.index().unwrap()));
assert_eq!(seats.occupied_count(), 1, "a duplicate join is not a second occupant");
}
#[test]
fn a_new_occupant_is_reported_as_fresh_so_stale_seat_state_is_reset() {
let mut seats: SeatTable<u64> = SeatTable::new(2);
let mut baseline = [0u32; 2];
let seat = seats.seat(1).index().unwrap();
for _ in 0..150 {
baseline.iter_mut().for_each(|b| *b += 1);
}
seats.unseat(&1);
for _ in 0..150 {
baseline.iter_mut().for_each(|b| *b += 1);
}
match seats.seat(2) {
Seating::Fresh(index) => {
assert_eq!(index, seat, "the freed seat is handed out again");
baseline[index] = 0;
}
other => panic!("a new key in a freed seat must be fresh, got {other:?}"),
}
assert_eq!(baseline[seat], 0, "the new occupant inherited the old one's history");
}
#[test]
fn a_full_world_refuses_rather_than_panicking() {
let mut seats: SeatTable<u64> = SeatTable::new(2);
assert!(seats.seat(1).is_fresh());
assert!(seats.seat(2).is_fresh());
assert_eq!(seats.seat(3), Seating::Full);
assert!(seats.is_full());
assert_eq!(seats.seat(3).index(), None);
}
#[test]
fn an_unseated_seat_is_handed_out_again() {
let mut seats: SeatTable<u64> = SeatTable::new(1);
let taken = seats.seat(1).index().unwrap();
assert_eq!(seats.seat(2), Seating::Full);
assert_eq!(seats.unseat(&1), Some(taken));
assert_eq!(seats.seat(2), Seating::Fresh(taken));
}
#[test]
fn unseating_a_stranger_is_not_an_error() {
let mut seats: SeatTable<u64> = SeatTable::new(2);
seats.seat(1);
assert!(seats.unseat(&1).is_some());
assert_eq!(seats.unseat(&1), None);
assert_eq!(seats.unseat(&99), None);
assert_eq!(seats.occupied_count(), 0);
}
#[test]
fn rebuilding_the_world_reseats_everyone_who_still_fits() {
let mut seats: SeatTable<u64> = SeatTable::new(4);
for key in 1..=3 {
seats.seat(key);
}
let reseated = seats.reseat_all(8);
assert_eq!(reseated.len(), 3, "everybody who was connected is in the new world");
assert_eq!(seats.capacity(), 8);
for (key, seat) in &reseated {
assert_eq!(seats.seat_of(key), Some(*seat));
}
let squeezed = seats.reseat_all(2);
assert_eq!(squeezed.len(), 2);
assert!(squeezed.iter().all(|(_, seat)| *seat < 2));
assert_eq!(seats.occupied_count(), 2);
}
#[test]
fn every_seat_index_stays_inside_the_capacity() {
let mut seats: SeatTable<u64> = SeatTable::new(3);
for key in 1..=3 {
let index = seats.seat(key).index().unwrap();
assert!(index < 3, "{index} is outside a capacity of 3");
}
assert_eq!(seats.by_seat().len(), 3, "each seat is held by exactly one key");
}
}
#[cfg(test)]
mod roster_tests {
use super::*;
#[test]
fn plain_seating_matches_the_table() {
let mut roster: Roster<u64> = Roster::new(2);
assert_eq!(roster.admit(1), Admission::Seated { seat: 0, fresh: true });
assert_eq!(roster.admit(1), Admission::Seated { seat: 0, fresh: false });
assert_eq!(roster.admit(2), Admission::Seated { seat: 1, fresh: true });
assert_eq!(roster.admit(3), Admission::Turned(Turnaway::Full));
assert_eq!(roster.depart(&1), Departure::Freed { seat: 0 });
assert_eq!(roster.depart(&1), Departure::NotPresent);
assert_eq!(roster.admit(3), Admission::Seated { seat: 0, fresh: true });
}
#[test]
fn a_lock_turns_away_with_seats_free_and_the_unlock_seats_the_queue() {
let mut roster: Roster<u64> = Roster::new(3).with_waitlist();
roster.admit(1);
roster.lock();
assert_eq!(roster.admit(2), Admission::Waitlisted { position: 0 });
assert_eq!(roster.admit(3), Admission::Waitlisted { position: 1 });
assert!(roster.resolve().is_empty(), "a lock also stops the queue draining");
roster.unlock();
assert_eq!(
roster.resolve(),
vec![Shuffle::Promoted { key: 2, seat: 1 }, Shuffle::Promoted { key: 3, seat: 2 }],
"queue order, at the boundary"
);
}
#[test]
fn without_a_waitlist_a_lock_is_a_turnaway() {
let mut roster: Roster<u64> = Roster::new(3);
roster.lock();
assert_eq!(roster.admit(1), Admission::Turned(Turnaway::Locked));
}
#[test]
fn a_freed_seat_reaches_the_waitlist_at_the_tick_not_at_the_departure() {
let mut roster: Roster<u64> = Roster::new(1).with_waitlist();
roster.admit(1);
assert_eq!(roster.admit(2), Admission::Waitlisted { position: 0 });
roster.depart(&1);
assert_eq!(roster.seat_state(0), SeatState::Open, "not promoted mid-departure");
assert_eq!(roster.resolve(), vec![Shuffle::Promoted { key: 2, seat: 0 }]);
assert_eq!(roster.seat_of(&2), Some(0));
}
#[test]
fn asking_again_while_queued_reports_the_position_rather_than_queueing_twice() {
let mut roster: Roster<u64> = Roster::new(1).with_waitlist();
roster.admit(1);
roster.admit(2);
assert_eq!(roster.admit(2), Admission::Waitlisted { position: 0 });
assert_eq!(roster.waiting().count(), 1);
}
#[test]
fn a_waiter_who_leaves_is_out_of_the_queue() {
let mut roster: Roster<u64> = Roster::new(1).with_waitlist();
roster.admit(1);
roster.admit(2);
roster.admit(3);
assert_eq!(roster.depart(&2), Departure::Unwaitlisted);
roster.depart(&1);
assert_eq!(
roster.resolve(),
vec![Shuffle::Promoted { key: 3, seat: 0 }],
"the leaver's place is not held in the queue"
);
}
#[test]
fn a_held_seat_resumes_intact_and_expires_open() {
let mut roster: Roster<u64> = Roster::new(2).holding_seats();
roster.admit(1);
assert_eq!(roster.depart(&1), Departure::Held { seat: 0 });
assert_eq!(roster.seat_state(0), SeatState::Held(&1));
assert!(roster.is_full() || roster.occupied_count() == 1, "a held seat is not free");
assert_eq!(roster.admit(2), Admission::Seated { seat: 1, fresh: true }, "holds do not block other seats");
assert_eq!(roster.admit(1), Admission::Resumed { seat: 0 });
assert_eq!(roster.depart(&1), Departure::Held { seat: 0 });
assert_eq!(roster.expire(&1), Some(0));
assert_eq!(roster.expire(&1), None, "an expiry lands once");
assert_eq!(roster.seat_state(0), SeatState::Open);
}
#[test]
fn an_expired_hold_feeds_the_waitlist_on_the_tick() {
let mut roster: Roster<u64> = Roster::new(1).holding_seats().with_waitlist();
roster.admit(1);
roster.depart(&1);
assert_eq!(roster.admit(2), Admission::Waitlisted { position: 0 }, "a held seat is not open");
roster.expire(&1);
assert_eq!(roster.resolve(), vec![Shuffle::Promoted { key: 2, seat: 0 }]);
}
#[test]
fn a_better_ranked_waiter_displaces_the_worst_seated() {
let mut roster: Roster<u64> = Roster::new(2).with_waitlist();
roster.admit_ranked(10, 1);
roster.admit_ranked(11, 1);
assert_eq!(roster.admit_ranked(1, 0), Admission::Waitlisted { position: 0 });
assert_eq!(roster.admit_ranked(2, 0), Admission::Waitlisted { position: 1 });
assert_eq!(
roster.resolve(),
vec![
Shuffle::Displaced { key: 11, seat: 1 },
Shuffle::Promoted { key: 1, seat: 1 },
Shuffle::Displaced { key: 10, seat: 0 },
Shuffle::Promoted { key: 2, seat: 0 },
]
);
let displaced: Vec<u64> = roster.waiting().copied().collect();
assert_eq!(displaced, vec![11, 10], "the bots wait in their band");
}
#[test]
fn equal_ranks_never_displace_each_other() {
let mut roster: Roster<u64> = Roster::new(1).with_waitlist();
roster.admit(1);
roster.admit(2);
assert!(roster.resolve().is_empty(), "waiting your turn is not being outranked");
}
#[test]
fn a_held_seat_is_never_displaced() {
let mut roster: Roster<u64> = Roster::new(1).holding_seats().with_waitlist();
roster.admit_ranked(10, 1);
roster.depart(&10);
roster.admit_ranked(1, 0);
assert!(roster.resolve().is_empty());
assert_eq!(roster.seat_state(0), SeatState::Held(&10));
}
#[test]
fn a_second_disconnect_report_keeps_the_hold() {
let mut roster: Roster<u64> = Roster::new(1).holding_seats();
roster.admit(1);
assert_eq!(roster.depart(&1), Departure::Held { seat: 0 });
assert_eq!(roster.depart(&1), Departure::Held { seat: 0 }, "a repeat report must not break the hold");
assert_eq!(roster.seat_state(0), SeatState::Held(&1));
}
#[test]
fn open_seats_read_as_whatever_the_game_says_they_are() {
let mut roster: Roster<u64> = Roster::new(4);
roster.admit(7);
let occupants: Vec<bool> = roster.seats().map(|state| matches!(state, SeatState::Open)).collect();
assert_eq!(occupants.iter().filter(|open| **open).count(), 3, "three seats for bots");
}
}