1//! Which of a memory's pictures is the card's, kept as a last-writer-wins register on the fact. 2//! 3//! A fact can carry several pictures (`Fact::pictures`). The card shows one of them. With no choice made it 4//! is the first; anyone, the child included, can star another, and that choice reaches every device the way 5//! `Visibility` does. The register carries its own time, `at_ms`, and the later write wins; on an exact tie 6//! the smaller picture name wins, so the answer does not depend on which copy was merged first. 7//! 8//! The choice is a typed enum, so "chosen, with no picture" and "chosen, with no time" cannot be written 9//! down. A choice is only ever made through [`Fact::choose_cover`], which refuses a picture the fact does not 10//! have, and a stored or merged cover that names one anyway (a damaged file, a duplicate that carried other 11//! pictures) is ignored by [`Fact::card_picture`] rather than shown. 12 13use serde::{Deserialize, Serialize}; 14 15use crate::types::{Fact, PictureId}; 16 17/// Which picture is the card's, and the moment that was decided. 18#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] 19pub enum Cover { 20 /// Nobody has starred a picture: the card shows the first one. 21 #[default] 22 Unchosen, 23 /// A picture was starred at `at_ms`. 24 Chosen { picture: PictureId, at_ms: u64 }, 25} 26 27impl Cover { 28 /// True for the state every new fact has, which is left out of the stored and wire forms so a fact 29 /// nobody has touched is byte for byte what it was before this state existed. 30 pub fn is_default(&self) -> bool { 31 *self == Cover::Unchosen 32 } 33 34 fn at_ms(&self) -> u64 { 35 match self { 36 Cover::Unchosen => 0, 37 Cover::Chosen { at_ms, .. } => *at_ms, 38 } 39 } 40 41 /// Starring `picture` at `now_ms`. The write is made later than the one it follows even if this 42 /// device's clock is behind, so a causally later star always wins the merge. 43 fn chosen_after(&self, picture: PictureId, now_ms: u64) -> Cover { 44 Cover::Chosen { picture, at_ms: now_ms.max(self.at_ms().saturating_add(1)) } 45 } 46 47 /// The register's merge: the later write, and on a tie the smaller picture name. Commutative, 48 /// associative and idempotent. 49 pub fn merged(self, other: Cover) -> Cover { 50 if other.order_key() > self.order_key() { other } else { self } 51 } 52 53 /// Later time first, then the smaller name; `Unchosen` is below every choice. 54 fn order_key(&self) -> (bool, u64, std::cmp::Reverse<&str>) { 55 match self { 56 Cover::Unchosen => (false, 0, std::cmp::Reverse("")), 57 Cover::Chosen { picture, at_ms } => (true, *at_ms, std::cmp::Reverse(picture.0.as_str())), 58 } 59 } 60} 61 62/// A star on a picture the memory does not have. 63#[derive(Clone, Debug, PartialEq, Eq)] 64pub struct NotThisMemorysPicture; 65 66impl Fact { 67 /// The picture the card shows: the starred one if the fact has it, otherwise the first, otherwise none 68 /// (the card then shows its pixel icon). 69 pub fn card_picture(&self) -> Option<&PictureId> { 70 match &self.cover { 71 Cover::Chosen { picture, .. } if self.pictures.contains(picture) => Some(picture), 72 _ => self.pictures.first(), 73 } 74 } 75 76 /// Stars one of this fact's pictures at `now_ms`. Refuses a picture the fact does not have. 77 pub fn choose_cover(&mut self, picture: &PictureId, now_ms: u64) -> Result<(), NotThisMemorysPicture> { 78 if !self.pictures.contains(picture) { 79 return Err(NotThisMemorysPicture); 80 } 81 self.cover = self.cover.chosen_after(picture.clone(), now_ms); 82 Ok(()) 83 } 84 85 /// Takes in another copy's star. A cover naming a picture this copy does not have is left out, so the 86 /// 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} 100 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}