From ab64881029383c992aa4b68dbe202030a5a85fa4 Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Wed, 6 Dec 2023 23:36:21 +0800 Subject: [PATCH] fix: text tracker error #211 (#212) * fix: text tracker error #211 * chore: rm only * fix: refine --- crates/loro-internal/fuzz/Cargo.lock | 99 +------------------ .../src/container/richtext/tracker.rs | 36 +++++-- .../container/richtext/tracker/crdt_rope.rs | 1 - crates/loro-internal/src/oplog.rs | 6 ++ loro-js/tests/issue.test.ts | 83 ++++++++++++++++ 5 files changed, 117 insertions(+), 108 deletions(-) create mode 100644 loro-js/tests/issue.test.ts diff --git a/crates/loro-internal/fuzz/Cargo.lock b/crates/loro-internal/fuzz/Cargo.lock index f1886e18..637a3ed7 100644 --- a/crates/loro-internal/fuzz/Cargo.lock +++ b/crates/loro-internal/fuzz/Cargo.lock @@ -2,24 +2,12 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - [[package]] name = "append-only-bytes" version = "0.1.12" @@ -71,12 +59,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "bumpalo" -version = "3.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" - [[package]] name = "bytecount" version = "0.6.3" @@ -116,17 +98,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" -[[package]] -name = "crdt-list" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e257356d86ea7330c1105eaf6041c6df627c5b1968f2338021df88b19de1875a" -dependencies = [ - "arbitrary", - "arref", - "rand", -] - [[package]] name = "critical-section" version = "1.1.1" @@ -389,7 +360,6 @@ dependencies = [ "append-only-bytes", "arbitrary", "arref", - "crdt-list", "debug-log", "enum-as-inner 0.5.1", "enum_dispatch", @@ -413,14 +383,12 @@ dependencies = [ "string_cache", "tabled", "thiserror", - "tracing", ] [[package]] name = "loro-internal-fuzz" version = "0.0.0" dependencies = [ - "crdt-list", "libfuzzer-sys", "loro-internal", ] @@ -441,14 +409,10 @@ version = "0.1.0" dependencies = [ "append-only-bytes", "arref", - "bumpalo", - "crdt-list", "debug-log", - "enum-as-inner 0.5.1", + "enum-as-inner 0.6.0", "fxhash", - "heapless", "num", - "ouroboros", "smallvec", ] @@ -555,29 +519,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "ouroboros" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca" -dependencies = [ - "aliasable", - "ouroboros_macro", -] - -[[package]] -name = "ouroboros_macro" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d" -dependencies = [ - "Inflector", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.105", -] - [[package]] name = "papergrid" version = "0.7.1" @@ -621,12 +562,6 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - [[package]] name = "postcard" version = "1.0.2" @@ -949,38 +884,6 @@ dependencies = [ "syn 2.0.25", ] -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.105", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", -] - [[package]] name = "typenum" version = "1.16.0" diff --git a/crates/loro-internal/src/container/richtext/tracker.rs b/crates/loro-internal/src/container/richtext/tracker.rs index a6fc40f4..1177c1f8 100644 --- a/crates/loro-internal/src/container/richtext/tracker.rs +++ b/crates/loro-internal/src/container/richtext/tracker.rs @@ -1,4 +1,4 @@ -use generic_btree::LeafIndex; +use generic_btree::{rle::Sliceable, LeafIndex}; use loro_common::{Counter, IdSpan, PeerID, ID}; use rle::HasLength; @@ -69,10 +69,11 @@ impl Tracker { &self.applied_vv } - pub(crate) fn insert(&mut self, op_id: ID, pos: usize, content: RichtextChunk) { - if self.applied_vv.includes_id(op_id) { - let last_id = op_id.inc(content.len() as Counter - 1); - assert!(self.applied_vv.includes_id(last_id)); + pub(crate) fn insert(&mut self, mut op_id: ID, mut pos: usize, mut content: RichtextChunk) { + let last_id = op_id.inc(content.len() as Counter - 1); + let applied_counter_end = self.applied_vv.get(&last_id.peer).copied().unwrap_or(0); + if applied_counter_end > last_id.counter { + // the op is included in the applied vv if !self.current_vv.includes_id(last_id) { // PERF: may be slow let mut updates = Default::default(); @@ -87,6 +88,12 @@ impl Tracker { self.batch_update(updates, false); } return; + } else if applied_counter_end > op_id.counter { + // need to slice the content + let start = (applied_counter_end - op_id.counter) as usize; + op_id.counter = applied_counter_end; + pos += start; + content = content.slice(start..); } // debug_log::group!("before insert {} pos={}", op_id, pos); @@ -127,10 +134,11 @@ impl Tracker { } /// If `reverse` is true, the deletion happens from the end of the range to the start. - pub(crate) fn delete(&mut self, op_id: ID, pos: usize, len: usize, reverse: bool) { - if self.applied_vv.includes_id(op_id) { - let last_id = op_id.inc(len as Counter - 1); - assert!(self.applied_vv.includes_id(last_id)); + pub(crate) fn delete(&mut self, mut op_id: ID, mut pos: usize, mut len: usize, reverse: bool) { + let last_id = op_id.inc(len as Counter - 1); + let applied_counter_end = self.applied_vv.get(&last_id.peer).copied().unwrap_or(0); + if applied_counter_end > last_id.counter { + // the op is included in the applied_vv if !self.current_vv.includes_id(last_id) { // PERF: may be slow let mut updates = Default::default(); @@ -141,6 +149,16 @@ impl Tracker { self.batch_update(updates, false); } return; + } else if applied_counter_end > op_id.counter { + // need to slice the op + let start = (applied_counter_end - op_id.counter) as usize; + op_id.counter = applied_counter_end; + len -= start; + if reverse { + pos -= start; + } else { + pos += start; + } } let mut ans = Vec::new(); diff --git a/crates/loro-internal/src/container/richtext/tracker/crdt_rope.rs b/crates/loro-internal/src/container/richtext/tracker/crdt_rope.rs index 3cbba12b..2c6ecce0 100644 --- a/crates/loro-internal/src/container/richtext/tracker/crdt_rope.rs +++ b/crates/loro-internal/src/container/richtext/tracker/crdt_rope.rs @@ -352,7 +352,6 @@ impl CrdtRope { while let Some((index, elem)) = iter.next() { // The elements will not be changed by this method. // This index is current index of the elem (calculated by `status` field rather than `diff_status` field) - debug_log::debug_dbg!(&index, &elem); match elem.diff() { DiffStatus::NotChanged => {} DiffStatus::Created => { diff --git a/crates/loro-internal/src/oplog.rs b/crates/loro-internal/src/oplog.rs index 65eaf1f4..da68d349 100644 --- a/crates/loro-internal/src/oplog.rs +++ b/crates/loro-internal/src/oplog.rs @@ -151,6 +151,7 @@ impl std::fmt::Debug for OpLog { pub(crate) struct EnsureChangeDepsAreAtTheEnd; impl OpLog { + #[inline] pub fn new() -> Self { Self { dag: AppDag::default(), @@ -164,6 +165,7 @@ impl OpLog { } } + #[inline] pub fn new_with_arena(arena: SharedArena) -> Self { Self { dag: AppDag::default(), @@ -173,10 +175,12 @@ impl OpLog { } } + #[inline] pub fn latest_timestamp(&self) -> Timestamp { self.latest_timestamp } + #[inline] pub fn dag(&self) -> &AppDag { &self.dag } @@ -192,10 +196,12 @@ impl OpLog { timestamp } + #[inline] pub fn is_empty(&self) -> bool { self.dag.map.is_empty() && self.arena.can_import_snapshot() } + #[inline] pub fn changes(&self) -> &ClientChanges { &self.changes } diff --git a/loro-js/tests/issue.test.ts b/loro-js/tests/issue.test.ts new file mode 100644 index 00000000..4e39f7ef --- /dev/null +++ b/loro-js/tests/issue.test.ts @@ -0,0 +1,83 @@ + +import { describe, expect, expectTypeOf, it } from "vitest"; +import { + Loro, + LoroList, + LoroMap, + isContainer, + setPanicHook, + toEncodedVersion, + getType, +} from "../src"; +import { Container, LoroText, OpId } from "../dist/loro"; +import { setDebug } from "loro-wasm"; + +setPanicHook(); + +it("#211", () => { + const loro1 = new Loro() + loro1.setPeerId(0n) + const text1 = loro1.getText("text") + + const loro2 = new Loro() + loro2.setPeerId(1n) + const text2 = loro2.getText("text") + + console.log("[1] Insert T to #0") + text1.insert(0, 'T') + loro1.commit() + show(text1, loro1, text2, loro2) + + console.log("[2] Synchronize") + loro1.import(loro2.exportFrom(loro1.version())) + loro2.import(loro1.exportFrom(loro2.version())) + show(text1, loro1, text2, loro2) + const frontiers1After2 = loro1.frontiers() + const frontiers2After2 = loro2.frontiers() + + console.log("[3] Append A to #0") + text1.insert(1, 'A') + loro1.commit() + show(text1, loro1, text2, loro2) + + console.log("[4] Append B to #1") + text2.insert(1, 'B') + loro2.commit() + show(text1, loro1, text2, loro2) + + console.log("[5] Play back to the frontiers after 2") + loro1.checkout(frontiers1After2) + loro2.checkout(frontiers2After2) + show(text1, loro1, text2, loro2) + + console.log("[6] Check both to the latest") + loro1.checkoutToLatest() + loro2.checkoutToLatest() + show(text1, loro1, text2, loro2) + const frontiers1Before7 = loro1.frontiers() + const frontiers2Before7 = loro2.frontiers() + + console.log("[7] Append B to #1") + text2.insert(2, 'B') + loro2.commit() + show(text1, loro1, text2, loro2) + + console.log("[8] Play back to the frontiers before 7") + console.log("----------------------------------------------------------"); + loro1.checkout(frontiers1Before7) + console.log("----------------------------------------------------------"); + loro2.checkout(frontiers2Before7) + show(text1, loro1, text2, loro2) +}) + + +function show(text1: LoroText, loro1: Loro, text2: LoroText, loro2: Loro) { + console.log(` #0 has content: ${JSON.stringify(text1.toString())}`) + console.log(` #0 has frontiers: ${showFrontiers(loro1.frontiers())}`) + console.log(` #1 has content: ${JSON.stringify(text2.toString())}`) + console.log(` #1 has frontiers: ${showFrontiers(loro2.frontiers())}`) +} + +function showFrontiers(frontiers: OpId[]) { + return frontiers.map((x) => `${x.peer}@${x.counter}`).join(";"); +}