From dd38eb12648575584acf8a9e2840a09f75753b73 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Dec 2021 22:05:13 -0700 Subject: [PATCH] Start on maintaining an insertions tree I'm correctly assigning fragment ids to all fragments in the fragments tree, but I have a randomized test failure when making sure that the insertions tree matches the state of the fragments tree. --- crates/text/src/locator.rs | 10 +- crates/text/src/random_char_iter.rs | 12 +- crates/text/src/tests.rs | 23 +++- crates/text/src/text.rs | 163 ++++++++++++++++++---------- 4 files changed, 140 insertions(+), 68 deletions(-) diff --git a/crates/text/src/locator.rs b/crates/text/src/locator.rs index 487c8c260867536fca3480c1ac0fcd62d946a533..0a22ea58f904b4fc28efc2ac785bd67ef5abd2dd 100644 --- a/crates/text/src/locator.rs +++ b/crates/text/src/locator.rs @@ -2,20 +2,20 @@ use smallvec::{smallvec, SmallVec}; use std::iter; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Locator(SmallVec<[u32; 4]>); +pub struct Locator(SmallVec<[u8; 4]>); impl Locator { pub fn min() -> Self { - Self(smallvec![u32::MIN]) + Self(smallvec![u8::MIN]) } pub fn max() -> Self { - Self(smallvec![u32::MAX]) + Self(smallvec![u8::MAX]) } pub fn between(lhs: &Self, rhs: &Self) -> Self { - let lhs = lhs.0.iter().copied().chain(iter::repeat(u32::MIN)); - let rhs = rhs.0.iter().copied().chain(iter::repeat(u32::MAX)); + let lhs = lhs.0.iter().copied().chain(iter::repeat(u8::MIN)); + let rhs = rhs.0.iter().copied().chain(iter::repeat(u8::MAX)); let mut location = SmallVec::new(); for (lhs, rhs) in lhs.zip(rhs) { let mid = lhs + (rhs.saturating_sub(lhs)) / 2; diff --git a/crates/text/src/random_char_iter.rs b/crates/text/src/random_char_iter.rs index 244665688d6008caa1bbb0c8208aef0df863b8e9..1b0e6cc64d573196bc75b55326f10d1a00c46eab 100644 --- a/crates/text/src/random_char_iter.rs +++ b/crates/text/src/random_char_iter.rs @@ -14,13 +14,13 @@ impl Iterator for RandomCharIter { fn next(&mut self) -> Option { match self.0.gen_range(0..100) { // whitespace - 0..=19 => [' ', '\n', '\t'].choose(&mut self.0).copied(), + 0..=5 => ['\n'].choose(&mut self.0).copied(), // two-byte greek letters - 20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))), - // three-byte characters - 33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(), - // four-byte characters - 46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(), + // 20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))), + // // three-byte characters + // 33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(), + // // four-byte characters + // 46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(), // ascii letters _ => Some(self.0.gen_range(b'a'..b'z' + 1).into()), } diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index a13273b898b9d6febd865add8cfee83d30f81fc1..e55f478c9f5fef57f8316004da309bd5287160e2 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -51,7 +51,7 @@ fn test_random_edits(mut rng: StdRng) { ); for _i in 0..operations { - let (old_ranges, new_text, _) = buffer.randomly_edit(&mut rng, 5); + let (old_ranges, new_text, _) = buffer.randomly_edit(&mut rng, 1); for old_range in old_ranges.iter().rev() { reference_string.replace_range(old_range.clone(), &new_text); } @@ -78,6 +78,27 @@ fn test_random_edits(mut rng: StdRng) { TextSummary::from(&reference_string[range]) ); + // Ensure every fragment is ordered by locator in the fragment tree and corresponds + // to an insertion fragment in the insertions tree. + let mut prev_fragment_id = Locator::min(); + for fragment in buffer.snapshot.fragments.items(&None) { + assert!(fragment.id > prev_fragment_id); + prev_fragment_id = fragment.id.clone(); + + let insertion_fragment = buffer + .snapshot + .insertions + .get( + &InsertionFragmentKey { + timestamp: fragment.insertion_timestamp, + split_offset: fragment.insertion_offset, + }, + &(), + ) + .unwrap(); + assert_eq!(insertion_fragment.fragment_id, fragment.id); + } + if rng.gen_bool(0.3) { buffer_versions.push((buffer.clone(), buffer.subscribe())); } diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 3d2f95c169f3a9d36528616c8371851645423838..c9343eb7a249e6585c98a3a1cebc2dffbcbfc6b4 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -359,7 +359,7 @@ impl Subscription { } } -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] pub struct InsertionTimestamp { pub replica_id: ReplicaId, pub local: clock::Seq, @@ -385,7 +385,8 @@ impl InsertionTimestamp { #[derive(Eq, PartialEq, Clone, Debug)] struct Fragment { id: Locator, - timestamp: InsertionTimestamp, + insertion_timestamp: InsertionTimestamp, + insertion_offset: usize, len: usize, visible: bool, deletions: HashSet, @@ -414,10 +415,10 @@ struct InsertionFragment { fragment_id: Locator, } -#[derive(Clone, Debug, Default)] -struct InsertionSummary { - max_timestamp: InsertionTimestamp, - max_split_offset: usize, +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +struct InsertionFragmentKey { + timestamp: InsertionTimestamp, + split_offset: usize, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -470,34 +471,26 @@ impl Buffer { let mut version = clock::Global::new(); let visible_text = Rope::from(history.base_text.as_ref()); if visible_text.len() > 0 { - let timestamp = InsertionTimestamp { + let insertion_timestamp = InsertionTimestamp { replica_id: 0, local: 1, lamport: 1, }; - local_clock.observe(timestamp.local()); - lamport_clock.observe(timestamp.lamport()); - version.observe(timestamp.local()); + local_clock.observe(insertion_timestamp.local()); + lamport_clock.observe(insertion_timestamp.lamport()); + version.observe(insertion_timestamp.local()); let fragment_id = Locator::between(&Locator::min(), &Locator::max()); - fragments.push( - Fragment { - id: fragment_id, - timestamp, - len: visible_text.len(), - visible: true, - deletions: Default::default(), - max_undos: Default::default(), - }, - &None, - ); - insertions.push( - InsertionFragment { - timestamp, - split_offset: 0, - fragment_id, - }, - &(), - ); + let fragment = Fragment { + id: fragment_id, + insertion_timestamp, + insertion_offset: 0, + len: visible_text.len(), + visible: true, + deletions: Default::default(), + max_undos: Default::default(), + }; + insertions.push(InsertionFragment::new(&fragment), &()); + fragments.push(fragment, &None); } Buffer { @@ -586,7 +579,7 @@ impl Buffer { ranges: Vec::with_capacity(ranges.len()), new_text: None, }; - let mut insertions = Vec::new(); + let mut new_insertions = Vec::new(); let mut ranges = ranges .map(|range| range.start.to_offset(&*self)..range.end.to_offset(&*self)) @@ -612,6 +605,8 @@ impl Buffer { if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); suffix.len = fragment_end - fragment_start; + suffix.insertion_offset += fragment_start - old_fragments.start().visible; + new_insertions.push(InsertionFragment::insert_new(&suffix)); new_ropes.push_fragment(&suffix, suffix.visible); new_fragments.push(suffix, &None); } @@ -630,6 +625,15 @@ impl Buffer { if fragment_start < range.start { let mut prefix = old_fragments.item().unwrap().clone(); prefix.len = range.start - fragment_start; + prefix.insertion_offset += fragment_start - old_fragments.start().visible; + + // log::info!( + // "pushing prefix between {:?} and {:?}", + // new_fragments.summary().max_id, + // prefix.id + // ); + prefix.id = Locator::between(&new_fragments.summary().max_id, &prefix.id); + new_insertions.push(InsertionFragment::insert_new(&prefix)); new_ropes.push_fragment(&prefix, prefix.visible); new_fragments.push(prefix, &None); fragment_start = range.start; @@ -642,17 +646,32 @@ impl Buffer { old: fragment_start..fragment_start, new: new_start..new_start + new_text.len(), }); + + // log::info!( + // "pushing new fragment between {:?} and {:?}", + // new_fragments.summary().max_id, + // old_fragments + // .item() + // .map_or(&Locator::max(), |old_fragment| &old_fragment.id) + // ); + + let fragment = Fragment { + id: Locator::between( + &new_fragments.summary().max_id, + old_fragments + .item() + .map_or(&Locator::max(), |old_fragment| &old_fragment.id), + ), + insertion_timestamp: timestamp, + insertion_offset: 0, + len: new_text.len(), + deletions: Default::default(), + max_undos: Default::default(), + visible: true, + }; + new_insertions.push(InsertionFragment::insert_new(&fragment)); new_ropes.push_str(new_text); - new_fragments.push( - Fragment { - timestamp, - len: new_text.len(), - deletions: Default::default(), - max_undos: Default::default(), - visible: true, - }, - &None, - ); + new_fragments.push(fragment, &None); } // Advance through every fragment that intersects this range, marking the intersecting @@ -664,6 +683,8 @@ impl Buffer { let intersection_end = cmp::min(range.end, fragment_end); if fragment.visible { intersection.len = intersection_end - fragment_start; + intersection.id = + Locator::between(&new_fragments.summary().max_id, &intersection.id); intersection.deletions.insert(timestamp.local()); intersection.visible = false; } @@ -675,6 +696,7 @@ impl Buffer { new: new_start..new_start, }); } + new_insertions.push(InsertionFragment::insert_new(&intersection)); new_ropes.push_fragment(&intersection, fragment.visible); new_fragments.push(intersection, &None); fragment_start = intersection_end; @@ -695,6 +717,8 @@ impl Buffer { if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); suffix.len = fragment_end - fragment_start; + suffix.insertion_offset += fragment_start - old_fragments.start().visible; + new_insertions.push(InsertionFragment::insert_new(&suffix)); new_ropes.push_fragment(&suffix, suffix.visible); new_fragments.push(suffix, &None); } @@ -708,6 +732,7 @@ impl Buffer { drop(old_fragments); self.snapshot.fragments = new_fragments; + self.snapshot.insertions.edit(new_insertions, &()); self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; self.update_subscriptions(edits); @@ -865,7 +890,7 @@ impl Buffer { // timestamp. while let Some(fragment) = old_fragments.item() { if fragment_start == range.start - && fragment.timestamp.lamport() > timestamp.lamport() + && fragment.insertion_timestamp.lamport() > timestamp.lamport() { new_ropes.push_fragment(fragment, fragment.visible); new_fragments.push(fragment.clone(), &None); @@ -900,7 +925,9 @@ impl Buffer { new_ropes.push_str(new_text); new_fragments.push( Fragment { - timestamp, + id: todo!(), + insertion_timestamp: timestamp, + insertion_offset: todo!(), len: new_text.len(), deletions: Default::default(), max_undos: Default::default(), @@ -1008,7 +1035,9 @@ impl Buffer { let fragment_was_visible = fragment.visible; if fragment.was_visible(&undo.version, &self.undo_map) - || undo.counts.contains_key(&fragment.timestamp.local()) + || undo + .counts + .contains_key(&fragment.insertion_timestamp.local()) { fragment.visible = fragment.is_visible(&self.undo_map); fragment.max_undos.observe(undo.id); @@ -2028,13 +2057,13 @@ impl<'a, D: TextDimension<'a> + Ord, F: FnMut(&FragmentSummary) -> bool> Iterato impl Fragment { fn is_visible(&self, undos: &UndoMap) -> bool { - !undos.is_undone(self.timestamp.local()) + !undos.is_undone(self.insertion_timestamp.local()) && self.deletions.iter().all(|d| undos.is_undone(*d)) } fn was_visible(&self, version: &clock::Global, undos: &UndoMap) -> bool { - (version.observed(self.timestamp.local()) - && !undos.was_undone(self.timestamp.local(), version)) + (version.observed(self.insertion_timestamp.local()) + && !undos.was_undone(self.insertion_timestamp.local(), version)) && self .deletions .iter() @@ -2047,14 +2076,14 @@ impl sum_tree::Item for Fragment { fn summary(&self) -> Self::Summary { let mut max_version = clock::Global::new(); - max_version.observe(self.timestamp.local()); + max_version.observe(self.insertion_timestamp.local()); for deletion in &self.deletions { max_version.observe(*deletion); } max_version.join(&self.max_undos); let mut min_insertion_version = clock::Global::new(); - min_insertion_version.observe(self.timestamp.local()); + min_insertion_version.observe(self.insertion_timestamp.local()); let max_insertion_version = min_insertion_version.clone(); if self.visible { FragmentSummary { @@ -2086,6 +2115,7 @@ impl sum_tree::Summary for FragmentSummary { type Context = Option; fn add_summary(&mut self, other: &Self, _: &Self::Context) { + self.max_id = other.max_id.clone(); self.text.visible += &other.text.visible; self.text.deleted += &other.text.deleted; self.max_version.join(&other.max_version); @@ -2116,22 +2146,43 @@ impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary { } impl sum_tree::Item for InsertionFragment { - type Summary = InsertionSummary; + type Summary = InsertionFragmentKey; fn summary(&self) -> Self::Summary { - InsertionSummary { - max_timestamp: self.timestamp, - max_split_offset: self.split_offset, + InsertionFragmentKey { + timestamp: self.timestamp, + split_offset: self.split_offset, } } } -impl sum_tree::Summary for InsertionSummary { +impl sum_tree::KeyedItem for InsertionFragment { + type Key = InsertionFragmentKey; + + fn key(&self) -> Self::Key { + sum_tree::Item::summary(self) + } +} + +impl InsertionFragment { + fn new(fragment: &Fragment) -> Self { + Self { + timestamp: fragment.insertion_timestamp, + split_offset: fragment.insertion_offset, + fragment_id: fragment.id.clone(), + } + } + + fn insert_new(fragment: &Fragment) -> sum_tree::Edit { + sum_tree::Edit::Insert(Self::new(fragment)) + } +} + +impl sum_tree::Summary for InsertionFragmentKey { type Context = (); - fn add_summary(&mut self, summary: &Self, cx: &()) { - self.max_timestamp = summary.max_timestamp; - self.max_split_offset = summary.max_split_offset; + fn add_summary(&mut self, summary: &Self, _: &()) { + *self = *summary; } }