extern crate alloc;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use crate::kairos::Kairos;
use crate::metis::{Anchor, Dot, Locus, Metatheses, Rhapsody};
use proptest::prelude::*;
use super::{Moves, Text, clock, locus_at, move_to, type_after};
const REPLICAS: usize = 3;
#[derive(Clone, Debug)]
enum Op {
Insert {
replica: usize,
caret_pick: usize,
},
Delete {
replica: usize,
victim_pick: usize,
},
Move {
replica: usize,
target_pick: usize,
dest_pick: usize,
before: bool,
},
Undo {
replica: usize,
testimony_pick: usize,
},
Gossip {
from: usize,
to: usize,
},
}
fn arb_ops() -> impl Strategy<Value = Vec<Op>> {
let op = prop_oneof![
3 => (0..REPLICAS, 0usize..8)
.prop_map(|(replica, caret_pick)| Op::Insert { replica, caret_pick }),
1 => (0..REPLICAS, 0usize..8)
.prop_map(|(replica, victim_pick)| Op::Delete { replica, victim_pick }),
3 => (0..REPLICAS, 0usize..8, 0usize..8, any::<bool>()).prop_map(
|(replica, target_pick, dest_pick, before)| Op::Move {
replica,
target_pick,
dest_pick,
before,
}
),
1 => (0..REPLICAS, 0usize..8)
.prop_map(|(replica, testimony_pick)| Op::Undo { replica, testimony_pick }),
3 => (0..REPLICAS, 0..REPLICAS).prop_map(|(from, to)| Op::Gossip { from, to }),
];
prop::collection::vec(op, 0..32)
}
fn gossip(fleet: &mut [(Text, Moves)], from: usize, to: usize) {
let text_delta = fleet[from].0.owed_to(fleet[to].0.state().context());
let move_delta = fleet[from].1.owed_to(fleet[to].1.state().context());
let sender = u32::try_from(from + 1).unwrap();
let _ = fleet[to].0.absorb(sender, &text_delta);
let _ = fleet[to].1.absorb(sender, &move_delta);
}
fn run(ops: &[Op]) -> Vec<(Text, Moves)> {
let mut fleet: Vec<(Text, Moves)> = (0..REPLICAS)
.map(|i| {
let station = u32::try_from(i + 1).unwrap();
(Text::new(station), Moves::new(station))
})
.collect();
let clocks: Vec<_> = (0..REPLICAS)
.map(|i| clock(u32::try_from(i + 1).unwrap()))
.collect();
for op in ops {
match *op {
Op::Insert {
replica,
caret_pick,
} => {
let visible = fleet[replica].0.state().store().order();
let caret = if visible.is_empty() {
None
} else {
Some(visible[caret_pick % visible.len()].into())
};
let _ = type_after(&mut fleet[replica].0, &clocks[replica], caret);
}
Op::Delete {
replica,
victim_pick,
} => {
let visible = fleet[replica].0.state().store().order();
if visible.is_empty() {
continue;
}
let victim = visible[victim_pick % visible.len()];
let mut gone = crate::metis::DotSet::new();
let _ = gone.insert(victim);
let _ = fleet[replica].0.retract(gone);
}
Op::Move {
replica,
target_pick,
dest_pick,
before,
} => {
let woven: Vec<Dot> = fleet[replica].0.state().store().woven().dots().collect();
if woven.len() < 2 {
continue;
}
let target = woven[target_pick % woven.len()];
let dest = woven[dest_pick % woven.len()];
let anchor = if before {
Anchor::Before(dest.into())
} else {
Anchor::After(dest.into())
};
let to = locus_at(&clocks[replica], anchor);
let _ = move_to(&mut fleet[replica].1, target.into(), to);
}
Op::Undo {
replica,
testimony_pick,
} => {
let held: Vec<Dot> = fleet[replica]
.1
.state()
.store()
.iter()
.map(|(dot, _)| dot)
.collect();
if held.is_empty() {
continue;
}
let victim = held[testimony_pick % held.len()];
let mut undo = crate::metis::DotSet::new();
let _ = undo.insert(victim);
let _ = fleet[replica].1.retract(undo);
}
Op::Gossip { from, to } => {
if from != to {
gossip(&mut fleet, from, to);
}
}
}
}
for _ in 0..2 {
for from in 0..REPLICAS {
for to in 0..REPLICAS {
if from != to {
gossip(&mut fleet, from, to);
}
}
}
}
fleet
}
type Replay = (Vec<Dot>, Vec<Dot>, Vec<Dot>);
fn anchor_identity(anchor: Anchor) -> Option<Dot> {
anchor.dot().and_then(|at| Dot::try_from(at).ok())
}
fn reference_recension(text: &Rhapsody, moves: &Metatheses) -> Replay {
struct Play {
key: (Kairos, Dot),
target: Dot,
locus: Locus,
witness: Dot,
}
enum Frame {
Visit(Dot),
Emit(Dot),
}
let mut effective: BTreeMap<Dot, Locus> = BTreeMap::new();
let born: Vec<Dot> = text.woven().dots().collect();
let born_set: BTreeSet<Dot> = born.iter().copied().collect();
for &dot in &born {
let locus = text.locus(dot).unwrap();
let _ = effective.insert(dot, locus);
}
let mut plays: Vec<Play> = Vec::new();
let mut pending: Vec<Dot> = Vec::new();
for (dot, metathesis) in moves {
let target = Dot::try_from(metathesis.target)
.ok()
.filter(|target| born_set.contains(target));
if let Some(target) = target {
plays.push(Play {
key: (metathesis.to.rank, dot),
target,
locus: metathesis.to,
witness: dot,
});
} else {
pending.push(dot);
}
}
plays.sort_by_key(|play| play.key);
let budget = born.len() + plays.len() + 1;
let mut refused: Vec<Dot> = Vec::new();
for play in &plays {
let mut cursor = anchor_identity(play.locus.anchor);
let mut steps = 0usize;
let mut cycles = false;
while let Some(at) = cursor {
if at == play.target {
cycles = true;
break;
}
steps += 1;
if steps > budget {
break; }
cursor = effective
.get(&at)
.and_then(|locus| anchor_identity(locus.anchor));
}
if cycles {
refused.push(play.witness);
} else {
let _ = effective.insert(play.target, play.locus);
}
}
let bucket = |anchor: Anchor| -> Vec<Dot> {
let mut kids: Vec<Dot> = effective
.iter()
.filter(|(_, locus)| locus.anchor == anchor)
.map(|(&dot, _)| dot)
.collect();
kids.sort_by(|x, y| {
let (rx, ry) = (effective[x].rank, effective[y].rank);
ry.cmp(&rx).then_with(|| x.cmp(y))
});
kids
};
let mut order = Vec::new();
let mut stack: Vec<Frame> = bucket(Anchor::Origin)
.into_iter()
.rev()
.map(Frame::Visit)
.collect();
while let Some(frame) = stack.pop() {
match frame {
Frame::Emit(dot) => {
if text.is_visible(dot) {
order.push(dot);
}
}
Frame::Visit(dot) => {
for kid in bucket(Anchor::After(dot.into())).into_iter().rev() {
stack.push(Frame::Visit(kid));
}
stack.push(Frame::Emit(dot));
for kid in bucket(Anchor::Before(dot.into())) {
stack.push(Frame::Visit(kid));
}
}
}
}
(order, refused, pending)
}
proptest! {
#[test]
fn prop_recension_converges_and_matches_the_reference(ops in arb_ops()) {
let fleet = run(&ops);
let (reference_text, reference_moves) = (
fleet[0].0.state().store(),
fleet[0].1.state().store(),
);
let (oracle_order, oracle_refused, oracle_pending) =
reference_recension(reference_text, reference_moves);
for (text, moves) in &fleet {
prop_assert_eq!(text.state().store(), reference_text);
prop_assert_eq!(moves.state().store(), reference_moves);
let recension = text.state().store().recension(moves.state().store());
prop_assert_eq!(recension.order(), oracle_order.clone());
prop_assert_eq!(recension.refused(), &oracle_refused[..]);
prop_assert_eq!(recension.pending(), &oracle_pending[..]);
let identity = text.state().store().recension(&Metatheses::new());
prop_assert_eq!(identity.order(), text.state().store().order());
prop_assert!(identity.refused().is_empty());
prop_assert!(identity.pending().is_empty());
}
}
#[test]
fn prop_no_moves_means_no_refusals(ops in arb_ops()) {
let move_free: Vec<Op> = ops
.into_iter()
.filter(|op| !matches!(op, Op::Move { .. } | Op::Undo { .. }))
.collect();
let fleet = run(&move_free);
for (text, moves) in &fleet {
prop_assert!(moves.state().store().is_empty());
let recension = text.state().store().recension(moves.state().store());
prop_assert!(recension.refused().is_empty());
prop_assert_eq!(recension.order(), text.state().store().order());
}
}
}