diff --git a/crates/examples/fuzz/Cargo.lock b/crates/examples/fuzz/Cargo.lock index 1c8bd864..a6fd6143 100644 --- a/crates/examples/fuzz/Cargo.lock +++ b/crates/examples/fuzz/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.43", + "syn 2.0.51", ] [[package]] @@ -167,14 +167,14 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.43", + "syn 2.0.51", ] [[package]] name = "debug-log" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90f9d9c0c144c4aa35a874e362392ec6aef3b9291c882484235069540b26c73" +checksum = "caf861b629ec23fc562cccc8b47cc98a54d192ecf4e7b575ce30659d8c814c3a" dependencies = [ "once_cell", ] @@ -187,7 +187,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.51", ] [[package]] @@ -223,7 +223,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.51", ] [[package]] @@ -235,7 +235,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.51", ] [[package]] @@ -246,6 +246,7 @@ dependencies = [ "bench-utils", "debug-log", "loro", + "serde_json", "tabled", ] @@ -406,7 +407,7 @@ dependencies = [ [[package]] name = "loro" -version = "0.2.2" +version = "0.3.0" dependencies = [ "either", "enum-as-inner 0.6.0", @@ -415,7 +416,7 @@ dependencies = [ [[package]] name = "loro-common" -version = "0.1.0" +version = "0.2.0" dependencies = [ "arbitrary", "enum-as-inner 0.6.0", @@ -429,7 +430,7 @@ dependencies = [ [[package]] name = "loro-internal" -version = "0.2.2" +version = "0.3.0" dependencies = [ "append-only-bytes", "arref", @@ -455,13 +456,12 @@ dependencies = [ "serde_columnar", "serde_json", "smallvec", - "string_cache", "thiserror", ] [[package]] name = "loro-preload" -version = "0.1.0" +version = "0.2.0" dependencies = [ "bytes", "loro-common", @@ -471,7 +471,7 @@ dependencies = [ [[package]] name = "loro-rle" -version = "0.1.0" +version = "0.2.0" dependencies = [ "append-only-bytes", "arref", @@ -695,18 +695,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -788,9 +788,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -817,25 +817,25 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.51", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.51", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.43" +version = "2.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c" dependencies = [ "proc-macro2", "quote", @@ -965,7 +965,7 @@ checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.51", ] [[package]] diff --git a/crates/examples/fuzz/fuzz_targets/draw.rs b/crates/examples/fuzz/fuzz_targets/draw.rs index 15a414d5..2bbfc888 100644 --- a/crates/examples/fuzz/fuzz_targets/draw.rs +++ b/crates/examples/fuzz/fuzz_targets/draw.rs @@ -3,6 +3,4 @@ use bench_utils::{draw::DrawAction, Action}; use libfuzzer_sys::fuzz_target; -fuzz_target!(|actions: Vec>| { - examples::draw::run_actions_fuzz_in_async_mode(5, 100, &actions) -}); +fuzz_target!(|actions: Vec>| examples::draw::fuzz(5, 100, &actions)); diff --git a/crates/loro-common/src/id.rs b/crates/loro-common/src/id.rs index 9a08c0db..a0ad689e 100644 --- a/crates/loro-common/src/id.rs +++ b/crates/loro-common/src/id.rs @@ -1,4 +1,4 @@ -use crate::{span::IdSpan, CounterSpan}; +use crate::{span::IdSpan, CounterSpan, IdFull, IdLp, IdLpSpan, Lamport}; use super::{Counter, LoroError, PeerID, ID}; const UNKNOWN: PeerID = 404; @@ -13,12 +13,24 @@ impl Debug for ID { } } +impl Debug for IdLp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(format!("L{}@{}", self.lamport, self.peer).as_str()) + } +} + impl Display for ID { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(format!("{}@{}", self.counter, self.peer).as_str()) } } +impl Display for IdLp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(format!("L{}@{}", self.lamport, self.peer).as_str()) + } +} + impl TryFrom<&str> for ID { type Error = LoroError; @@ -151,3 +163,69 @@ impl RangeBounds for (ID, ID) { std::ops::Bound::Excluded(&self.1) } } + +impl IdLp { + pub const NONE_ID: IdLp = IdLp::new(u64::MAX, 0); + + #[inline] + pub const fn new(peer: PeerID, lp: Lamport) -> Self { + Self { peer, lamport: lp } + } + + pub fn inc(&self, offset: i32) -> IdLp { + IdLp { + peer: self.peer, + lamport: (self.lamport as i32 + offset) as Lamport, + } + } +} + +impl From for IdLpSpan { + fn from(value: IdLp) -> Self { + IdLpSpan { + peer: value.peer, + lamport: crate::LamportSpan { + start: value.lamport, + end: value.lamport + 1, + }, + } + } +} + +impl IdFull { + pub const NONE_ID: IdFull = IdFull { + peer: PeerID::MAX, + lamport: 0, + counter: 0, + }; + + pub fn new(peer: PeerID, counter: Counter, lamport: Lamport) -> Self { + Self { + peer, + lamport, + counter, + } + } + + pub fn inc(&self, offset: i32) -> IdFull { + IdFull { + peer: self.peer, + lamport: (self.lamport as i32 + offset) as Lamport, + counter: self.counter + offset as Counter, + } + } + + pub fn id(&self) -> ID { + ID { + peer: self.peer, + counter: self.counter, + } + } + + pub fn idlp(&self) -> IdLp { + IdLp { + peer: self.peer, + lamport: self.lamport, + } + } +} diff --git a/crates/loro-common/src/lib.rs b/crates/loro-common/src/lib.rs index 785a70ac..ddd40867 100644 --- a/crates/loro-common/src/lib.rs +++ b/crates/loro-common/src/lib.rs @@ -18,17 +18,36 @@ pub use internal_string::InternalString; pub use span::*; pub use value::{to_value, LoroValue}; -/// Unique id for each peer. It's usually random +/// Unique id for each peer. It's a random u64 by default. pub type PeerID = u64; +/// If it's the nth Op of a peer, the counter will be n. pub type Counter = i32; +/// It's the [Lamport clock](https://en.wikipedia.org/wiki/Lamport_timestamp) pub type Lamport = u32; +/// It's the unique ID of an Op represented by [PeerID] and [Counter]. #[derive(PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)] pub struct ID { pub peer: PeerID, pub counter: Counter, } +/// It's the unique ID of an Op represented by [PeerID] and [Lamport] clock. +/// It's used to define the total order of Ops. +#[derive(PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord)] +pub struct IdLp { + pub lamport: Lamport, + pub peer: PeerID, +} + +/// It's the unique ID of an Op represented by [PeerID], [Lamport] clock and [Counter]. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)] +pub struct IdFull { + pub peer: PeerID, + pub lamport: Lamport, + pub counter: Counter, +} + /// [ContainerID] includes the Op's [ID] and the type. So it's impossible to have /// the same [ContainerID] with conflict [ContainerType]. /// diff --git a/crates/loro-common/src/span.rs b/crates/loro-common/src/span.rs index 303661ef..dd1e4e2d 100644 --- a/crates/loro-common/src/span.rs +++ b/crates/loro-common/src/span.rs @@ -236,6 +236,36 @@ impl Mergable for CounterSpan { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct LamportSpan { + pub start: Lamport, + pub end: Lamport, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct IdLpSpan { + pub peer: PeerID, + pub lamport: LamportSpan, +} + +impl HasLength for IdLpSpan { + fn content_len(&self) -> usize { + (self.lamport.end - self.lamport.start) as usize + } +} + +impl IdLpSpan { + pub fn new(peer: PeerID, from: Lamport, to: Lamport) -> Self { + Self { + peer, + lamport: LamportSpan { + start: from, + end: to, + }, + } + } +} + /// This struct supports reverse repr: [CounterSpan]'s from can be less than to. But we should use it conservatively. /// We need this because it'll make merging deletions easier. #[derive(Clone, Copy, PartialEq, Eq, Debug)] diff --git a/crates/loro-internal/fuzz/Cargo.lock b/crates/loro-internal/fuzz/Cargo.lock index 36ccac1d..cfa7ee55 100644 --- a/crates/loro-internal/fuzz/Cargo.lock +++ b/crates/loro-internal/fuzz/Cargo.lock @@ -342,7 +342,7 @@ dependencies = [ [[package]] name = "loro-common" -version = "0.1.0" +version = "0.2.0" dependencies = [ "arbitrary", "enum-as-inner 0.6.0", @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "loro-internal" -version = "0.2.2" +version = "0.3.0" dependencies = [ "append-only-bytes", "arbitrary", @@ -398,7 +398,7 @@ dependencies = [ [[package]] name = "loro-preload" -version = "0.1.0" +version = "0.2.0" dependencies = [ "bytes", "loro-common", @@ -408,7 +408,7 @@ dependencies = [ [[package]] name = "loro-rle" -version = "0.1.0" +version = "0.2.0" dependencies = [ "append-only-bytes", "arref", diff --git a/crates/loro-internal/src/container/richtext.rs b/crates/loro-internal/src/container/richtext.rs index 5f27d1df..09346e13 100644 --- a/crates/loro-internal/src/container/richtext.rs +++ b/crates/loro-internal/src/container/richtext.rs @@ -19,7 +19,7 @@ mod tracker; use crate::{change::Lamport, delta::StyleMeta, utils::string_slice::StringSlice, InternalString}; use fugue_span::*; -use loro_common::{Counter, LoroValue, PeerID, ID}; +use loro_common::{Counter, IdLp, LoroValue, PeerID, ID}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -99,6 +99,10 @@ impl StyleOp { pub fn id(&self) -> ID { ID::new(self.peer, self.cnt) } + + pub fn idlp(&self) -> IdLp { + IdLp::new(self.peer, self.lamport) + } } impl PartialOrd for StyleOp { diff --git a/crates/loro-internal/src/container/richtext/fugue_span.rs b/crates/loro-internal/src/container/richtext/fugue_span.rs index 64196a15..09815ba9 100644 --- a/crates/loro-internal/src/container/richtext/fugue_span.rs +++ b/crates/loro-internal/src/container/richtext/fugue_span.rs @@ -1,7 +1,7 @@ use std::ops::Range; use generic_btree::rle::{HasLength, Mergeable, Sliceable}; -use loro_common::{Counter, HasId, IdSpan, ID}; +use loro_common::{Counter, HasId, IdFull, IdSpan, Lamport, ID}; use serde::{Deserialize, Serialize}; use super::AnchorType; @@ -170,7 +170,7 @@ impl Sliceable for RichtextChunk { #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub(super) struct FugueSpan { - pub id: ID, + pub id: IdFull, /// The status at the current version pub status: Status, /// The status at the `new` version. @@ -223,7 +223,7 @@ impl Sliceable for FugueSpan { origin_left: if range.start == 0 { self.origin_left } else { - Some(self.id.inc((range.start - 1) as Counter)) + Some(self.id.inc((range.start - 1) as Counter).id()) }, origin_right: self.origin_right, content: self.content._slice(range), @@ -244,6 +244,7 @@ impl Mergeable for FugueSpan { && self.status == rhs.status && self.diff_status == rhs.diff_status && self.id.counter + self.content.len() as Counter == rhs.id.counter + && self.id.lamport + self.content.len() as Lamport == rhs.id.lamport && rhs.origin_left.is_some() && rhs.origin_left.unwrap().peer == self.id.peer && rhs.origin_left.unwrap().counter @@ -265,7 +266,7 @@ impl Mergeable for FugueSpan { impl FugueSpan { #[allow(unused)] - pub fn new(id: ID, content: RichtextChunk) -> Self { + pub fn new(id: IdFull, content: RichtextChunk) -> Self { Self { id, status: Status::default(), @@ -293,7 +294,7 @@ impl FugueSpan { impl HasId for FugueSpan { fn id_start(&self) -> ID { - self.id + self.id.id() } } diff --git a/crates/loro-internal/src/container/richtext/richtext_state.rs b/crates/loro-internal/src/container/richtext/richtext_state.rs index cb6a3eb2..642e035d 100644 --- a/crates/loro-internal/src/container/richtext/richtext_state.rs +++ b/crates/loro-internal/src/container/richtext/richtext_state.rs @@ -4,7 +4,7 @@ use generic_btree::{ rle::{HasLength, Mergeable, Sliceable}, BTree, BTreeTrait, Cursor, }; -use loro_common::{IdSpan, LoroValue, ID}; +use loro_common::{IdFull, IdLpSpan, Lamport, LoroValue}; use serde::{ser::SerializeStruct, Serialize}; use std::{ fmt::{Display, Formatter}, @@ -68,18 +68,18 @@ mod text_chunk { use std::ops::Range; use append_only_bytes::BytesSlice; - use loro_common::ID; + use loro_common::{IdFull, IdLp}; #[derive(Clone, Debug, PartialEq)] pub(crate) struct TextChunk { bytes: BytesSlice, unicode_len: i32, utf16_len: i32, - start_op_id: ID, + id: IdFull, } impl TextChunk { - pub fn new(bytes: BytesSlice, id: ID) -> Self { + pub fn new(bytes: BytesSlice, id: IdFull) -> Self { let mut utf16_len = 0; let mut unicode_len = 0; for c in std::str::from_utf8(&bytes).unwrap().chars() { @@ -91,35 +91,42 @@ mod text_chunk { unicode_len, bytes, utf16_len: utf16_len as i32, - start_op_id: id, + id, } } - pub fn id(&self) -> ID { - self.start_op_id + #[inline] + pub fn idlp(&self) -> IdLp { + IdLp::new(self.id.peer, self.id.lamport) } + #[inline] pub fn bytes(&self) -> &BytesSlice { &self.bytes } + #[inline] pub fn as_str(&self) -> &str { // SAFETY: We know that the text is valid UTF-8 unsafe { std::str::from_utf8_unchecked(&self.bytes) } } + #[inline] pub fn len(&self) -> i32 { self.unicode_len } + #[inline] pub fn unicode_len(&self) -> i32 { self.unicode_len } + #[inline] pub fn utf16_len(&self) -> i32 { self.utf16_len } + #[inline] pub fn event_len(&self) -> i32 { if cfg!(feature = "wasm") { self.utf16_len @@ -151,7 +158,7 @@ mod text_chunk { utf16_len: 0, // This is a dummy value. // It's fine because the length is 0. We never actually use this value. - start_op_id: ID::NONE_ID, + id: IdFull::NONE_ID, } } @@ -199,7 +206,7 @@ mod text_chunk { } (true, false) => { self.bytes.slice_(end_byte..); - self.start_op_id = self.start_op_id.inc(end_unicode_index as i32); + self.id = self.id.inc(end_unicode_index as i32); None } (false, true) => { @@ -208,7 +215,7 @@ mod text_chunk { } (false, false) => { let next = self.bytes.slice_clone(end_byte..); - let next = Self::new(next, self.start_op_id.inc(end_unicode_index as i32)); + let next = Self::new(next, self.id.inc(end_unicode_index as i32)); self.unicode_len -= next.unicode_len; self.utf16_len -= next.utf16_len; self.bytes.slice_(..start_byte); @@ -307,7 +314,7 @@ mod text_chunk { unicode_len: range.len() as i32, bytes: self.bytes.slice_clone(start..end), utf16_len: utf16_len as i32, - start_op_id: self.start_op_id.inc(range.start as i32), + id: self.id.inc(range.start as i32), }; ans.check(); ans @@ -328,7 +335,7 @@ mod text_chunk { unicode_len: self.unicode_len - pos as i32, bytes: self.bytes.slice_clone(byte_offset..), utf16_len: self.utf16_len - utf16_len as i32, - start_op_id: self.start_op_id.inc(pos as i32), + id: self.id.inc(pos as i32), }; self.unicode_len = pos as i32; @@ -342,8 +349,7 @@ mod text_chunk { impl generic_btree::rle::Mergeable for TextChunk { fn can_merge(&self, rhs: &Self) -> bool { - self.bytes.can_merge(&rhs.bytes) - && self.start_op_id.inc(self.unicode_len) == rhs.start_op_id + self.bytes.can_merge(&rhs.bytes) && self.id.inc(self.unicode_len) == rhs.id } fn merge_right(&mut self, rhs: &Self) { @@ -359,7 +365,7 @@ mod text_chunk { self.bytes = new; self.utf16_len += left.utf16_len; self.unicode_len += left.unicode_len; - self.start_op_id = left.start_op_id; + self.id = left.id; self.check(); } } @@ -376,7 +382,7 @@ pub(crate) enum RichtextStateChunk { } impl RichtextStateChunk { - pub fn new_text(s: BytesSlice, id: ID) -> Self { + pub fn new_text(s: BytesSlice, id: IdFull) -> Self { Self::Text(TextChunk::new(s, id)) } @@ -384,17 +390,17 @@ impl RichtextStateChunk { Self::Style { style, anchor_type } } - pub(crate) fn get_id_span(&self) -> loro_common::IdSpan { + pub(crate) fn get_id_lp_span(&self) -> IdLpSpan { match self { RichtextStateChunk::Text(t) => { - let id = t.id(); - IdSpan::new(id.peer, id.counter, id.counter + t.unicode_len()) + let id = t.idlp(); + IdLpSpan::new(id.peer, id.lamport, id.lamport + t.unicode_len() as Lamport) } RichtextStateChunk::Style { style, anchor_type } => match anchor_type { - AnchorType::Start => style.id().into(), + AnchorType::Start => style.idlp().into(), AnchorType::End => { - let id = style.id(); - IdSpan::new(id.peer, id.counter + 1, id.counter + 2) + let id = style.idlp(); + IdLpSpan::new(id.peer, id.lamport + 1, id.lamport + 2) } }, } @@ -453,7 +459,7 @@ impl Serialize for RichtextStateChunk { } impl RichtextStateChunk { - pub fn try_new(s: BytesSlice, id: ID) -> Result { + pub fn try_new(s: BytesSlice, id: IdFull) -> Result { std::str::from_utf8(&s)?; Ok(RichtextStateChunk::Text(TextChunk::new(s, id))) } @@ -1238,7 +1244,12 @@ impl RichtextState { } /// This is used to accept changes from DiffCalculator - pub(crate) fn insert_at_entity_index(&mut self, entity_index: usize, text: BytesSlice, id: ID) { + pub(crate) fn insert_at_entity_index( + &mut self, + entity_index: usize, + text: BytesSlice, + id: IdFull, + ) { let elem = RichtextStateChunk::try_new(text, id).unwrap(); self.style_ranges .as_mut() @@ -2291,7 +2302,7 @@ mod test { let state = &mut self.state; let text = self.bytes.slice(start..); let entity_index = state.get_entity_index_for_text_insert(pos, PosType::Unicode); - state.insert_at_entity_index(entity_index, text, ID::new(0, 0)); + state.insert_at_entity_index(entity_index, text, IdFull::new(0, 0, 0)); }; } diff --git a/crates/loro-internal/src/container/richtext/tracker.rs b/crates/loro-internal/src/container/richtext/tracker.rs index 44e569fc..49a8f1ab 100644 --- a/crates/loro-internal/src/container/richtext/tracker.rs +++ b/crates/loro-internal/src/container/richtext/tracker.rs @@ -1,5 +1,5 @@ use generic_btree::{rle::Sliceable, LeafIndex}; -use loro_common::{Counter, HasId, HasIdSpan, IdSpan, PeerID, ID}; +use loro_common::{Counter, HasId, HasIdSpan, IdFull, IdSpan, PeerID, ID}; use rle::HasLength; use crate::VersionVector; @@ -41,7 +41,7 @@ impl Tracker { let result = this.rope.tree.push(FugueSpan { content: RichtextChunk::new_unknown(u32::MAX / 4), - id: ID::new(UNKNOWN_PEER_ID, 0), + id: IdFull::new(UNKNOWN_PEER_ID, 0, 0), status: Status::default(), diff_status: None, origin_left: None, @@ -69,14 +69,14 @@ impl Tracker { &self.applied_vv } - pub(crate) fn insert(&mut self, mut op_id: ID, mut pos: usize, mut content: RichtextChunk) { + pub(crate) fn insert(&mut self, mut op_id: IdFull, mut pos: usize, mut content: RichtextChunk) { // debug_log::group!("TrackerInsert"); // debug_log::debug_dbg!(&op_id, pos, content); // debug_log::debug_dbg!(&self); 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 > op_id.counter { - if !self.current_vv.includes_id(last_id) { + if !self.current_vv.includes_id(last_id.id()) { // PERF: may be slow let mut updates = Default::default(); let cnt_start = self.current_vv.get(&op_id.peer).copied().unwrap_or(0); @@ -93,7 +93,7 @@ impl Tracker { if applied_counter_end > last_id.counter { // the op is included in the applied vv - self.current_vv.extend_to_include_last_id(last_id); + self.current_vv.extend_to_include_last_id(last_id.id()); // debug_log::debug_log!("Ops are already included {:#?}", &self); return; } @@ -122,15 +122,15 @@ impl Tracker { |id| self.id_to_cursor.get_insert(id).unwrap(), ); self.id_to_cursor.push( - op_id, + op_id.id(), id_to_cursor::Cursor::new_insert(result.leaf, content.len()), ); self.update_insert_by_split(&result.splitted.arr); let end_id = op_id.inc(content.len() as Counter); - self.current_vv.extend_to_include_end_id(end_id); - self.applied_vv.extend_to_include_end_id(end_id); + self.current_vv.extend_to_include_end_id(end_id.id()); + self.applied_vv.extend_to_include_end_id(end_id.id()); // debug_log::debug_dbg!(&self); } @@ -381,11 +381,11 @@ mod test { #[test] fn test_len() { let mut t = Tracker::new(); - t.insert(ID::new(1, 0), 0, RichtextChunk::new_text(0..2)); + t.insert(IdFull::new(1, 0, 0), 0, RichtextChunk::new_text(0..2)); assert_eq!(t.rope.len(), 2); t.checkout(&Default::default()); assert_eq!(t.rope.len(), 0); - t.insert(ID::new(2, 0), 0, RichtextChunk::new_text(2..4)); + t.insert(IdFull::new(2, 0, 0), 0, RichtextChunk::new_text(2..4)); let v = vv!(1 => 2, 2 => 2); t.checkout(&v); assert_eq!(&t.applied_vv, &v); @@ -395,7 +395,7 @@ mod test { #[test] fn test_retreat_and_forward_delete() { let mut t = Tracker::new(); - t.insert(ID::new(1, 0), 0, RichtextChunk::new_text(0..10)); + t.insert(IdFull::new(1, 0, 0), 0, RichtextChunk::new_text(0..10)); t.delete(ID::new(2, 0), 0, 10, true); t.checkout(&vv!(1 => 10, 2=>5)); assert_eq!(t.rope.len(), 5); @@ -410,7 +410,7 @@ mod test { #[test] fn test_checkout_in_doc_with_del_span() { let mut t = Tracker::new(); - t.insert(ID::new(1, 0), 0, RichtextChunk::new_text(0..10)); + t.insert(IdFull::new(1, 0, 0), 0, RichtextChunk::new_text(0..10)); t.delete(ID::new(2, 0), 0, 10, false); t.checkout(&vv!(1 => 10, 2=>4)); let v: Vec = t.rope.tree().iter().copied().collect(); @@ -424,7 +424,7 @@ mod test { #[test] fn test_checkout_in_doc_with_reversed_del_span() { let mut t = Tracker::new(); - t.insert(ID::new(1, 0), 0, RichtextChunk::new_text(0..10)); + t.insert(IdFull::new(1, 0, 0), 0, RichtextChunk::new_text(0..10)); t.delete(ID::new(2, 0), 0, 10, true); t.checkout(&vv!(1 => 10, 2=>4)); let v: Vec = t.rope.tree().iter().copied().collect(); 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 eeaf3af7..22770015 100644 --- a/crates/loro-internal/src/container/richtext/tracker/crdt_rope.rs +++ b/crates/loro-internal/src/container/richtext/tracker/crdt_rope.rs @@ -5,7 +5,7 @@ use generic_btree::{ BTree, BTreeTrait, Cursor, FindResult, LeafIndex, Query, SplittedLeaves, }; use itertools::Itertools; -use loro_common::{Counter, HasCounter, HasCounterSpan, HasIdSpan, IdSpan, ID}; +use loro_common::{Counter, HasCounter, HasCounterSpan, HasIdSpan, IdFull, IdSpan, ID}; use smallvec::SmallVec; use crate::container::richtext::{fugue_span::DiffStatus, FugueSpan, RichtextChunk, Status}; @@ -74,7 +74,8 @@ impl CrdtRope { left_node .elem() .id - .inc(left_node.elem().rle_len() as Counter - 1), + .inc(left_node.elem().rle_len() as Counter - 1) + .id(), ) } else { None @@ -82,7 +83,7 @@ impl CrdtRope { } else { let left_node = self.tree.get_leaf(start.leaf().into()); assert!(left_node.elem().rle_len() >= start.offset()); - Some(left_node.elem().id.inc(start.offset() as Counter - 1)) + Some(left_node.elem().id.inc(start.offset() as Counter - 1).id()) }; let (origin_right, parent_right_leaf, in_between) = { @@ -97,7 +98,8 @@ impl CrdtRope { } if !iter.elem.status.future { - origin_right = Some(iter.elem.id.inc(iter.start.unwrap_or(0) as Counter)); + origin_right = + Some(iter.elem.id.inc(iter.start.unwrap_or(0) as Counter).id()); let parent_right = match iter.start { Some(offset) if offset > 0 => { // It's guaranteed that origin_right's origin_left == this.origin_left. @@ -177,7 +179,7 @@ impl CrdtRope { let elem_idx = find_elem(other_origin_right); let elem = self.tree.get_elem(elem_idx).unwrap(); // It must be the start of the elem - assert_eq!(elem.id, other_origin_right); + assert_eq!(elem.id.id(), other_origin_right); if elem.origin_left == content.origin_left { Some(elem_idx) } else { @@ -408,7 +410,7 @@ impl CrdtRope { #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub(crate) enum CrdtRopeDelta { Retain(usize), - Insert { chunk: RichtextChunk, id: ID }, + Insert { chunk: RichtextChunk, id: IdFull }, Delete(usize), } @@ -627,7 +629,7 @@ mod test { fn span(id: u32, range: Range) -> FugueSpan { FugueSpan::new( - ID::new(id as PeerID, 0 as Counter), + IdFull::new(id as PeerID, 0 as Counter, 0), RichtextChunk::new_text(range), ) } @@ -635,14 +637,14 @@ mod test { #[allow(unused)] fn unknown_span(id: u32, len: usize) -> FugueSpan { FugueSpan::new( - ID::new(id as PeerID, 0 as Counter), + IdFull::new(id as PeerID, 0 as Counter, 0), RichtextChunk::new_unknown(len as u32), ) } fn future_span(id: u32, range: Range) -> FugueSpan { let mut fugue = FugueSpan::new( - ID::new(id as PeerID, 0 as Counter), + IdFull::new(id as PeerID, 0 as Counter, 0), RichtextChunk::new_text(range), ); @@ -652,7 +654,7 @@ mod test { fn dead_span(id: u32, range: Range) -> FugueSpan { let mut span = FugueSpan::new( - ID::new(id as PeerID, 0 as Counter), + IdFull::new(id as PeerID, 0 as Counter, 0), RichtextChunk::new_text(range), ); @@ -825,7 +827,7 @@ mod test { CrdtRopeDelta::Retain(2), CrdtRopeDelta::Insert { chunk: RichtextChunk::new_text(10..13), - id: ID::new(1, 0) + id: IdFull::new(1, 0, 0) } ], vec, @@ -849,7 +851,7 @@ mod test { assert_eq!( vec![CrdtRopeDelta::Insert { chunk: RichtextChunk::new_text(2..10), - id: ID::new(0, 2) + id: IdFull::new(0, 2, 2) }], vec, ); diff --git a/crates/loro-internal/src/delta/map_delta.rs b/crates/loro-internal/src/delta/map_delta.rs index 1e0eb790..6fa122e5 100644 --- a/crates/loro-internal/src/delta/map_delta.rs +++ b/crates/loro-internal/src/delta/map_delta.rs @@ -4,16 +4,12 @@ use std::{ }; use fxhash::FxHashMap; +use loro_common::IdLp; use serde::{ser::SerializeStruct, Serialize}; use crate::{ - arena::SharedArena, - change::Lamport, - handler::ValueOrContainer, - id::{Counter, PeerID, ID}, - span::{HasId, HasLamport}, - txn::Transaction, - DocState, InternalString, LoroValue, + arena::SharedArena, change::Lamport, handler::ValueOrContainer, id::PeerID, span::HasLamport, + txn::Transaction, DocState, InternalString, LoroValue, }; #[derive(Default, Debug, Clone, Serialize)] @@ -23,7 +19,6 @@ pub struct MapDelta { #[derive(Debug, Clone)] pub struct MapValue { - pub counter: Counter, pub value: Option, pub lamp: Lamport, pub peer: PeerID, @@ -52,8 +47,8 @@ impl PartialEq for MapValue { impl Eq for MapValue {} impl MapValue { - pub fn id(&self) -> ID { - ID::new(self.peer, self.counter) + pub fn idlp(&self) -> IdLp { + IdLp::new(self.peer, self.lamp) } } @@ -64,9 +59,8 @@ pub struct ResolvedMapDelta { #[derive(Debug, Clone)] pub struct ResolvedMapValue { - pub counter: Counter, pub value: Option, - pub lamport: (Lamport, PeerID), + pub idlp: IdLp, } impl ResolvedMapValue { @@ -77,8 +71,7 @@ impl ResolvedMapValue { state: &Weak>, ) -> Self { ResolvedMapValue { - counter: v.counter, - lamport: (v.lamp, v.peer), + idlp: IdLp::new(v.peer, v.lamp), value: v .value .map(|v| ValueOrContainer::from_value(v, arena, txn, state)), @@ -120,7 +113,7 @@ impl ResolvedMapDelta { let mut updated = self.updated.clone(); for (k, v) in x.updated.into_iter() { if let Some(old) = updated.get_mut(&k) { - if v.lamport > old.lamport { + if v.idlp > old.idlp { *old = v; } } else { @@ -147,17 +140,11 @@ impl ResolvedMapDelta { impl Hash for MapValue { fn hash(&self, state: &mut H) { // value is not being hashed - self.counter.hash(state); + self.peer.hash(state); self.lamp.hash(state); } } -impl HasId for MapValue { - fn id_start(&self) -> crate::id::ID { - ID::new(self.peer, self.counter) - } -} - impl HasLamport for MapValue { fn lamport(&self) -> Lamport { self.lamp @@ -172,7 +159,7 @@ impl Serialize for MapValue { let mut s = serializer.serialize_struct("MapValue", 2)?; s.serialize_field("value", &self.value)?; s.serialize_field("lamport", &self.lamp)?; - s.serialize_field("id", &self.id())?; + s.serialize_field("id", &self.idlp())?; s.end() } } diff --git a/crates/loro-internal/src/delta/tree.rs b/crates/loro-internal/src/delta/tree.rs index 03a34167..12c2d040 100644 --- a/crates/loro-internal/src/delta/tree.rs +++ b/crates/loro-internal/src/delta/tree.rs @@ -4,7 +4,7 @@ use std::{ }; use fxhash::{FxHashMap, FxHashSet}; -use loro_common::{ContainerType, LoroValue, TreeID, ID}; +use loro_common::{ContainerType, IdFull, LoroValue, TreeID}; use serde::Serialize; use crate::state::TreeParentId; @@ -70,7 +70,7 @@ pub struct TreeDelta { pub struct TreeDeltaItem { pub target: TreeID, pub action: TreeInternalDiff, - pub last_effective_move_op_id: ID, + pub last_effective_move_op_id: IdFull, } /// The action of [`TreeDiff`]. It's the same as [`crate::container::tree::tree_op::TreeOp`], but semantic. @@ -97,7 +97,7 @@ impl TreeDeltaItem { target: TreeID, parent: TreeParentId, old_parent: TreeParentId, - op_id: ID, + op_id: IdFull, is_new_parent_deleted: bool, is_old_parent_deleted: bool, ) -> Self { diff --git a/crates/loro-internal/src/diff_calc.rs b/crates/loro-internal/src/diff_calc.rs index 829a130e..96789ad5 100644 --- a/crates/loro-internal/src/diff_calc.rs +++ b/crates/loro-internal/src/diff_calc.rs @@ -18,7 +18,6 @@ use crate::{ }, delta::{Delta, MapDelta, MapValue}, event::InternalDiff, - id::Counter, op::{RichOp, SliceRange, SliceRanges}, span::{HasId, HasLamport}, version::Frontiers, @@ -416,14 +415,12 @@ impl DiffCalculatorTrait for MapDiffCalculator { } MapValue { - counter: v.counter, value, lamp: v.lamport, peer: v.peer, } }) .unwrap_or_else(|| MapValue { - counter: 0, value: None, lamp: 0, peer: 0, @@ -439,7 +436,6 @@ impl DiffCalculatorTrait for MapDiffCalculator { struct CompactMapValue { lamport: Lamport, peer: PeerID, - counter: Counter, value: Option, } @@ -457,12 +453,6 @@ impl PartialOrd for CompactMapValue { } } -impl HasId for CompactMapValue { - fn id_start(&self) -> ID { - ID::new(self.peer, self.counter) - } -} - use rle::{HasLength, Sliceable}; #[derive(Default)] @@ -503,7 +493,7 @@ impl DiffCalculatorTrait for ListDiffCalculator { crate::op::InnerContent::List(l) => match l { crate::container::list::list_op::InnerListOp::Insert { slice, pos } => { self.tracker.insert( - op.id_start(), + op.id_full(), *pos, RichtextChunk::new_text(slice.0.clone()), ); @@ -603,7 +593,7 @@ impl DiffCalculatorTrait for RichtextDiffCalculator { pos, } => { self.tracker.insert( - op.id_start(), + op.id_full(), *pos as usize, RichtextChunk::new_text(*unicode_start..*unicode_start + *len), ); @@ -634,12 +624,12 @@ impl DiffCalculatorTrait for RichtextDiffCalculator { info: *info, }); self.tracker.insert( - op.id_start(), + op.id_full(), *start as usize, RichtextChunk::new_style_anchor(style_id as u32, AnchorType::Start), ); self.tracker.insert( - op.id_start().inc(1), + op.id_full().inc(1), // need to shift 1 because we insert the start style anchor before this pos *end as usize + 1, RichtextChunk::new_style_anchor(style_id as u32, AnchorType::End), diff --git a/crates/loro-internal/src/diff_calc/tree.rs b/crates/loro-internal/src/diff_calc/tree.rs index 103d9776..0cad6d7e 100644 --- a/crates/loro-internal/src/diff_calc/tree.rs +++ b/crates/loro-internal/src/diff_calc/tree.rs @@ -2,7 +2,7 @@ use std::collections::BTreeSet; use fxhash::FxHashMap; use itertools::Itertools; -use loro_common::{ContainerID, HasId, IdSpan, Lamport, TreeID, ID}; +use loro_common::{ContainerID, HasId, IdFull, IdSpan, Lamport, TreeID, ID}; use crate::{ container::idx::ContainerIdx, @@ -243,7 +243,7 @@ impl TreeDiffCalculator { op.target, op.parent, old_parent, - op.id, + op.id_full(), is_parent_deleted, is_old_parent_deleted, ); @@ -300,6 +300,16 @@ pub struct MoveLamportAndID { pub(crate) effected: bool, } +impl MoveLamportAndID { + fn id_full(&self) -> IdFull { + IdFull { + peer: self.id.peer, + lamport: self.lamport, + counter: self.id.counter, + } + } +} + impl PartialOrd for MoveLamportAndID { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -374,12 +384,12 @@ impl TreeCacheForDiff { } /// get the parent of the first effected op and its id - fn get_parent_with_id(&self, tree_id: TreeID) -> (TreeParentId, ID) { - let mut ans = (TreeParentId::Unexist, ID::NONE_ID); + fn get_parent_with_id(&self, tree_id: TreeID) -> (TreeParentId, IdFull) { + let mut ans = (TreeParentId::Unexist, IdFull::NONE_ID); if let Some(cache) = self.tree.get(&tree_id) { for op in cache.iter().rev() { if op.effected { - ans = (op.parent, op.id); + ans = (op.parent, op.id_full()); break; } } @@ -406,7 +416,7 @@ impl TreeCacheForDiff { ans } - fn get_children_with_id(&self, parent: TreeParentId) -> Vec<(TreeID, ID)> { + fn get_children_with_id(&self, parent: TreeParentId) -> Vec<(TreeID, IdFull)> { let mut ans = vec![]; for (tree_id, _) in self.tree.iter() { let Some(op) = self.get_last_effective_move(*tree_id) else { @@ -414,7 +424,7 @@ impl TreeCacheForDiff { }; if op.parent == parent { - ans.push((*tree_id, op.id)); + ans.push((*tree_id, op.id_full())); } } diff --git a/crates/loro-internal/src/encoding.rs b/crates/loro-internal/src/encoding.rs index 78d443dd..a112031d 100644 --- a/crates/loro-internal/src/encoding.rs +++ b/crates/loro-internal/src/encoding.rs @@ -3,7 +3,7 @@ mod encode_reordered; use crate::op::OpWithId; use crate::LoroDoc; use crate::{oplog::OpLog, LoroError, VersionVector}; -use loro_common::{HasCounter, IdSpan, LoroResult}; +use loro_common::{IdLpSpan, LoroResult}; use num_traits::{FromPrimitive, ToPrimitive}; use rle::{HasLength, Sliceable}; const MAGIC_BYTES: [u8; 4] = *b"loro"; @@ -86,24 +86,24 @@ pub(crate) struct StateSnapshotEncoder<'a> { /// The `check_idspan` function is used to check if the id span is valid. /// If the id span is invalid, the function should return an error that /// contains the missing id span. - check_idspan: &'a dyn Fn(IdSpan) -> Result<(), IdSpan>, + check_idspan: &'a dyn Fn(IdLpSpan) -> Result<(), IdLpSpan>, /// The `encoder_by_op` function is used to encode an operation. encoder_by_op: &'a mut dyn FnMut(OpWithId), /// The `record_idspan` function is used to record the id span to track the /// encoded order. - record_idspan: &'a mut dyn FnMut(IdSpan), + record_idspan: &'a mut dyn FnMut(IdLpSpan), #[allow(unused)] mode: EncodeMode, } impl StateSnapshotEncoder<'_> { - pub fn encode_op(&mut self, id_span: IdSpan, get_op: impl FnOnce() -> OpWithId) { + pub fn encode_op(&mut self, id_span: IdLpSpan, get_op: impl FnOnce() -> OpWithId) { if let Err(span) = (self.check_idspan)(id_span) { let mut op = get_op(); if span == id_span { (self.encoder_by_op)(op); } else { - debug_assert_eq!(span.ctr_start(), id_span.ctr_start()); + debug_assert_eq!(span.lamport.start, id_span.lamport.start); op.op = op.op.slice(span.atom_len(), op.op.atom_len()); (self.encoder_by_op)(op); } @@ -121,6 +121,7 @@ impl StateSnapshotEncoder<'_> { pub(crate) struct StateSnapshotDecodeContext<'a> { pub oplog: &'a OpLog, pub ops: &'a mut dyn Iterator, + #[allow(unused)] pub blob: &'a [u8], pub mode: EncodeMode, } diff --git a/crates/loro-internal/src/encoding/encode_reordered.rs b/crates/loro-internal/src/encoding/encode_reordered.rs index 694ad720..fd276665 100644 --- a/crates/loro-internal/src/encoding/encode_reordered.rs +++ b/crates/loro-internal/src/encoding/encode_reordered.rs @@ -4,7 +4,7 @@ use fxhash::{FxHashMap, FxHashSet}; use generic_btree::rle::Sliceable; use itertools::Itertools; use loro_common::{ - ContainerID, ContainerType, Counter, HasCounterSpan, HasId, HasIdSpan, HasLamportSpan, IdSpan, + ContainerID, ContainerType, Counter, HasCounterSpan, HasId, HasIdSpan, HasLamportSpan, IdLp, InternalString, LoroError, LoroResult, PeerID, ID, }; use num_traits::FromPrimitive; @@ -13,7 +13,7 @@ use serde_columnar::columnar; use crate::{ arena::SharedArena, - change::Change, + change::{Change, Lamport}, container::{idx::ContainerIdx, list::list_op::DeleteSpan, richtext::TextStyleInfoFlag}, encoding::{ encode_reordered::value::{ValueKind, ValueWriter}, @@ -315,6 +315,7 @@ fn extract_ops( ops.push(OpWithId { peer, op: op.clone(), + lamport: None, }); } @@ -381,15 +382,16 @@ pub(crate) fn encode_snapshot(oplog: &OpLog, state: &DocState, vv: &VersionVecto let mut op_len = 0; let bytes = state.encode_snapshot(super::StateSnapshotEncoder { - check_idspan: &|id_span| { - if let Some(counter) = vv.intersect_span(id_span) { - Err(IdSpan { - client_id: id_span.client_id, - counter, - }) - } else { - Ok(()) - } + check_idspan: &|_id_span| { + // TODO: todo!("check intersection by vv that defined by idlp"); + // if let Some(counter) = vv.intersect_span(id_span) { + // Err(IdSpan { + // client_id: id_span.peer, + // counter, + // }) + // } else { + Ok(()) + // } }, encoder_by_op: &mut |op| { origin_ops.push(TempOp { @@ -398,15 +400,16 @@ pub(crate) fn encode_snapshot(oplog: &OpLog, state: &DocState, vv: &VersionVecto peer_id: op.peer, container_index, prop_that_used_for_sort: -1, - // lamport value is fake, but it's only used for sorting and will not be encoded - lamport: 0, + lamport: op.lamport.unwrap(), }); }, record_idspan: &mut |id_span| { let len = id_span.atom_len(); op_len += len; pos_mapping_heap.push(PosMappingItem { - start_id: id_span.id_start(), + start_id: oplog + .idlp_to_id(IdLp::new(id_span.peer, id_span.lamport.start)) + .unwrap(), len, target_value: pos_target_value, }); @@ -475,6 +478,12 @@ pub(crate) fn encode_snapshot(oplog: &OpLog, state: &DocState, vv: &VersionVecto serde_columnar::to_vec(&doc).unwrap() } +#[derive(Clone, Copy, PartialEq, Debug, Eq, PartialOrd, Ord)] +struct IdWithLamport { + peer: PeerID, + lamport: Lamport, +} + #[derive(Clone, Copy, PartialEq, Debug, Eq)] struct PosMappingItem { start_id: ID, @@ -500,7 +509,10 @@ impl PosMappingItem { let new_len = self.len - pos; self.len = pos; PosMappingItem { - start_id: self.start_id.inc(pos as i32), + start_id: ID { + peer: self.start_id.peer, + counter: self.start_id.counter + pos as Counter, + }, len: new_len, target_value: self.target_value + pos as i32, } @@ -513,12 +525,14 @@ fn calc_sorted_ops_for_snapshot<'a>( ) -> Vec> { origin_ops.sort_unstable(); pos_mapping_heap.sort_unstable(); + debug_log::debug_dbg!(&origin_ops, &pos_mapping_heap); let mut ops: Vec> = Vec::with_capacity(origin_ops.len()); let ops_len: usize = origin_ops.iter().map(|x| x.atom_len()).sum(); let mut origin_top = origin_ops.pop(); let mut pos_top = pos_mapping_heap.pop(); while origin_top.is_some() || pos_top.is_some() { + debug_log::debug_dbg!(&origin_top, &pos_top); let Some(mut inner_origin_top) = origin_top else { unreachable!() }; @@ -639,7 +653,7 @@ pub(crate) fn decode_snapshot(doc: &LoroDoc, bytes: &[u8]) -> LoroResult<()> { let ExtractedOps { ops_map, - ops, + mut ops, containers, } = extract_ops( &iter.raw_values, @@ -651,6 +665,14 @@ pub(crate) fn decode_snapshot(doc: &LoroDoc, bytes: &[u8]) -> LoroResult<()> { true, )?; + let changes = decode_changes(iter.changes, iter.start_counters, peer_ids, deps, ops_map)?; + let (new_ids, pending_changes) = import_changes_to_oplog(changes, &mut oplog)?; + + for op in ops.iter_mut() { + // update op's lamport + op.lamport = oplog.get_lamport_at(op.id()); + } + decode_snapshot_states( &mut state, frontiers, @@ -661,8 +683,6 @@ pub(crate) fn decode_snapshot(doc: &LoroDoc, bytes: &[u8]) -> LoroResult<()> { &oplog, ) .unwrap(); - let changes = decode_changes(iter.changes, iter.start_counters, peer_ids, deps, ops_map)?; - let (new_ids, pending_changes) = import_changes_to_oplog(changes, &mut oplog)?; assert!(pending_changes.is_empty()); // we cannot assert this because frontiers of oplog is not updated yet when batch_importing // assert_eq!(&state.frontiers, oplog.frontiers()); @@ -820,6 +840,7 @@ mod encode { self.op.atom_len() } } + impl<'a> generic_btree::rle::HasLength for TempOp<'a> { #[inline(always)] fn rle_len(&self) -> usize { @@ -827,6 +848,22 @@ mod encode { } } + impl TempOp<'_> { + pub fn idlp(&self) -> IdWithLamport { + IdWithLamport { + peer: self.peer_id, + lamport: self.lamport, + } + } + + pub fn idlp_end(&self) -> IdWithLamport { + IdWithLamport { + peer: self.peer_id, + lamport: self.lamport + self.op.atom_len() as Lamport, + } + } + } + impl<'a> generic_btree::rle::Sliceable for TempOp<'a> { fn _slice(&self, range: std::ops::Range) -> TempOp<'a> { Self { @@ -916,8 +953,8 @@ mod encode { msg_len: 0, }); - for (i, op) in change.ops().iter().enumerate() { - let lamport = i as Lamport + change.lamport(); + for op in change.ops().iter() { + let lamport = (op.counter - change.id.counter) as Lamport + change.lamport(); push_op(TempOp { op: Cow::Borrowed(op), lamport, @@ -936,7 +973,7 @@ mod encode { use super::{ value::{MarkStart, Value, ValueKind}, - EncodedChange, EncodedOp, + EncodedChange, EncodedOp, IdWithLamport, }; mod value_register { use fxhash::FxHashMap; diff --git a/crates/loro-internal/src/fuzz/recursive_refactored.rs b/crates/loro-internal/src/fuzz/recursive_refactored.rs index 73608799..341ef120 100644 --- a/crates/loro-internal/src/fuzz/recursive_refactored.rs +++ b/crates/loro-internal/src/fuzz/recursive_refactored.rs @@ -4345,6 +4345,208 @@ mod failed_tests { ) } + #[test] + fn fuzz_15() { + test_multi_sites( + 5, + &mut [ + List { + site: 90, + container_idx: 90, + key: 90, + value: I32(1515870810), + }, + List { + site: 90, + container_idx: 175, + key: 165, + value: I32(1515890085), + }, + List { + site: 90, + container_idx: 90, + key: 131, + value: I32(1520805286), + }, + Sync { from: 122, to: 90 }, + Sync { from: 165, to: 165 }, + Sync { from: 90, to: 90 }, + List { + site: 26, + container_idx: 90, + key: 131, + value: I32(1515879083), + }, + Sync { from: 165, to: 165 }, + List { + site: 90, + container_idx: 90, + key: 90, + value: I32(1509972611), + }, + List { + site: 165, + container_idx: 165, + key: 165, + value: I32(1515870810), + }, + ], + ) + } + + #[test] + fn fuzz_16() { + test_multi_sites( + 5, + &mut [ + List { + site: 8, + container_idx: 0, + key: 92, + value: Null, + }, + Sync { from: 113, to: 7 }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: I32(-1077952577), + }, + Map { + site: 191, + container_idx: 191, + key: 191, + value: Container(C::Text), + }, + Sync { from: 61, to: 58 }, + List { + site: 58, + container_idx: 58, + key: 58, + value: I32(1617542919), + }, + List { + site: 191, + container_idx: 191, + key: 191, + value: Container(C::Text), + }, + Sync { from: 202, to: 202 }, + List { + site: 0, + container_idx: 58, + key: 58, + value: Null, + }, + List { + site: 58, + container_idx: 186, + key: 58, + value: Null, + }, + Sync { from: 8, to: 92 }, + Sync { from: 191, to: 28 }, + List { + site: 0, + container_idx: 100, + key: 191, + value: I32(1618020287), + }, + List { + site: 191, + container_idx: 191, + key: 191, + value: Container(C::Text), + }, + Sync { from: 202, to: 191 }, + Sync { from: 191, to: 113 }, + List { + site: 191, + container_idx: 191, + key: 191, + value: Container(C::Text), + }, + Map { + site: 58, + container_idx: 58, + key: 245, + value: I32(-1077976064), + }, + List { + site: 58, + container_idx: 0, + key: 100, + value: Container(C::Map), + }, + Map { + site: 100, + container_idx: 191, + key: 191, + value: Null, + }, + ], + ) + } + + #[test] + fn fuzz_17() { + test_multi_sites( + 5, + &mut [ + Text { + site: 3, + container_idx: 0, + pos: 0, + value: 27756, + is_del: false, + }, + Text { + site: 3, + container_idx: 0, + pos: 2, + value: 47288, + is_del: false, + }, + List { + site: 1, + container_idx: 0, + key: 0, + value: I32(1), + }, + Text { + site: 3, + container_idx: 0, + pos: 10, + value: 4, + is_del: true, + }, + Text { + site: 0, + container_idx: 0, + pos: 0, + value: 27756, + is_del: false, + }, + Sync { from: 3, to: 4 }, + Text { + site: 0, + container_idx: 0, + pos: 6, + value: 27756, + is_del: false, + }, + Sync { from: 4, to: 0 }, + Text { + site: 0, + container_idx: 0, + pos: 13, + value: 15476, + is_del: false, + }, + ], + ) + } + #[test] fn to_minify() { minify_error(5, vec![], test_multi_sites, normalize) diff --git a/crates/loro-internal/src/op.rs b/crates/loro-internal/src/op.rs index e768a83b..8dc90aa7 100644 --- a/crates/loro-internal/src/op.rs +++ b/crates/loro-internal/src/op.rs @@ -6,7 +6,7 @@ use crate::{ }; use crate::{delta::DeltaValue, LoroValue}; use enum_as_inner::EnumAsInner; -use loro_common::IdSpan; +use loro_common::{IdFull, IdSpan}; use rle::{HasIndex, HasLength, Mergable, Sliceable}; use serde::{ser::SerializeSeq, Deserialize, Serialize}; use smallvec::SmallVec; @@ -29,6 +29,7 @@ pub struct Op { pub(crate) struct OpWithId { pub peer: PeerID, pub op: Op, + pub lamport: Option, } impl OpWithId { @@ -39,6 +40,14 @@ impl OpWithId { } } + pub fn id_full(&self) -> IdFull { + IdFull::new( + self.peer, + self.op.counter, + self.lamport.expect("op should already be imported"), + ) + } + #[allow(unused)] pub fn id_span(&self) -> IdSpan { IdSpan::new( @@ -66,6 +75,12 @@ pub struct RawOp<'a> { pub content: RawOpContent<'a>, } +impl RawOp<'_> { + pub(crate) fn id_full(&self) -> loro_common::IdFull { + IdFull::new(self.id.peer, self.id.counter, self.lamport) + } +} + /// RichOp includes lamport and timestamp info, which is used for conflict resolution. #[derive(Debug, Clone)] pub struct RichOp<'a> { @@ -77,15 +92,6 @@ pub struct RichOp<'a> { pub end: usize, } -/// RichOp includes lamport and timestamp info, which is used for conflict resolution. -#[derive(Debug, Clone)] -pub struct OwnedRichOp { - pub op: Op, - pub client_id: PeerID, - pub lamport: Lamport, - pub timestamp: Timestamp, -} - impl Op { #[inline] #[allow(unused)] @@ -262,15 +268,6 @@ impl<'a> RichOp<'a> { self.op.slice(self.start, self.end) } - pub fn as_owned(&self) -> OwnedRichOp { - OwnedRichOp { - op: self.get_sliced(), - client_id: self.peer, - lamport: self.lamport, - timestamp: self.timestamp, - } - } - pub fn op(&self) -> &Op { self.op } @@ -298,18 +295,9 @@ impl<'a> RichOp<'a> { counter: self.op.counter + self.start as Counter, } } -} -impl OwnedRichOp { - pub fn rich_op(&self) -> RichOp { - RichOp { - op: &self.op, - peer: self.client_id, - lamport: self.lamport, - timestamp: self.timestamp, - start: 0, - end: self.op.atom_len(), - } + pub(crate) fn id_full(&self) -> IdFull { + IdFull::new(self.peer, self.op.counter, self.lamport) } } @@ -461,7 +449,7 @@ impl<'a> Mergable for ListSlice<'a> { #[derive(Debug, Clone)] pub struct SliceRanges { pub ranges: SmallVec<[SliceRange; 2]>, - pub id: ID, + pub id: IdFull, } impl Serialize for SliceRanges { @@ -487,6 +475,10 @@ impl DeltaValue for SliceRanges { return Err(other); } + if self.id.lamport + self.length() as Lamport != other.id.lamport { + return Err(other); + } + self.ranges.extend(other.ranges); Ok(()) } diff --git a/crates/loro-internal/src/oplog.rs b/crates/loro-internal/src/oplog.rs index e104ada6..4a3dd8aa 100644 --- a/crates/loro-internal/src/oplog.rs +++ b/crates/loro-internal/src/oplog.rs @@ -460,6 +460,11 @@ impl OpLog { self.get_change_at(id).map(|c| c.lamport).unwrap_or(0) } + pub(crate) fn get_lamport_at(&self, id: ID) -> Option { + self.get_change_at(id) + .map(|c| c.lamport + (id.counter - c.id.counter) as Lamport) + } + pub(crate) fn get_max_lamport_at(&self, id: ID) -> Lamport { self.get_change_at(id) .map(|c| { @@ -817,6 +822,34 @@ impl OpLog { 0 } } + + pub(crate) fn idlp_to_id(&self, id: loro_common::IdLp) -> Option { + if let Some(peer_changes) = self.changes.get(&id.peer) { + let r = peer_changes.binary_search_by(|c| { + if c.lamport > id.lamport { + Ordering::Greater + } else if c.lamport + c.atom_len() as Lamport <= id.lamport { + Ordering::Less + } else { + Ordering::Equal + } + }); + + match r { + Ok(index) => { + let change = &peer_changes[index]; + let counter = (id.lamport - change.lamport) as Counter + change.id.counter; + Some(ID::new(id.peer, counter)) + } + Err(_) => { + debug_log::debug_dbg!(id, &peer_changes); + None + } + } + } else { + None + } + } } #[derive(Debug)] diff --git a/crates/loro-internal/src/state/list_state.rs b/crates/loro-internal/src/state/list_state.rs index 99cb96b3..b355a164 100644 --- a/crates/loro-internal/src/state/list_state.rs +++ b/crates/loro-internal/src/state/list_state.rs @@ -22,7 +22,7 @@ use generic_btree::{ rle::{HasLength, Mergeable, Sliceable}, BTree, BTreeTrait, Cursor, LeafIndex, LengthFinder, UseLengthFinder, }; -use loro_common::{IdSpan, LoroResult, ID}; +use loro_common::{IdFull, IdLpSpan, LoroResult}; #[derive(Debug)] pub struct ListState { @@ -44,7 +44,7 @@ impl Clone for ListState { #[derive(Debug, Clone)] pub(crate) struct Elem { pub v: LoroValue, - pub id: ID, + pub id: IdFull, } impl HasLength for Elem { @@ -161,7 +161,7 @@ impl ListState { Some(index as usize) } - pub fn insert(&mut self, index: usize, value: LoroValue, id: ID) { + pub fn insert(&mut self, index: usize, value: LoroValue, id: IdFull) { if index > self.len() { panic!("Index {index} out of range. The length is {}", self.len()); } @@ -231,7 +231,7 @@ impl ListState { // PERF: use &[LoroValue] // PERF: batch - pub fn insert_batch(&mut self, index: usize, values: Vec, start_id: ID) { + pub fn insert_batch(&mut self, index: usize, values: Vec, start_id: IdFull) { let mut id = start_id; for (i, value) in values.into_iter().enumerate() { self.insert(index + i, value, id); @@ -387,10 +387,10 @@ impl ContainerState for ListState { crate::container::list::list_op::ListOp::Insert { slice, pos } => match slice { ListSlice::RawData(list) => match list { std::borrow::Cow::Borrowed(list) => { - self.insert_batch(*pos, list.to_vec(), op.id); + self.insert_batch(*pos, list.to_vec(), op.id_full()); } std::borrow::Cow::Owned(list) => { - self.insert_batch(*pos, list.clone(), op.id); + self.insert_batch(*pos, list.clone(), op.id_full()); } }, _ => unreachable!(), @@ -445,7 +445,7 @@ impl ContainerState for ListState { #[doc = "Get a list of ops that can be used to restore the state to the current state"] fn encode_snapshot(&self, mut encoder: StateSnapshotEncoder) -> Vec { for elem in self.list.iter() { - let id_span: IdSpan = elem.id.into(); + let id_span: IdLpSpan = elem.id.idlp().into(); encoder.encode_op(id_span, || unimplemented!()); } @@ -463,7 +463,7 @@ impl ContainerState for ListState { .arena .get_values(value.0.start as usize..value.0.end as usize); let len = list.len(); - self.insert_batch(index, list, op.id()); + self.insert_batch(index, list, op.id_full()); index += len; } } @@ -482,11 +482,11 @@ mod test { fn id(name: &str) -> ContainerID { ContainerID::new_root(name, crate::ContainerType::List) } - list.insert(0, LoroValue::Container(id("abc")), ID::new(0, 0)); - list.insert(0, LoroValue::Container(id("x")), ID::new(0, 0)); + list.insert(0, LoroValue::Container(id("abc")), IdFull::new(0, 0, 0)); + list.insert(0, LoroValue::Container(id("x")), IdFull::new(0, 0, 0)); assert_eq!(list.get_child_container_index(&id("x")), Some(0)); assert_eq!(list.get_child_container_index(&id("abc")), Some(1)); - list.insert(1, LoroValue::Bool(false), ID::new(0, 0)); + list.insert(1, LoroValue::Bool(false), IdFull::new(0, 0, 0)); assert_eq!(list.get_child_container_index(&id("x")), Some(0)); assert_eq!(list.get_child_container_index(&id("abc")), Some(2)); } diff --git a/crates/loro-internal/src/state/map_state.rs b/crates/loro-internal/src/state/map_state.rs index 2b60ba23..b32d3cb7 100644 --- a/crates/loro-internal/src/state/map_state.rs +++ b/crates/loro-internal/src/state/map_state.rs @@ -4,7 +4,7 @@ use std::{ }; use fxhash::FxHashMap; -use loro_common::{ContainerID, LoroResult}; +use loro_common::{ContainerID, IdLp, LoroResult}; use rle::HasLength; use crate::{ @@ -16,7 +16,6 @@ use crate::{ handler::ValueOrContainer, op::{Op, RawOp, RawOpContent}, txn::Transaction, - utils::delta_rle_encoded_num::DeltaRleEncodedNums, DocState, InternalString, LoroValue, }; @@ -57,8 +56,7 @@ impl ContainerState for MapState { resolved_delta = resolved_delta.with_entry( key, ResolvedMapValue { - counter: value.counter, - lamport: (value.lamp, value.peer), + idlp: IdLp::new(value.peer, value.lamp), value: value .value .map(|v| ValueOrContainer::from_value(v, arena, txn, state)), @@ -88,7 +86,6 @@ impl ContainerState for MapState { MapValue { lamp: op.lamport, peer: op.id.peer, - counter: op.id.counter, value: None, }, ); @@ -100,7 +97,6 @@ impl ContainerState for MapState { MapValue { lamp: op.lamport, peer: op.id.peer, - counter: op.id.counter, value: Some(value.clone().unwrap()), }, ); @@ -163,20 +159,16 @@ impl ContainerState for MapState { #[doc = " Get a list of ops that can be used to restore the state to the current state"] fn encode_snapshot(&self, mut encoder: StateSnapshotEncoder) -> Vec { - let mut lamports = DeltaRleEncodedNums::new(); for v in self.map.values() { - lamports.push(v.lamp); - encoder.encode_op(v.id().into(), || unimplemented!()); + encoder.encode_op(v.idlp().into(), || unimplemented!()); } - lamports.encode() + Default::default() } #[doc = " Restore the state to the state represented by the ops that exported by `get_snapshot_ops`"] fn import_from_snapshot_ops(&mut self, ctx: StateSnapshotDecodeContext) { assert_eq!(ctx.mode, EncodeMode::Snapshot); - let lamports = DeltaRleEncodedNums::decode(ctx.blob); - let mut iter = lamports.iter(); for op in ctx.ops { debug_assert_eq!( op.op.atom_len(), @@ -188,9 +180,8 @@ impl ContainerState for MapState { self.map.insert( content.key.clone(), MapValue { - counter: op.op.counter, value: content.value.clone(), - lamp: iter.next().unwrap(), + lamp: op.lamport.expect("op should already be imported"), peer: op.peer, }, ); diff --git a/crates/loro-internal/src/state/richtext_state.rs b/crates/loro-internal/src/state/richtext_state.rs index 4580b854..c0528754 100644 --- a/crates/loro-internal/src/state/richtext_state.rs +++ b/crates/loro-internal/src/state/richtext_state.rs @@ -23,9 +23,7 @@ use crate::{ event::{Diff, Index, InternalDiff}, op::{Op, RawOp}, txn::Transaction, - utils::{ - delta_rle_encoded_num::DeltaRleEncodedNums, lazy::LazyLoad, string_slice::StringSlice, - }, + utils::{lazy::LazyLoad, string_slice::StringSlice}, DocState, }; @@ -431,7 +429,7 @@ impl ContainerState for RichtextState { self.state.get_mut().insert_at_entity_index( *pos as usize, slice.clone(), - r_op.id, + r_op.id_full(), ); } list_op::InnerListOp::Delete(del) => { @@ -527,33 +525,22 @@ impl ContainerState for RichtextState { } } - let mut lamports = DeltaRleEncodedNums::new(); for chunk in iter { - match chunk { - RichtextStateChunk::Style { style, anchor_type } - if *anchor_type == AnchorType::Start => - { - lamports.push(style.lamport); - } - _ => {} - } - - let id_span = chunk.get_id_span(); + debug_log::debug_dbg!(&chunk); + let id_span = chunk.get_id_lp_span(); encoder.encode_op(id_span, || unimplemented!()); } - lamports.encode() + Default::default() } #[doc = " Restore the state to the state represented by the ops that exported by `get_snapshot_ops`"] fn import_from_snapshot_ops(&mut self, ctx: StateSnapshotDecodeContext) { assert_eq!(ctx.mode, EncodeMode::Snapshot); - let lamports = DeltaRleEncodedNums::decode(ctx.blob); - let mut lamport_iter = lamports.iter(); let mut loader = RichtextStateLoader::default(); let mut id_to_style = FxHashMap::default(); for op in ctx.ops { - let id = op.id(); + let id = op.id_full(); let chunk = match op.op.content.into_list().unwrap() { list_op::InnerListOp::InsertText { slice, .. } => { RichtextStateChunk::new_text(slice.clone(), id) @@ -562,7 +549,7 @@ impl ContainerState for RichtextState { key, value, info, .. } => { let style_op = Arc::new(StyleOp { - lamport: lamport_iter.next().unwrap(), + lamport: op.lamport.expect("op should already be imported"), peer: op.peer, cnt: op.op.counter, key, diff --git a/crates/loro-internal/src/state/tree_state.rs b/crates/loro-internal/src/state/tree_state.rs index e9ffcaa6..68a073ac 100644 --- a/crates/loro-internal/src/state/tree_state.rs +++ b/crates/loro-internal/src/state/tree_state.rs @@ -1,7 +1,7 @@ use enum_as_inner::EnumAsInner; use fxhash::FxHashMap; use itertools::Itertools; -use loro_common::{ContainerID, LoroError, LoroResult, LoroTreeError, LoroValue, TreeID, ID}; +use loro_common::{ContainerID, IdFull, LoroError, LoroResult, LoroTreeError, LoroValue, TreeID}; use rle::HasLength; use serde::{Deserialize, Serialize}; use std::collections::VecDeque; @@ -68,7 +68,7 @@ pub struct TreeState { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct TreeStateNode { pub parent: TreeParentId, - pub last_move_op: ID, + pub last_move_op: IdFull, } impl TreeState { @@ -79,7 +79,12 @@ impl TreeState { } } - pub fn mov(&mut self, target: TreeID, parent: TreeParentId, id: ID) -> Result<(), LoroError> { + pub fn mov( + &mut self, + target: TreeID, + parent: TreeParentId, + id: IdFull, + ) -> Result<(), LoroError> { if parent.is_none() { // new root node self.trees.insert( @@ -269,7 +274,7 @@ impl ContainerState for TreeState { } None => TreeParentId::None, }; - self.mov(target, parent, raw_op.id) + self.mov(target, parent, raw_op.id_full()) } _ => unreachable!(), } @@ -349,10 +354,10 @@ impl ContainerState for TreeState { #[doc = " Get a list of ops that can be used to restore the state to the current state"] fn encode_snapshot(&self, mut encoder: StateSnapshotEncoder) -> Vec { for node in self.trees.values() { - if node.last_move_op == ID::NONE_ID { + if node.last_move_op == IdFull::NONE_ID { continue; } - encoder.encode_op(node.last_move_op.into(), || unimplemented!()); + encoder.encode_op(node.last_move_op.idlp().into(), || unimplemented!()); } Vec::new() @@ -381,7 +386,7 @@ impl ContainerState for TreeState { target, TreeStateNode { parent, - last_move_op: op.id(), + last_move_op: op.id_full(), }, ); } @@ -511,9 +516,9 @@ mod tests { 0, loro_common::ContainerType::Tree, )); - state.mov(ID1, TreeParentId::None, ID::NONE_ID).unwrap(); + state.mov(ID1, TreeParentId::None, IdFull::NONE_ID).unwrap(); state - .mov(ID2, TreeParentId::Node(ID1), ID::NONE_ID) + .mov(ID2, TreeParentId::Node(ID1), IdFull::NONE_ID) .unwrap(); } @@ -523,9 +528,9 @@ mod tests { 0, loro_common::ContainerType::Tree, )); - state.mov(ID1, TreeParentId::None, ID::NONE_ID).unwrap(); + state.mov(ID1, TreeParentId::None, IdFull::NONE_ID).unwrap(); state - .mov(ID2, TreeParentId::Node(ID1), ID::NONE_ID) + .mov(ID2, TreeParentId::Node(ID1), IdFull::NONE_ID) .unwrap(); let roots = Forest::from_tree_state(&state.trees); let json = serde_json::to_string(&roots).unwrap(); @@ -541,17 +546,19 @@ mod tests { 0, loro_common::ContainerType::Tree, )); - state.mov(ID1, TreeParentId::None, ID::NONE_ID).unwrap(); + state.mov(ID1, TreeParentId::None, IdFull::NONE_ID).unwrap(); state - .mov(ID2, TreeParentId::Node(ID1), ID::NONE_ID) + .mov(ID2, TreeParentId::Node(ID1), IdFull::NONE_ID) .unwrap(); state - .mov(ID3, TreeParentId::Node(ID2), ID::NONE_ID) + .mov(ID3, TreeParentId::Node(ID2), IdFull::NONE_ID) .unwrap(); state - .mov(ID4, TreeParentId::Node(ID1), ID::NONE_ID) + .mov(ID4, TreeParentId::Node(ID1), IdFull::NONE_ID) + .unwrap(); + state + .mov(ID2, TreeParentId::Deleted, IdFull::NONE_ID) .unwrap(); - state.mov(ID2, TreeParentId::Deleted, ID::NONE_ID).unwrap(); let roots = Forest::from_tree_state(&state.trees); let json = serde_json::to_string(&roots).unwrap(); assert_eq!( diff --git a/crates/loro-internal/src/txn.rs b/crates/loro-internal/src/txn.rs index c67d50ab..6fe3c725 100644 --- a/crates/loro-internal/src/txn.rs +++ b/crates/loro-internal/src/txn.rs @@ -7,7 +7,7 @@ use std::{ use enum_as_inner::EnumAsInner; use generic_btree::rle::{HasLength as RleHasLength, Mergeable as GBSliceable}; -use loro_common::{ContainerType, LoroResult}; +use loro_common::{ContainerType, IdLp, LoroResult}; use rle::{HasLength, Mergable, RleVec}; use smallvec::{smallvec, SmallVec}; @@ -573,10 +573,9 @@ fn change_to_diff( diff: Diff::Map(ResolvedMapDelta::new().with_entry( key, ResolvedMapValue { - counter: op.counter, value: value.map(|v| ValueOrContainer::from_value(v, arena, txn, state)), - lamport: (lamport, peer), + idlp: IdLp::new(peer, lamport), }, )), }), diff --git a/crates/loro-internal/src/utils/delta_rle_encoded_num.rs b/crates/loro-internal/src/utils/delta_rle_encoded_num.rs deleted file mode 100644 index 800a69ac..00000000 --- a/crates/loro-internal/src/utils/delta_rle_encoded_num.rs +++ /dev/null @@ -1,37 +0,0 @@ -use serde_columnar::columnar; - -#[columnar(vec, ser, de)] -#[derive(Debug, Clone)] -struct EncodedNum { - #[columnar(strategy = "DeltaRle")] - num: u32, -} - -#[derive(Default)] -#[columnar(ser, de)] -pub struct DeltaRleEncodedNums { - #[columnar(class = "vec")] - nums: Vec, -} - -impl DeltaRleEncodedNums { - pub fn new() -> Self { - Self::default() - } - - pub fn push(&mut self, n: u32) { - self.nums.push(EncodedNum { num: n }); - } - - pub fn iter(&self) -> impl Iterator + '_ { - self.nums.iter().map(|n| n.num) - } - - pub fn encode(&self) -> Vec { - serde_columnar::to_vec(&self).unwrap() - } - - pub fn decode(encoded: &[u8]) -> Self { - serde_columnar::from_bytes(encoded).unwrap() - } -} diff --git a/crates/loro-internal/src/utils/mod.rs b/crates/loro-internal/src/utils/mod.rs index 9c14288f..aaa75c07 100644 --- a/crates/loro-internal/src/utils/mod.rs +++ b/crates/loro-internal/src/utils/mod.rs @@ -1,4 +1,3 @@ -pub(crate) mod delta_rle_encoded_num; pub(crate) mod lazy; pub mod string_slice; pub(crate) mod utf16;