diff --git a/Cargo.lock b/Cargo.lock index 92ef56a2..593ea02f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -778,6 +778,7 @@ dependencies = [ "fxhash", "js-sys", "loro-rle", + "nonmax", "serde", "serde_columnar", "string_cache", @@ -939,6 +940,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + [[package]] name = "num" version = "0.4.1" diff --git a/crates/loro-common/Cargo.toml b/crates/loro-common/Cargo.toml index e5e06785..6940e672 100644 --- a/crates/loro-common/Cargo.toml +++ b/crates/loro-common/Cargo.toml @@ -23,6 +23,7 @@ string_cache = "0.8" arbitrary = { version = "1.3.0", features = ["derive"] } js-sys = { version = "0.3.60", optional = true } serde_columnar = "0.3.3" +nonmax = "0.5.5" [features] wasm = ["wasm-bindgen", "js-sys"] diff --git a/crates/loro-common/src/lib.rs b/crates/loro-common/src/lib.rs index ddd40867..f81d90e3 100644 --- a/crates/loro-common/src/lib.rs +++ b/crates/loro-common/src/lib.rs @@ -3,6 +3,7 @@ use std::{fmt::Display, sync::Arc}; use arbitrary::Arbitrary; use enum_as_inner::EnumAsInner; +use nonmax::NonMaxI32; use serde::{Deserialize, Serialize}; mod error; mod id; @@ -32,6 +33,41 @@ pub struct ID { pub counter: Counter, } +/// It's the unique ID of an Op represented by [PeerID] and [Counter]. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct CompactId { + pub peer: PeerID, + pub counter: NonMaxI32, +} + +impl CompactId { + pub fn new(peer: PeerID, counter: Counter) -> Self { + Self { + peer, + counter: NonMaxI32::new(counter).unwrap(), + } + } + + pub fn to_id(&self) -> ID { + ID { + peer: self.peer, + counter: self.counter.get(), + } + } +} + +impl TryFrom for CompactId { + type Error = ID; + + fn try_from(id: ID) -> Result { + if id.counter == i32::MAX { + return Err(id); + } + + Ok(Self::new(id.peer, id.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)] diff --git a/crates/loro-internal/src/container/richtext/fugue_span.rs b/crates/loro-internal/src/container/richtext/fugue_span.rs index 09815ba9..18b960fb 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, IdFull, IdSpan, Lamport, ID}; +use loro_common::{CompactId, Counter, HasId, IdFull, IdSpan, Lamport, ID}; use serde::{Deserialize, Serialize}; use super::AnchorType; @@ -176,8 +176,8 @@ pub(super) struct FugueSpan { /// The status at the `new` version. /// It's used when calculating diff. pub diff_status: Option, - pub origin_left: Option, - pub origin_right: Option, + pub origin_left: Option, + pub origin_right: Option, pub content: RichtextChunk, } @@ -223,7 +223,13 @@ impl Sliceable for FugueSpan { origin_left: if range.start == 0 { self.origin_left } else { - Some(self.id.inc((range.start - 1) as Counter).id()) + Some( + self.id + .inc((range.start - 1) as Counter) + .id() + .try_into() + .unwrap(), + ) }, origin_right: self.origin_right, content: self.content._slice(range), @@ -247,7 +253,7 @@ impl Mergeable for FugueSpan { && 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 + && rhs.origin_left.unwrap().counter.get() == self.id.counter + self.content.len() as Counter - 1 && self.origin_right == rhs.origin_right && self.content.can_merge(&rhs.content) 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 22770015..b118ce2e 100644 --- a/crates/loro-internal/src/container/richtext/tracker/crdt_rope.rs +++ b/crates/loro-internal/src/container/richtext/tracker/crdt_rope.rs @@ -109,7 +109,7 @@ impl CrdtRope { } _ => { // Otherwise, we need to test whether origin_right's origin_left == this.origin_left - if iter.elem.origin_left == origin_left { + if iter.elem.origin_left.map(|x| x.to_id()) == origin_left { Some(origin_right) } else { None @@ -127,8 +127,8 @@ impl CrdtRope { (origin_right, parent_right_idx, in_between) }; - content.origin_left = origin_left; - content.origin_right = origin_right; + content.origin_left = origin_left.map(|x| x.try_into().unwrap()); + content.origin_right = origin_right.map(|x| x.try_into().unwrap()); (parent_right_leaf, in_between) }; @@ -144,7 +144,7 @@ impl CrdtRope { let other_origin_left = other_elem.origin_left; if other_origin_left != content.origin_left && other_origin_left - .map(|left| visited.iter().all(|x| !x.contains_id(left))) + .map(|left| visited.iter().all(|x| !x.contains_id(left.to_id()))) .unwrap_or(true) { // The other_elem's origin_left must be at the left side of content's origin_left. @@ -176,10 +176,10 @@ impl CrdtRope { let other_parent_right_idx = if let Some(other_origin_right) = other_elem.origin_right { - let elem_idx = find_elem(other_origin_right); + let elem_idx = find_elem(other_origin_right.to_id()); let elem = self.tree.get_elem(elem_idx).unwrap(); // It must be the start of the elem - assert_eq!(elem.id.id(), other_origin_right); + assert_eq!(elem.id.id(), other_origin_right.to_id()); if elem.origin_left == content.origin_left { Some(elem_idx) } else { @@ -621,7 +621,7 @@ impl LeafUpdate { mod test { use std::ops::Range; - use loro_common::{Counter, PeerID, ID}; + use loro_common::{CompactId, Counter, PeerID, ID}; use crate::container::richtext::RichtextChunk; @@ -726,8 +726,8 @@ mod test { let mut rope = CrdtRope::new(); rope.insert(0, span(0, 0..10), |_| panic!()); let fugue = rope.insert(5, span(1, 10..20), |_| panic!()).content; - assert_eq!(fugue.origin_left, Some(ID::new(0, 4))); - assert_eq!(fugue.origin_right, Some(ID::new(0, 5))); + assert_eq!(fugue.origin_left, Some(CompactId::new(0, 4))); + assert_eq!(fugue.origin_right, Some(CompactId::new(0, 5))); } #[test] @@ -738,11 +738,11 @@ mod test { rope.delete(5, 2, |_| {}); assert_eq!(rope.len(), 8); let fugue = rope.insert(6, span(1, 10..20), |_| panic!()).content; - assert_eq!(fugue.origin_left, Some(ID::new(0, 7))); - assert_eq!(fugue.origin_right, Some(ID::new(0, 8))); + assert_eq!(fugue.origin_left, Some(CompactId::new(0, 7))); + assert_eq!(fugue.origin_right, Some(CompactId::new(0, 8))); let fugue = rope.insert(5, span(1, 10..11), |_| panic!()).content; - assert_eq!(fugue.origin_left, Some(ID::new(0, 4))); - assert_eq!(fugue.origin_right, Some(ID::new(0, 5))); + assert_eq!(fugue.origin_left, Some(CompactId::new(0, 4))); + assert_eq!(fugue.origin_right, Some(CompactId::new(0, 5))); } #[test] @@ -753,8 +753,8 @@ mod test { rope.insert(0, span(0, 0..10), |_| panic!()); rope.insert(5, future_span(1, 10..20), |_| panic!()); let fugue = rope.insert(5, span(1, 10..20), |_| panic!()).content; - assert_eq!(fugue.origin_left, Some(ID::new(0, 4))); - assert_eq!(fugue.origin_right, Some(ID::new(0, 5))); + assert_eq!(fugue.origin_left, Some(CompactId::new(0, 4))); + assert_eq!(fugue.origin_right, Some(CompactId::new(0, 5))); } { // insert deleted @@ -762,8 +762,8 @@ mod test { rope.insert(0, span(0, 0..10), |_| panic!()); rope.insert(5, dead_span(1, 10..20), |_| panic!()); let fugue = rope.insert(5, span(1, 10..20), |_| panic!()).content; - assert_eq!(fugue.origin_left, Some(ID::new(0, 4))); - assert_eq!(fugue.origin_right, Some(ID::new(1, 0))); + assert_eq!(fugue.origin_left, Some(CompactId::new(0, 4))); + assert_eq!(fugue.origin_right, Some(CompactId::new(1, 0))); } }