Skip to main content

blockworx_doc/
register.rs

1//! The LWW register — the only write path into document state.
2//! Rationale: `docs/doc-ng-design-notes.md`.
3
4use crate::write_order::WriteOrder;
5use serde::Serialize;
6
7/// Value + the total order of the write that set it. [`Register::apply`] is
8/// the entire merge rule. Default = (`T::default()`, `WriteOrder::BOTTOM`).
9/// Serialized with its order inline: write orders are merge state a
10/// snapshot (and the content hash) must cover.
11#[derive(Default, Clone, Serialize)]
12pub struct Register<T> {
13    value: T,
14    order: WriteOrder,
15}
16
17/// A loss is not an error — it is the merge working — but it is what a
18/// collision review reports.
19#[derive(Clone, Copy, PartialEq, Eq, Debug)]
20pub enum Applied {
21    Won,
22    LostToNewer,
23}
24
25impl<T: Clone> Register<T> {
26    pub fn new(value: &T, order: WriteOrder) -> Self {
27        Self {
28            value: value.clone(),
29            order,
30        }
31    }
32    pub fn order(&self) -> WriteOrder {
33        self.order
34    }
35    pub fn apply(&mut self, value: &T, order: WriteOrder) -> Applied {
36        if order > self.order {
37            self.value = value.clone();
38            self.order = order;
39            Applied::Won
40        } else {
41            Applied::LostToNewer
42        }
43    }
44}
45
46impl<T> AsRef<T> for Register<T> {
47    fn as_ref(&self) -> &T {
48        &self.value
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use crate::{rev::Rev, write_order::Seq};
56
57    fn order(rev: u64, seq: usize) -> WriteOrder {
58        WriteOrder::new(Rev::new(rev), Seq::new(seq))
59    }
60
61    fn at(rev: u64) -> WriteOrder {
62        order(rev, 0)
63    }
64
65    #[test]
66    fn a_register_starts_at_its_zero_at_bottom() {
67        let register = Register::<u8>::default();
68        assert_eq!(*register.as_ref(), 0);
69        assert_eq!(register.order(), WriteOrder::BOTTOM);
70    }
71
72    #[test]
73    fn a_later_write_wins() {
74        let mut register = Register::<u8>::default();
75        assert_eq!(register.apply(&3, at(5)), Applied::Won);
76        assert_eq!(*register.as_ref(), 3);
77        assert_eq!(register.order(), at(5));
78    }
79
80    #[test]
81    fn an_earlier_write_is_dropped() {
82        let mut register = Register::<u8>::default();
83        register.apply(&3, at(5));
84        assert_eq!(register.apply(&9, at(2)), Applied::LostToNewer);
85        assert_eq!(*register.as_ref(), 3, "the stale write must not land");
86        assert_eq!(register.order(), at(5));
87    }
88
89    /// The intra-commit case: two writes sharing a rev resolve by seq, so
90    /// a commit that touches one register twice ends on its last write.
91    #[test]
92    fn a_commits_second_write_to_one_register_wins_by_seq() {
93        let (first, second) = (order(5, 0), order(5, 1));
94        assert_eq!(
95            first.rev, second.rev,
96            "the fixture must share a rev or the seq isn't what's ordering them"
97        );
98
99        let mut register = Register::<u8>::default();
100        assert_eq!(register.apply(&1, first), Applied::Won);
101        assert_eq!(register.apply(&2, second), Applied::Won);
102        assert_eq!(*register.as_ref(), 2);
103    }
104
105    /// An equal key can only be the same write arriving again; dropping it
106    /// is what makes duplicate delivery a no-op.
107    #[test]
108    fn re_delivering_a_write_changes_nothing() {
109        let mut register = Register::<u8>::default();
110        register.apply(&3, at(5));
111        assert_eq!(register.apply(&3, at(5)), Applied::LostToNewer);
112        assert_eq!(*register.as_ref(), 3);
113        assert_eq!(register.order(), at(5));
114    }
115}