whiskers.git / crates / whiskers-core / src / visibility.rs
1//! Whether the child has put a fact away, kept apart from the parents' permanent `forgotten`.
2//!
3//! The two are different acts with different reaches. **Forgetting** is the parents': the fact is
4//! deleted and its identity is tombstoned so no device brings it back. **Hiding** is the child's: the
5//! fact stays (the parents still see it, under "removed by her"), Whiskers stops using it, and a
6//! parent can restore it. A hidden fact is therefore not a second list but a state of the fact.
7//!
8//! The state is a last-writer-wins register, so merging two copies in any order, any number of times,
9//! gives the same answer (the invariant of `MemorySnapshot`). It carries its own time, `at_ms`, and the
10//! newer write wins; on an exact tie the fact stays hidden, so the answer does not depend on which copy
11//! was merged first and a tie never lets Whiskers use what she put away.
12//!
13//! The state is a typed enum, not two booleans: "hidden and shown" and "hidden with no time" cannot be
14//! written down.
15
16use serde::{Deserialize, Serialize};
17
18/// Whether Whiskers may use a fact, and the moment that was decided.
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
20pub enum Visibility {
21    /// Whiskers uses it. `at_ms` is when a parent last restored it (0 for a fact never hidden).
22    Shown { at_ms: u64 },
23    /// She put it away. Whiskers does not use it; the parents can restore it.
24    HiddenByChild { at_ms: u64 },
25}
26
27impl Default for Visibility {
28    fn default() -> Self {
29        Visibility::Shown { at_ms: 0 }
30    }
31}
32
33impl Visibility {
34    /// True for the state every new fact has, which is left out of the stored and wire forms so a
35    /// fact nobody has touched is byte for byte what it was before this state existed.
36    pub fn is_default(&self) -> bool {
37        *self == Visibility::default()
38    }
39
40    pub fn is_hidden(&self) -> bool {
41        matches!(self, Visibility::HiddenByChild { .. })
42    }
43
44    pub fn at_ms(&self) -> u64 {
45        match *self {
46            Visibility::Shown { at_ms } | Visibility::HiddenByChild { at_ms } => at_ms,
47        }
48    }
49
50    /// Her putting the fact away at `now_ms`. The write is made later than the one it follows even if
51    /// this device's clock is behind, so a causally later act always wins the merge.
52    pub fn hidden_after(self, now_ms: u64) -> Visibility {
53        Visibility::HiddenByChild { at_ms: now_ms.max(self.at_ms().saturating_add(1)) }
54    }
55
56    /// A parent's restore at `now_ms`, later than whatever it follows (see [`hidden_after`](Self::hidden_after)).
57    pub fn restored_after(self, now_ms: u64) -> Visibility {
58        Visibility::Shown { at_ms: now_ms.max(self.at_ms().saturating_add(1)) }
59    }
60
61    /// The register's merge: the later write, and on a tie the hidden one. Commutative, associative
62    /// and idempotent.
63    pub fn merged(self, other: Visibility) -> Visibility {
64        if other.order_key() > self.order_key() { other } else { self }
65    }
66
67    fn order_key(&self) -> (u64, bool) {
68        (self.at_ms(), self.is_hidden())
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    fn all() -> Vec<Visibility> {
77        let mut v = Vec::new();
78        for t in [0, 1, 2, 5] {
79            v.push(Visibility::Shown { at_ms: t });
80            v.push(Visibility::HiddenByChild { at_ms: t });
81        }
82        v
83    }
84
85    #[test]
86    fn the_merge_is_commutative_associative_and_idempotent() {
87        for a in all() {
88            assert_eq!(a.merged(a), a);
89            for b in all() {
90                assert_eq!(a.merged(b), b.merged(a), "{a:?} {b:?}");
91                for c in all() {
92                    assert_eq!(a.merged(b).merged(c), a.merged(b.merged(c)), "{a:?} {b:?} {c:?}");
93                }
94            }
95        }
96    }
97
98    #[test]
99    fn a_tie_stays_hidden_in_either_order() {
100        let (s, h) = (Visibility::Shown { at_ms: 7 }, Visibility::HiddenByChild { at_ms: 7 });
101        assert_eq!(s.merged(h), h);
102        assert_eq!(h.merged(s), h);
103    }
104
105    #[test]
106    fn a_restore_beats_the_hide_it_follows_even_from_a_slow_clock() {
107        let hidden = Visibility::default().hidden_after(1_000);
108        let restored = hidden.restored_after(10); // the parents' clock is behind
109        assert!(!restored.is_hidden());
110        assert_eq!(hidden.merged(restored), restored);
111        assert_eq!(restored.merged(hidden), restored);
112        // and her hiding it again beats that restore
113        let again = restored.hidden_after(0);
114        assert_eq!(restored.merged(again), again);
115    }
116
117    #[test]
118    fn the_default_is_left_out_of_the_stored_form() {
119        assert!(Visibility::default().is_default());
120        assert!(!Visibility::default().hidden_after(3).is_default());
121        assert!(!Visibility::default().hidden_after(3).restored_after(9).is_default());
122    }
123}