use std::collections::BTreeMap;
use crate::ack::AckWindow;
use crate::digest::SetDigest;
use crate::slot::SlotKey;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Held<Entity> {
generation: u16,
entity: Entity,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Agreement {
Agreed,
Diverged { held: u64, expected: u64 },
}
impl Agreement {
pub fn agreed(self) -> bool {
matches!(self, Agreement::Agreed)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Divergence {
pub extra: Vec<SlotKey>,
pub missing: Vec<SlotKey>,
}
#[derive(Clone, Debug)]
pub struct DeltaMirror<Entity> {
held: BTreeMap<u32, Held<Entity>>,
acks: AckWindow,
applied_seq: Option<u64>,
generational: bool,
digest: u64,
frames_lost: u64,
stale_refs: u64,
divergences: u64,
}
impl<Entity> Default for DeltaMirror<Entity> {
fn default() -> Self {
Self::new()
}
}
impl<Entity> DeltaMirror<Entity> {
pub fn new() -> Self {
Self {
held: BTreeMap::new(),
acks: AckWindow::new(),
applied_seq: None,
generational: true,
digest: 0,
frames_lost: 0,
stale_refs: 0,
divergences: 0,
}
}
pub fn with_generations(mut self, generational: bool) -> Self {
self.generational = generational;
self
}
pub fn set_generations(&mut self, generational: bool) {
if self.generational != generational {
self.generational = generational;
self.held.clear();
}
}
pub fn begin(&mut self, seq: u64, full_baseline: bool) {
if let Some(previous) = self.applied_seq
&& seq > previous + 1
{
self.frames_lost += seq - previous - 1;
}
if self.applied_seq.is_none_or(|previous| seq > previous) {
self.applied_seq = Some(seq);
}
self.acks.observe(seq);
if full_baseline {
self.held.clear();
}
}
pub fn insert(&mut self, key: SlotKey, entity: Entity) {
let key = self.normalise(key);
self.held.insert(key.index, Held { generation: key.generation, entity });
}
pub fn remove(&mut self, key: SlotKey) -> Option<Entity> {
let key = self.normalise(key);
match self.held.get(&key.index) {
Some(held) if held.generation != key.generation => {
self.stale_refs += 1;
None
}
Some(_) => self.held.remove(&key.index).map(|held| held.entity),
None => None,
}
}
pub fn get(&self, key: SlotKey) -> Option<&Entity> {
let key = self.normalise(key);
self.held.get(&key.index).filter(|held| held.generation == key.generation).map(|held| &held.entity)
}
pub fn get_mut(&mut self, key: SlotKey) -> Option<&mut Entity> {
let key = self.normalise(key);
match self.held.get_mut(&key.index) {
Some(held) if held.generation == key.generation => Some(&mut held.entity),
Some(_) => {
self.stale_refs += 1;
None
}
None => None,
}
}
pub fn contains(&self, key: SlotKey) -> bool {
self.get(key).is_some()
}
pub fn settle(&mut self, expected: u64) -> Agreement {
self.digest = self.compute_digest();
if self.digest == expected {
Agreement::Agreed
} else {
self.divergences += 1;
Agreement::Diverged { held: self.digest, expected }
}
}
pub fn divergence_from<I: IntoIterator<Item = u64>>(&self, server_keys: I) -> Divergence {
let theirs: std::collections::BTreeSet<u64> = server_keys.into_iter().collect();
let mine: std::collections::BTreeSet<u64> = self.keys().map(SlotKey::encode).collect();
Divergence {
extra: mine.difference(&theirs).map(|k| SlotKey::decode(*k)).collect(),
missing: theirs.difference(&mine).map(|k| SlotKey::decode(*k)).collect(),
}
}
pub fn digest(&self) -> u64 {
self.digest
}
fn compute_digest(&self) -> u64 {
SetDigest::from_keys(self.keys().map(SlotKey::encode)).digest()
}
pub fn acks(&self) -> &AckWindow {
&self.acks
}
pub fn keys(&self) -> impl Iterator<Item = SlotKey> + '_ {
self.held.iter().map(|(index, held)| SlotKey::new(*index, held.generation))
}
pub fn iter(&self) -> impl Iterator<Item = (SlotKey, &Entity)> + '_ {
self.held.iter().map(|(index, held)| (SlotKey::new(*index, held.generation), &held.entity))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (SlotKey, &mut Entity)> + '_ {
self.held.iter_mut().map(|(index, held)| (SlotKey::new(*index, held.generation), &mut held.entity))
}
pub fn values(&self) -> impl Iterator<Item = &Entity> + '_ {
self.held.values().map(|held| &held.entity)
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Entity> + '_ {
self.held.values_mut().map(|held| &mut held.entity)
}
pub fn len(&self) -> usize {
self.held.len()
}
pub fn is_empty(&self) -> bool {
self.held.is_empty()
}
pub fn clear(&mut self) {
self.held.clear();
}
pub fn frames_lost(&self) -> u64 {
self.frames_lost
}
pub fn stale_refs(&self) -> u64 {
self.stale_refs
}
pub fn divergences(&self) -> u64 {
self.divergences
}
pub fn applied_seq(&self) -> Option<u64> {
self.applied_seq
}
fn normalise(&self, key: SlotKey) -> SlotKey {
if self.generational { key } else { key.ungenerational() }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mirror() -> DeltaMirror<&'static str> {
DeltaMirror::new()
}
mod switching_the_key_scheme {
use super::*;
#[test]
fn switching_clears_the_mirror_because_the_old_keys_are_unreachable() {
let mut m = mirror();
m.begin(1, true);
m.insert(SlotKey::new(3, 1), "held");
assert!(m.contains(SlotKey::new(3, 1)));
m.set_generations(false);
assert!(!m.contains(SlotKey::new(3, 1)), "filed under a scheme that no longer applies");
assert_eq!(m.len(), 0, "so it is dropped rather than left unreachable");
}
#[test]
fn switching_to_the_scheme_it_already_has_keeps_everything() {
let mut m = DeltaMirror::<&'static str>::new().with_generations(true);
m.begin(1, true);
m.insert(SlotKey::new(3, 1), "held");
m.set_generations(true);
assert!(m.contains(SlotKey::new(3, 1)), "nothing changed, so nothing was lost");
assert_eq!(m.len(), 1);
}
#[test]
fn ignoring_generations_makes_a_recycled_slot_look_like_its_predecessor() {
let mut blind = DeltaMirror::<&'static str>::new().with_generations(false);
blind.begin(1, true);
blind.insert(SlotKey::new(3, 1), "the first occupant");
assert!(
blind.contains(SlotKey::new(3, 2)),
"a later generation of the same slot reads as the same entity"
);
let mut careful = DeltaMirror::<&'static str>::new().with_generations(true);
careful.begin(1, true);
careful.insert(SlotKey::new(3, 1), "the first occupant");
assert!(
!careful.contains(SlotKey::new(3, 2)),
"and with generations on it is a different one"
);
}
}
#[test]
fn what_the_server_sent_is_what_the_mirror_holds() {
let mut m = mirror();
m.begin(1, true);
m.insert(SlotKey::new(1, 0), "a");
m.insert(SlotKey::new(2, 0), "b");
assert_eq!(m.len(), 2);
assert_eq!(m.get(SlotKey::new(1, 0)), Some(&"a"));
m.begin(2, false);
assert_eq!(m.remove(SlotKey::new(1, 0)), Some("a"));
assert_eq!(m.len(), 1);
assert!(!m.contains(SlotKey::new(1, 0)));
}
#[test]
fn a_reference_to_a_dead_occupant_is_rejected_not_applied_to_its_replacement() {
let mut m = mirror();
m.begin(1, true);
m.insert(SlotKey::new(41, 7), "old");
m.insert(SlotKey::new(41, 8), "new");
assert_eq!(m.len(), 1, "the slot holds one occupant at a time");
assert_eq!(m.remove(SlotKey::new(41, 7)), None, "a stale removal must not fire");
assert_eq!(m.stale_refs(), 1, "and it must be counted, not silently ignored");
assert_eq!(m.get(SlotKey::new(41, 8)), Some(&"new"), "the live occupant survived");
assert!(m.get_mut(SlotKey::new(41, 7)).is_none());
assert_eq!(m.stale_refs(), 2);
}
#[test]
fn without_generations_the_same_reference_corrupts_the_mirror() {
let mut m = mirror().with_generations(false);
m.begin(1, true);
m.insert(SlotKey::new(41, 8), "new");
assert_eq!(m.remove(SlotKey::new(41, 7)), Some("new"), "a stale removal deleted a live entity");
assert_eq!(m.stale_refs(), 0, "and there was no way to notice");
}
#[test]
fn a_gap_in_the_sequence_is_counted_as_a_lost_frame() {
let mut m = mirror();
m.begin(1, true);
m.begin(2, false);
assert_eq!(m.frames_lost(), 0);
m.begin(5, false);
assert_eq!(m.frames_lost(), 2, "3 and 4 never arrived");
}
#[test]
fn a_full_baseline_replaces_the_mirror_rather_than_merging_into_it() {
let mut m = mirror();
m.begin(1, true);
m.insert(SlotKey::new(1, 0), "stale");
m.insert(SlotKey::new(9, 0), "drifted");
m.begin(2, true);
assert!(m.is_empty(), "a rebuild starts from nothing");
m.insert(SlotKey::new(1, 0), "fresh");
assert_eq!(m.len(), 1);
assert_eq!(m.get(SlotKey::new(1, 0)), Some(&"fresh"));
}
#[test]
fn the_digest_agrees_when_both_sides_hold_the_same_set() {
let mut a = mirror();
let mut b = mirror();
a.begin(1, true);
b.begin(1, true);
for key in [SlotKey::new(3, 1), SlotKey::new(1, 0), SlotKey::new(2, 5)] {
a.insert(key, "x");
}
for key in [SlotKey::new(2, 5), SlotKey::new(3, 1), SlotKey::new(1, 0)] {
b.insert(key, "x");
}
b.settle(0);
assert_eq!(a.settle(b.digest()), Agreement::Agreed);
}
#[test]
fn a_drifted_mirror_is_caught_by_the_digest_and_can_say_how() {
let mut m = mirror();
m.begin(1, true);
m.insert(SlotKey::new(1, 0), "a");
m.insert(SlotKey::new(2, 0), "b");
let server: Vec<u64> = [SlotKey::new(1, 0), SlotKey::new(3, 0)].iter().map(|k| k.encode()).collect();
let mut expected = DeltaMirror::<&str>::new();
expected.begin(1, true);
expected.insert(SlotKey::new(1, 0), "a");
expected.insert(SlotKey::new(3, 0), "c");
expected.settle(0);
let verdict = m.settle(expected.digest());
assert!(!verdict.agreed());
assert_eq!(m.divergences(), 1);
let divergence = m.divergence_from(server);
assert_eq!(divergence.extra, vec![SlotKey::new(2, 0)], "held something the server does not");
assert_eq!(divergence.missing, vec![SlotKey::new(3, 0)], "missing something the server expects");
}
#[test]
fn applying_the_same_packet_twice_changes_nothing() {
let mut m = mirror();
for seq in [1, 1] {
m.begin(seq, false);
m.insert(SlotKey::new(1, 0), "a");
m.insert(SlotKey::new(2, 0), "b");
m.remove(SlotKey::new(9, 0));
}
m.settle(0);
let once = m.digest();
let mut single = mirror();
single.begin(1, false);
single.insert(SlotKey::new(1, 0), "a");
single.insert(SlotKey::new(2, 0), "b");
single.settle(0);
assert_eq!(once, single.digest(), "a duplicate packet left the mirror somewhere else");
}
#[test]
fn acknowledgements_track_what_actually_arrived() {
let mut m = mirror();
for seq in [1, 2, 4] {
m.begin(seq, false);
}
let (newest, _mask) = m.acks().encode().expect("something arrived");
assert_eq!(newest, 4);
assert!(m.acks().contains(2));
assert!(!m.acks().contains(3), "the gap is visible to the server too");
}
}