Which of a memory's pictures is the card's, kept as a last-writer-wins register on the fact.
A fact can carry several pictures (Fact::pictures). The card shows one of them. With no choice made it
is the first; anyone, the child included, can star another, and that choice reaches every device the way
Visibility does. The register carries its own time, at_ms, and the later write wins; on an exact tie
the smaller picture name wins, so the answer does not depend on which copy was merged first.
The choice is a typed enum, so "chosen, with no picture" and "chosen, with no time" cannot be written
down. A choice is only ever made through [Fact::choose_cover], which refuses a picture the fact does not
have, and a stored or merged cover that names one anyway (a damaged file, a duplicate that carried other
pictures) is ignored by [Fact::card_picture] rather than shown.
13use serde::{Deserialize, Serialize};
15use crate::types::{Fact, PictureId};
Which picture is the card's, and the moment that was decided.
27impl Cover {
True for the state every new fact has, which is left out of the stored and wire forms so a fact nobody has touched is byte for byte what it was before this state existed.
Starring picture at now_ms. The write is made later than the one it follows even if this
device's clock is behind, so a causally later star always wins the merge.
The register's merge: the later write, and on a tie the smaller picture name. Commutative, associative and idempotent.
Later time first, then the smaller name; Unchosen is below every choice.
A star on a picture the memory does not have.
66impl Fact {
The picture the card shows: the starred one if the fact has it, otherwise the first, otherwise none (the card then shows its pixel icon).
Stars one of this fact's pictures at now_ms. Refuses a picture the fact does not have.
Takes in another copy's star. A cover naming a picture this copy does not have is left out, so the register never holds what the fact cannot show. Returns whether this copy changed.
87 pub(crate) fn merge_cover(&mut self, theirs: Cover) -> bool { 88 if let Cover::Chosen { picture, .. } = &theirs 89 && !self.pictures.contains(picture) 90 { 91 ::log::warn!("merge ignores a cover that names a picture this fact does not have"); 92 return false; 93 } 94 let merged = self.cover.clone().merged(theirs); 95 let changed = merged != self.cover; 96 self.cover = merged; 97 changed 98 } 99}
101#[cfg(test)] 102mod tests { 103 use super::*; 104 105 fn p(n: &str) -> PictureId { 106 PictureId(n.to_owned()) 107 } 108 109 fn all() -> Vec<Cover> { 110 let mut v = vec![Cover::Unchosen]; 111 for t in [0, 1, 4] { 112 for n in ["a.jpg", "b.jpg"] { 113 v.push(Cover::Chosen { picture: p(n), at_ms: t }); 114 } 115 } 116 v 117 } 118 119 #[test] 120 fn the_merge_is_commutative_associative_and_idempotent() { 121 for a in all() { 122 assert_eq!(a.clone().merged(a.clone()), a); 123 for b in all() { 124 assert_eq!(a.clone().merged(b.clone()), b.clone().merged(a.clone()), "{a:?} {b:?}"); 125 for c in all() { 126 assert_eq!( 127 a.clone().merged(b.clone()).merged(c.clone()), 128 a.clone().merged(b.clone().merged(c.clone())), 129 "{a:?} {b:?} {c:?}" 130 ); 131 } 132 } 133 } 134 } 135 136 #[test] 137 fn a_tie_goes_to_the_smaller_name_in_either_order() { 138 let (a, b) = (Cover::Chosen { picture: p("a.jpg"), at_ms: 3 }, Cover::Chosen { picture: p("b.jpg"), at_ms: 3 }); 139 assert_eq!(a.clone().merged(b.clone()), a); 140 assert_eq!(b.merged(a.clone()), a); 141 } 142 143 #[test] 144 fn a_later_star_beats_the_one_it_follows_even_from_a_slow_clock() { 145 let first = Cover::Unchosen.chosen_after(p("a.jpg"), 1_000); 146 let second = first.chosen_after(p("b.jpg"), 10); 147 assert_eq!(first.clone().merged(second.clone()), second); 148 assert_eq!(second.clone().merged(first), second); 149 } 150 151 #[test] 152 fn the_default_is_left_out_of_the_stored_form() { 153 assert!(Cover::default().is_default()); 154 assert!(!Cover::Unchosen.chosen_after(p("a.jpg"), 1).is_default()); 155 } 156}