#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum Place {
Seated(u8),
Watching,
LateWatching,
#[default]
Queued,
}
#[derive(Clone, PartialEq, Debug, Default)]
pub struct Peer {
pub place: Place,
pub name: String,
pub watch: bool,
pub silence: f32,
pub pick: Option<u8>,
pub bot: bool,
pub goodbye: Goodbye,
pub pause: Option<u32>,
pub round_trips: RoundTrips,
pub newest: Option<u32>,
pub wants: Option<[u32; crate::sim::MAX_PLAYERS]>,
pub frozen: bool,
pub seat_wish: Option<u8>,
}
const ROUND_TRIPS: usize = 5;
const FRESH_FOR: f32 = 8.0;
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub struct RoundTrips {
samples: [(f32, f32); ROUND_TRIPS],
taken: usize,
}
impl RoundTrips {
pub fn note(&mut self, seconds: f32) {
self.samples[self.taken % ROUND_TRIPS] = (seconds, 0.0);
self.taken += 1;
}
pub fn age(&mut self, delta: f32) {
for (_, age) in &mut self.samples {
*age += delta;
}
}
pub fn telling(&self) -> Option<f32> {
let remembered = &self.samples[..self.taken.min(ROUND_TRIPS)];
let fresh: Vec<f32> = remembered
.iter()
.filter(|(_, age)| *age <= FRESH_FOR)
.map(|(seconds, _)| *seconds)
.collect();
let mut chosen = match fresh.is_empty() {
true => remembered.iter().map(|(seconds, _)| *seconds).collect(),
false => fresh,
};
chosen.sort_by(f32::total_cmp);
let hitch = usize::from(chosen.len() >= 3);
chosen.iter().rev().nth(hitch).copied()
}
}
impl Peer {
pub fn seat(&self) -> Option<u8> {
match self.place {
Place::Seated(seat) => Some(seat),
Place::Watching | Place::LateWatching | Place::Queued => None,
}
}
pub fn watches(&self) -> bool {
self.watch
}
pub fn wants_a_chair(&self) -> bool {
!self.watch && self.seat().is_none()
}
pub fn left(&self) -> bool {
self.goodbye.said()
}
}
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub struct Goodbye(Option<f32>);
impl Goodbye {
pub fn heard(&mut self) {
self.0 = None;
}
pub fn spoke(&mut self) {
self.0.get_or_insert(0.0);
}
pub fn age(&mut self, delta: f32) {
if let Some(since) = &mut self.0 {
*since += delta;
}
}
pub fn said(&self) -> bool {
self.0.is_some()
}
pub fn stood(&self, grace: f32) -> bool {
self.0.is_some_and(|since| since >= grace)
}
}
pub fn renumber(index: &mut usize, gone: usize) -> bool {
match (*index).cmp(&gone) {
std::cmp::Ordering::Less => true,
std::cmp::Ordering::Equal => false,
std::cmp::Ordering::Greater => {
*index -= 1;
true
}
}
}
#[derive(Clone, PartialEq, Debug, Default)]
pub struct PeerBook(Vec<Peer>);
impl PeerBook {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get(&self, peer: usize) -> Option<&Peer> {
self.0.get(peer)
}
pub fn iter(&self) -> impl Iterator<Item = &Peer> {
self.0.iter()
}
pub fn at_the_table(&self) -> impl Iterator<Item = (usize, &Peer)> {
self.by_place()
.filter(|&(_, _, sits)| sits)
.map(|(at, peer, _)| (at, peer))
}
pub fn off_the_table(&self) -> impl Iterator<Item = (usize, &Peer)> {
self.by_place()
.filter(|&(_, _, sits)| !sits)
.map(|(at, peer, _)| (at, peer))
}
fn by_place(&self) -> impl Iterator<Item = (usize, &Peer, bool)> {
let mut chairs = crate::sim::MAX_PLAYERS - 1;
self.0.iter().enumerate().map(move |(at, peer)| {
let sits = !peer.watches() && chairs > 0;
chairs -= usize::from(sits);
(at, peer, sits)
})
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Peer> {
self.0.iter_mut()
}
pub fn reach(&mut self, count: usize) {
if self.0.len() < count {
self.0.resize_with(count, Peer::default);
}
}
pub fn row(&mut self, peer: usize) -> &mut Peer {
self.reach(peer + 1);
&mut self.0[peer]
}
pub fn fit(&mut self, count: usize) {
self.reach(count);
self.0.truncate(count);
}
pub fn forget(&mut self, peer: usize) {
if peer < self.0.len() {
self.0.remove(peer);
}
}
pub fn heard(&mut self, peer: usize) {
let row = self.row(peer);
row.silence = 0.0;
row.goodbye.heard();
}
pub fn answered(&mut self, peer: usize, stamp: u32) {
if let Some(seconds) = crate::transport::round_trip_since(stamp) {
self.row(peer).round_trips.note(seconds);
}
}
pub fn said_goodbye(&mut self, peer: usize) {
let row = self.row(peer);
row.silence = f32::INFINITY;
row.goodbye.spoke();
}
pub fn age(&mut self, delta: f32) {
for peer in &mut self.0 {
peer.silence += delta;
peer.goodbye.age(delta);
peer.round_trips.age(delta);
}
}
pub fn hush(&mut self) {
for peer in &mut self.0 {
peer.silence = 0.0;
peer.goodbye.heard();
}
}
pub fn deal(&mut self, plan: &[Option<u8>]) {
self.fit(plan.len());
for (peer, slot) in self.0.iter_mut().zip(plan) {
peer.newest = None;
peer.wants = None;
peer.place = match slot {
Some(seat) => Place::Seated(*seat),
None => Place::Watching,
};
}
}
pub fn seat_plan(&self, peers: usize) -> Vec<Option<u8>> {
let mut next = 1u8; (0..peers)
.map(|peer| {
let watches = self.get(peer).is_some_and(Peer::watches);
if watches || usize::from(next) >= crate::sim::MAX_PLAYERS {
return None;
}
let seat = next;
next += 1;
Some(seat)
})
.collect()
}
pub fn freeze(&mut self, plan: &[Option<u8>]) {
self.reach(plan.len());
for (row, seat) in self.0.iter_mut().zip(plan) {
row.frozen = seat.is_some();
}
}
pub fn frozen_plan(&self, peers: usize) -> Vec<Option<u8>> {
let mut next = 1u8; (0..peers)
.map(|peer| {
let sits = self
.get(peer)
.is_some_and(|row| row.frozen && !row.watches());
if !sits {
return None;
}
let seat = next;
next += 1;
Some(seat)
})
.collect()
}
pub fn vouch(
&self,
from: usize,
claimed: crate::transport::WireName,
) -> crate::transport::WireName {
match self.get(from).map_or("", |peer| peer.name.as_str()) {
"" => claimed,
known => crate::transport::wire_name(known),
}
}
pub fn to_forget(&self, gone: impl Fn(&Peer) -> bool) -> Vec<usize> {
(0..self.0.len())
.rev()
.filter(|&peer| gone(&self.0[peer]))
.collect()
}
pub fn planned(&self) -> usize {
self.0
.iter()
.take_while(|peer| matches!(peer.place, Place::Seated(_) | Place::Watching))
.count()
}
pub fn follows_by_plan(&self, peer: usize) -> bool {
peer < self.planned()
|| self
.get(peer)
.is_some_and(|row| row.place == Place::LateWatching)
}
pub fn holder_of(&self, seat: u8) -> Option<usize> {
self.0.iter().position(|peer| peer.seat() == Some(seat))
}
pub fn seat_of(&self, peer: usize) -> Option<u8> {
self.0.get(peer).and_then(Peer::seat)
}
pub fn unseat(&mut self) {
for peer in &mut self.0 {
peer.place = Place::Queued;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn indices_follow_their_peers_down() {
let mut said = vec![0, 1, 2, 3, 4];
for gone in [3, 1] {
said.retain_mut(|at| renumber(at, gone));
}
assert_eq!(said, [0, 1, 2], "peers 0, 2 and 4, renumbered");
}
#[test]
fn forgetting_a_peer_moves_the_rows_behind_it_together() {
let mut peers = PeerBook::default();
peers.row(0).name = "Bo".into();
peers.row(1).name = "Cy".into();
peers.row(2).name = "Dee".into();
peers.row(2).watch = true;
peers.row(2).silence = 3.0;
peers.deal(&[Some(1), Some(2), None]);
peers.forget(0);
assert_eq!(peers.len(), 2);
assert_eq!(peers.get(0).map(|p| p.name.as_str()), Some("Cy"));
let dee = peers.get(1).expect("Dee moved down");
assert_eq!(dee.name, "Dee");
assert!(dee.watches(), "the wish followed Dee down");
assert_eq!(dee.silence, 3.0, "and so did the silence");
assert_eq!(dee.place, Place::Watching);
assert_eq!(peers.seat_of(0), Some(2), "Cy keeps the chair it held");
}
#[test]
fn the_plan_is_the_leading_run_and_the_rest_are_in_line() {
let mut peers = PeerBook::default();
peers.deal(&[Some(1), None]);
assert_eq!(peers.planned(), 2);
peers.heard(3);
assert_eq!(peers.len(), 4, "grown to reach the stranger");
assert_eq!(peers.planned(), 2, "and the stranger is in line");
assert_eq!(peers.holder_of(1), Some(0));
assert_eq!(peers.holder_of(0), None, "the host's chair is nobody's");
peers.deal(&[Some(1)]);
assert_eq!(peers.planned(), 1);
assert_eq!(peers.len(), 1);
}
#[test]
fn unseating_keeps_the_watchers_and_frees_the_chairs() {
let mut peers = PeerBook::default();
peers.deal(&[Some(1), None, Some(2), None]);
peers.row(1).watch = true;
peers.row(2).watch = true;
peers.unseat();
let watching: Vec<bool> = peers.iter().map(Peer::watches).collect();
assert_eq!(watching, [false, true, true, false]);
assert!(peers.iter().all(|p| p.seat().is_none()));
assert_eq!(peers.planned(), 0);
}
#[test]
fn only_the_table_and_the_watchers_follow_the_round() {
let mut peers = PeerBook::default();
peers.reach(4);
peers.deal(&[Some(1), None]);
peers.reach(4);
assert_eq!(peers.planned(), 2, "a seat and a watching place");
assert!(peers.follows_by_plan(0), "the seated peer");
assert!(peers.follows_by_plan(1), "the watcher, in step from zero");
assert!(!peers.follows_by_plan(2), "the one in line");
assert!(!peers.follows_by_plan(3), "and the one behind it");
}
#[test]
fn a_late_watcher_follows_the_round_outside_the_plan() {
let mut peers = PeerBook::default();
peers.deal(&[Some(1), None]);
peers.reach(4);
peers.row(2).watch = true;
peers.row(2).place = Place::LateWatching;
assert_eq!(peers.planned(), 2, "the plan is still the launch's");
assert!(
peers.follows_by_plan(2),
"the late watcher is sent the round"
);
assert!(!peers.follows_by_plan(3), "the one in line is not");
assert!(peers.get(2).is_some_and(Peer::watches));
assert_eq!(peers.seat_of(2), None);
let mut peers = PeerBook::default();
peers.deal(&[Some(1)]);
peers.row(1).place = Place::LateWatching;
peers.row(2).place = Place::Queued;
assert_eq!(peers.planned(), 1);
}
#[test]
fn a_book_with_no_plan_has_nobody_following_by_it() {
let mut peers = PeerBook::default();
peers.reach(2);
assert_eq!(peers.planned(), 0, "nothing was ever dealt");
assert!(!peers.follows_by_plan(0));
assert!(!peers.follows_by_plan(1));
peers.row(1).place = Place::LateWatching;
assert!(peers.follows_by_plan(1), "but a late watcher does");
}
}