mirror of
https://github.com/loro-dev/loro.git
synced 2025-02-05 20:17:13 +00:00
Refactor ID (#274)
* refactor: add idlp and add lamport info to snapshot enc * fix: fix warnings * fix: idlp err due to incorrect merge * fix: comments * test: fix fuzz
This commit is contained in:
parent
1f4a59e85c
commit
b8eb57f4a5
27 changed files with 640 additions and 299 deletions
56
crates/examples/fuzz/Cargo.lock
generated
56
crates/examples/fuzz/Cargo.lock
generated
|
@ -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]]
|
||||
|
|
|
@ -3,6 +3,4 @@
|
|||
use bench_utils::{draw::DrawAction, Action};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|actions: Vec<Action<DrawAction>>| {
|
||||
examples::draw::run_actions_fuzz_in_async_mode(5, 100, &actions)
|
||||
});
|
||||
fuzz_target!(|actions: Vec<Action<DrawAction>>| examples::draw::fuzz(5, 100, &actions));
|
||||
|
|
|
@ -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<ID> 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<IdLp> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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].
|
||||
///
|
||||
|
|
|
@ -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)]
|
||||
|
|
8
crates/loro-internal/fuzz/Cargo.lock
generated
8
crates/loro-internal/fuzz/Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Self, Utf8Error> {
|
||||
pub fn try_new(s: BytesSlice, id: IdFull) -> Result<Self, Utf8Error> {
|
||||
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));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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<FugueSpan> = 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<FugueSpan> = t.rope.tree().iter().copied().collect();
|
||||
|
|
|
@ -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<u32>) -> 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<u32>) -> 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<u32>) -> 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,
|
||||
);
|
||||
|
|
|
@ -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<LoroValue>,
|
||||
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<ValueOrContainer>,
|
||||
pub lamport: (Lamport, PeerID),
|
||||
pub idlp: IdLp,
|
||||
}
|
||||
|
||||
impl ResolvedMapValue {
|
||||
|
@ -77,8 +71,7 @@ impl ResolvedMapValue {
|
|||
state: &Weak<Mutex<DocState>>,
|
||||
) -> 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<H: std::hash::Hasher>(&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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<LoroValue>,
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
|
|
@ -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<std::cmp::Ordering> {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Item = OpWithId>,
|
||||
#[allow(unused)]
|
||||
pub blob: &'a [u8],
|
||||
pub mode: EncodeMode,
|
||||
}
|
||||
|
|
|
@ -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<TempOp<'a>> {
|
||||
origin_ops.sort_unstable();
|
||||
pos_mapping_heap.sort_unstable();
|
||||
debug_log::debug_dbg!(&origin_ops, &pos_mapping_heap);
|
||||
let mut ops: Vec<TempOp<'a>> = 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<usize>) -> 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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<Lamport>,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -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<Lamport> {
|
||||
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<ID> {
|
||||
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)]
|
||||
|
|
|
@ -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<LoroValue>, start_id: ID) {
|
||||
pub fn insert_batch(&mut self, index: usize, values: Vec<LoroValue>, 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<u8> {
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -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<u8> {
|
||||
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,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<u8> {
|
||||
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!(
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
)),
|
||||
}),
|
||||
|
|
|
@ -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<EncodedNum>,
|
||||
}
|
||||
|
||||
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<Item = u32> + '_ {
|
||||
self.nums.iter().map(|n| n.num)
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
serde_columnar::to_vec(&self).unwrap()
|
||||
}
|
||||
|
||||
pub fn decode(encoded: &[u8]) -> Self {
|
||||
serde_columnar::from_bytes(encoded).unwrap()
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
pub(crate) mod delta_rle_encoded_num;
|
||||
pub(crate) mod lazy;
|
||||
pub mod string_slice;
|
||||
pub(crate) mod utf16;
|
||||
|
|
Loading…
Reference in a new issue