Skip to main content

abyo_crdt/
version.rs

1//! Version vectors — a summary of "what ops a replica has seen".
2//!
3//! Used by every CRDT in this crate to drive idempotent op application
4//! and incremental sync via [`crate::List::ops_since`] et al.
5
6use crate::id::{OpId, ReplicaId};
7use std::collections::BTreeMap;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// Summary of "what ops a replica has seen", indexed by replica.
13///
14/// `vv[replica]` is the highest `counter` ever observed from that replica.
15/// Because `counter`s are Lamport timestamps that strictly increase along
16/// any causal chain, "seen `(replica, counter)`" implies "seen everything
17/// from that replica with counter ≤ that counter".
18#[derive(Clone, Default, Debug, Eq, PartialEq)]
19#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
20pub struct VersionVector {
21    /// `replica → highest seen counter`.
22    inner: BTreeMap<ReplicaId, u64>,
23}
24
25impl VersionVector {
26    /// Empty version vector.
27    #[must_use]
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    /// Has this version vector seen op `id`?
33    #[must_use]
34    pub fn contains(&self, id: OpId) -> bool {
35        self.inner
36            .get(&id.replica)
37            .is_some_and(|&v| v >= id.counter)
38    }
39
40    /// Number of distinct replicas seen.
41    #[must_use]
42    pub fn replica_count(&self) -> usize {
43        self.inner.len()
44    }
45
46    /// Highest counter observed from `replica`, or 0 if none.
47    #[must_use]
48    pub fn get(&self, replica: ReplicaId) -> u64 {
49        self.inner.get(&replica).copied().unwrap_or(0)
50    }
51
52    /// Record `id` as observed.
53    pub fn observe(&mut self, id: OpId) {
54        let entry = self.inner.entry(id.replica).or_insert(0);
55        *entry = (*entry).max(id.counter);
56    }
57
58    /// Iterate `(replica, highest_counter)` pairs in replica-id order.
59    pub fn iter_clocks(&self) -> impl Iterator<Item = (ReplicaId, u64)> + '_ {
60        self.inner.iter().map(|(&r, &c)| (r, c))
61    }
62}