use super::bid::BidVector;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TableAction {
Update,
Reset,
Leave,
}
fn stamp(v: &BidVector, roster: &[u8], slot: u8) -> u16 {
roster
.iter()
.position(|s| *s == slot)
.and_then(|i| v.s.get(i).copied())
.unwrap_or(0)
}
pub fn decide(
j: usize,
sender: u8,
receiver: u8,
theirs: &BidVector,
mine: &BidVector,
roster: &[u8],
) -> TableAction {
use TableAction::{Leave, Reset, Update};
let (Some(&yk), Some(&yi)) = (theirs.y.get(j), mine.y.get(j)) else {
return Leave;
};
let (Some(&zk), Some(&zi)) = (theirs.z.get(j), mine.z.get(j)) else {
return Leave;
};
let sk = |slot: u8| stamp(theirs, roster, slot);
let si = |slot: u8| stamp(mine, roster, slot);
match (zk, zi) {
(Some(k), Some(i)) if k == sender && i == receiver => {
if yk > yi || (yk == yi && sender < receiver) {
Update
} else {
Leave
}
}
(Some(k), Some(i)) if k == sender && i == sender => Update,
(Some(k), Some(m)) if k == sender => {
if sk(m) > si(m) || yk > yi {
Update
} else {
Leave
}
}
(Some(k), None) if k == sender => Update,
(Some(k), Some(i)) if k == receiver && i == receiver => Leave,
(Some(k), Some(i)) if k == receiver && i == sender => Reset,
(Some(k), Some(m)) if k == receiver => {
if sk(m) > si(m) {
Reset
} else {
Leave
}
}
(Some(k), None) if k == receiver => Leave,
(Some(m), Some(i)) if i == receiver => {
if sk(m) > si(m) && yk > yi {
Update
} else {
Leave
}
}
(Some(m), Some(k)) if k == sender => {
if sk(m) > si(m) {
Update
} else {
Reset
}
}
(Some(m), Some(n)) if m == n => {
if sk(m) > si(m) {
Update
} else {
Leave
}
}
(Some(m), Some(n)) => {
if sk(m) > si(m) && (sk(n) > si(n) || yk > yi) {
Update
} else if sk(n) > si(n) && si(m) > sk(m) {
Reset
} else {
Leave
}
}
(Some(m), None) => {
if sk(m) > si(m) {
Update
} else {
Leave
}
}
(None, Some(i)) if i == receiver => Leave,
(None, Some(k)) if k == sender => Update,
(None, Some(m)) => {
if sk(m) > si(m) {
Update
} else {
Leave
}
}
(None, None) => Leave,
}
}
pub fn apply(action: TableAction, j: usize, theirs: &BidVector, mine: &mut BidVector) -> bool {
let (before_y, before_z) = (mine.y[j], mine.z[j]);
match action {
TableAction::Update => {
mine.y[j] = theirs.y[j];
mine.z[j] = theirs.z[j];
}
TableAction::Reset => {
mine.y[j] = 0.0;
mine.z[j] = None;
}
TableAction::Leave => {}
}
mine.y[j].to_bits() != before_y.to_bits() || mine.z[j] != before_z
}
pub fn merge_stamps(
mine: &mut BidVector,
theirs: &BidVector,
roster: &[u8],
sender: u8,
now: u16,
) -> bool {
let mut changed = false;
for (i, slot) in roster.iter().enumerate() {
let Some(entry) = mine.s.get_mut(i) else {
continue;
};
let next = if *slot == sender {
now
} else {
(*theirs.s.get(i).unwrap_or(&0)).max(*entry)
};
if next != *entry {
*entry = next;
changed = true;
}
}
changed
}
#[cfg(test)]
mod tests {
use super::TableAction::{Leave, Reset, Update};
use super::*;
const ROSTER: [u8; 4] = [1, 2, 3, 4];
const ME: u8 = 1;
const THEM: u8 = 2;
const OTHER: u8 = 3;
const FOURTH: u8 = 4;
fn vecs(
their_bid: f32,
their_z: Option<u8>,
my_bid: f32,
my_z: Option<u8>,
) -> (BidVector, BidVector) {
let mut theirs = BidVector::new(1, ROSTER.len());
let mut mine = BidVector::new(1, ROSTER.len());
theirs.y[0] = their_bid;
theirs.z[0] = their_z;
mine.y[0] = my_bid;
mine.z[0] = my_z;
(theirs, mine)
}
fn row(their_bid: f32, their_z: Option<u8>, my_bid: f32, my_z: Option<u8>) -> TableAction {
let (theirs, mine) = vecs(their_bid, their_z, my_bid, my_z);
decide(0, THEM, ME, &theirs, &mine, &ROSTER)
}
#[test]
fn sender_holds_it_and_outbids_me() {
assert_eq!(row(9.0, Some(THEM), 5.0, Some(ME)), Update);
assert_eq!(row(1.0, Some(THEM), 5.0, Some(ME)), Leave);
let (theirs, mine) = vecs(5.0, Some(THEM), 5.0, Some(ME));
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Leave);
let (theirs, mine) = vecs(5.0, Some(ME), 5.0, Some(THEM));
assert_eq!(decide(0, ME, THEM, &theirs, &mine, &ROSTER), Update);
}
#[test]
fn sender_confirms_what_i_already_believed_about_it() {
assert_eq!(row(3.0, Some(THEM), 9.0, Some(THEM)), Update);
}
#[test]
fn sender_holds_it_but_i_credit_a_third_party() {
assert_eq!(row(1.0, Some(THEM), 5.0, Some(OTHER)), Leave);
assert_eq!(row(9.0, Some(THEM), 5.0, Some(OTHER)), Update);
let (mut theirs, mut mine) = vecs(1.0, Some(THEM), 5.0, Some(OTHER));
theirs.s[2] = 50;
mine.s[2] = 10;
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Update);
theirs.s[2] = 5;
mine.s[2] = 50;
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Leave);
}
#[test]
fn sender_holds_it_and_i_thought_nobody_did() {
assert_eq!(row(1.0, Some(THEM), 0.0, None), Update);
}
#[test]
fn sender_thinks_i_hold_it() {
assert_eq!(row(4.0, Some(ME), 4.0, Some(ME)), Leave, "we agree");
assert_eq!(row(4.0, Some(ME), 4.0, Some(THEM)), Reset);
assert_eq!(row(4.0, Some(ME), 0.0, None), Leave);
let (mut theirs, mut mine) = vecs(4.0, Some(ME), 4.0, Some(OTHER));
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Leave);
theirs.s[2] = 9;
mine.s[2] = 1;
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Reset);
}
#[test]
fn sender_credits_a_third_party() {
let (mut theirs, mine) = vecs(9.0, Some(OTHER), 5.0, Some(ME));
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Leave);
theirs.s[2] = 9;
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Update);
theirs.y[0] = 1.0;
assert_eq!(
decide(0, THEM, ME, &theirs, &mine, &ROSTER),
Leave,
"fresher news alone must not take a task off its holder"
);
let (mut theirs, mine) = vecs(9.0, Some(OTHER), 5.0, Some(THEM));
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Reset);
theirs.s[2] = 9;
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Update);
let (mut theirs, mut mine) = vecs(9.0, Some(OTHER), 5.0, Some(OTHER));
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Leave);
theirs.s[2] = 3;
mine.s[2] = 1;
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Update);
let (mut theirs, mut mine) = vecs(9.0, Some(OTHER), 5.0, Some(FOURTH));
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Leave);
theirs.s[2] = 9; theirs.s[3] = 9; assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Update);
theirs.s[3] = 0;
assert_eq!(
decide(0, THEM, ME, &theirs, &mine, &ROSTER),
Update,
"better bid"
);
theirs.y[0] = 1.0;
theirs.s[2] = 0;
mine.s[2] = 9;
theirs.s[3] = 9;
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Reset);
let (mut theirs, mine) = vecs(9.0, Some(OTHER), 0.0, None);
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Leave);
theirs.s[2] = 1;
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Update);
}
#[test]
fn sender_thinks_the_task_is_unheld() {
assert_eq!(
row(0.0, None, 5.0, Some(ME)),
Leave,
"I hold it; I know better"
);
assert_eq!(row(0.0, None, 5.0, Some(THEM)), Update, "it released it");
assert_eq!(row(0.0, None, 0.0, None), Leave);
let (mut theirs, mut mine) = vecs(0.0, None, 5.0, Some(OTHER));
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Leave);
theirs.s[2] = 7;
mine.s[2] = 2;
assert_eq!(decide(0, THEM, ME, &theirs, &mine, &ROSTER), Update);
}
#[test]
fn an_out_of_range_task_index_is_a_no_op() {
let (theirs, mine) = vecs(9.0, Some(THEM), 0.0, None);
assert_eq!(decide(7, THEM, ME, &theirs, &mine, &ROSTER), Leave);
}
#[test]
fn apply_reports_whether_it_changed_anything() {
let (theirs, mut mine) = vecs(9.0, Some(THEM), 5.0, Some(ME));
assert!(apply(Update, 0, &theirs, &mut mine));
assert_eq!((mine.y[0], mine.z[0]), (9.0, Some(THEM)));
assert!(
!apply(Update, 0, &theirs, &mut mine),
"idempotent second apply"
);
assert!(apply(Reset, 0, &theirs, &mut mine));
assert_eq!((mine.y[0], mine.z[0]), (0.0, None));
assert!(!apply(Reset, 0, &theirs, &mut mine));
assert!(!apply(Leave, 0, &theirs, &mut mine));
}
#[test]
fn stamps_take_the_sender_first_hand_and_merge_the_rest_by_recency() {
let mut mine = BidVector::new(1, ROSTER.len());
let mut theirs = BidVector::new(1, ROSTER.len());
mine.s = vec![5, 5, 40, 5];
theirs.s = vec![1, 99, 10, 7];
assert!(merge_stamps(&mut mine, &theirs, &ROSTER, THEM, 100));
assert_eq!(mine.s[1], 100, "the sender was heard directly");
assert_eq!(mine.s[0], 5, "my own entry is not overwritten by a rumour");
assert_eq!(mine.s[2], 40, "mine was fresher");
assert_eq!(mine.s[3], 7, "theirs was fresher");
assert!(
!merge_stamps(&mut mine, &theirs, &ROSTER, THEM, 100),
"idempotent"
);
}
}