From cb134fc7c7b093157c63947798e83889bfd392e3 Mon Sep 17 00:00:00 2001 From: Leon Zhao Date: Thu, 5 Sep 2024 16:03:47 +0800 Subject: [PATCH 1/3] feat: add `with fractional index` config (#442) * feat: add `with_fractional_index` config * fix: share config state --- Cargo.lock | 1 + crates/fractional_index/Cargo.toml | 1 + crates/fractional_index/src/lib.rs | 4 +- crates/fuzz/src/actor.rs | 3 + crates/fuzz/src/container/map.rs | 8 ++- crates/fuzz/src/container/tree.rs | 8 ++- crates/loro-internal/src/configure.rs | 13 ++++ crates/loro-internal/src/handler/tree.rs | 1 - crates/loro-internal/src/loro.rs | 6 ++ crates/loro-internal/src/state.rs | 17 ++++- crates/loro-internal/src/state/tree_state.rs | 76 +++++++++++--------- crates/loro/src/lib.rs | 6 ++ 12 files changed, 102 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88d9366e..a6e55158 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1349,6 +1349,7 @@ dependencies = [ "criterion 0.5.1", "fractional_index", "imbl", + "once_cell", "rand", "serde", "smallvec", diff --git a/crates/fractional_index/Cargo.toml b/crates/fractional_index/Cargo.toml index b7533e39..c0477fcd 100644 --- a/crates/fractional_index/Cargo.toml +++ b/crates/fractional_index/Cargo.toml @@ -16,6 +16,7 @@ imbl = "^3.0" smallvec = { workspace = true } serde = { workspace = true, features = ["derive", "rc"], optional = true } rand = { version = "^0.8" } +once_cell = { workspace = true } [dev-dependencies] fraction_index = { version = "^2.0", package = "fractional_index" } diff --git a/crates/fractional_index/src/lib.rs b/crates/fractional_index/src/lib.rs index d7b3835a..a76e2c3c 100644 --- a/crates/fractional_index/src/lib.rs +++ b/crates/fractional_index/src/lib.rs @@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize}; mod jitter; const TERMINATOR: u8 = 128; +static DEFAULT_FRACTIONAL_INDEX: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| FractionalIndex(Arc::new(vec![TERMINATOR]))); #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -16,7 +18,7 @@ pub struct FractionalIndex(Arc>); impl Default for FractionalIndex { fn default() -> Self { - FractionalIndex(Arc::new(vec![TERMINATOR])) + DEFAULT_FRACTIONAL_INDEX.clone() } } diff --git a/crates/fuzz/src/actor.rs b/crates/fuzz/src/actor.rs index bc855cb9..b9c32759 100644 --- a/crates/fuzz/src/actor.rs +++ b/crates/fuzz/src/actor.rs @@ -24,6 +24,8 @@ use super::{ container::MapActor, }; +const DEFAULT_WITH_FRACTIONAL_INDEX: bool = false; + #[derive(Debug)] pub struct Undo { pub undo: UndoManager, @@ -45,6 +47,7 @@ impl Actor { pub fn new(id: PeerID) -> Self { let loro = LoroDoc::new(); loro.set_peer_id(id).unwrap(); + loro.set_with_fractional_index(DEFAULT_WITH_FRACTIONAL_INDEX); let undo = UndoManager::new(&loro); let tracker = Arc::new(Mutex::new(ContainerTracker::Map(MapTracker::empty( ContainerID::new_root("sys:root", ContainerType::Map), diff --git a/crates/fuzz/src/container/map.rs b/crates/fuzz/src/container/map.rs index c68a6553..8be43e85 100644 --- a/crates/fuzz/src/container/map.rs +++ b/crates/fuzz/src/container/map.rs @@ -7,7 +7,7 @@ use loro::{Container, ContainerID, ContainerType, LoroDoc, LoroMap, LoroValue}; use crate::{ actions::{Actionable, FromGenericAction, GenericAction}, - actor::{ActionExecutor, ActorTrait}, + actor::{assert_value_eq, ActionExecutor, ActorTrait}, crdt_fuzzer::FuzzValue, value::{ApplyDiff, ContainerTracker, MapTracker, Value}, }; @@ -66,7 +66,11 @@ impl ActorTrait for MapActor { let map = self.loro.get_map("map"); let value_a = map.get_deep_value(); let value_b = self.tracker.lock().unwrap().to_value(); - assert_eq!(&value_a, value_b.into_map().unwrap().get("map").unwrap()); + assert_value_eq( + &value_a, + value_b.into_map().unwrap().get("map").unwrap(), + None, + ); } fn container_len(&self) -> u8 { diff --git a/crates/fuzz/src/container/tree.rs b/crates/fuzz/src/container/tree.rs index b6c50dd2..5d9b39c5 100644 --- a/crates/fuzz/src/container/tree.rs +++ b/crates/fuzz/src/container/tree.rs @@ -14,7 +14,7 @@ use tracing::{debug, trace}; use crate::{ actions::{Actionable, FromGenericAction, GenericAction}, - actor::{ActionExecutor, ActorTrait}, + actor::{assert_value_eq, ActionExecutor, ActorTrait}, crdt_fuzzer::FuzzValue, value::{ApplyDiff, ContainerTracker, MapTracker, Value}, }; @@ -135,7 +135,11 @@ impl ActorTrait for TreeActor { let tree = loro.get_tree("tree"); let result = tree.get_value_with_meta(); let tracker = self.tracker.lock().unwrap().to_value(); - assert_eq!(&result, tracker.into_map().unwrap().get("tree").unwrap()); + assert_value_eq( + &result, + tracker.into_map().unwrap().get("tree").unwrap(), + None, + ); } fn add_new_container(&mut self, container: Container) { diff --git a/crates/loro-internal/src/configure.rs b/crates/loro-internal/src/configure.rs index c3ed81f3..69364823 100644 --- a/crates/loro-internal/src/configure.rs +++ b/crates/loro-internal/src/configure.rs @@ -5,6 +5,9 @@ pub struct Configure { pub(crate) text_style_config: Arc>, record_timestamp: Arc, pub(crate) merge_interval: Arc, + /// Whether the tree has fractional index. `false` by default. If false, the fractional index is always [`FractionalIndex::default`] and + /// `tree_position_jitter` is not used. + pub(crate) tree_with_fractional_index: Arc, /// do not use `jitter` by default pub(crate) tree_position_jitter: Arc, } @@ -16,6 +19,7 @@ impl Default for Configure { record_timestamp: Arc::new(AtomicBool::new(false)), merge_interval: Arc::new(AtomicI64::new(1000 * 1000)), tree_position_jitter: Arc::new(AtomicU8::new(0)), + tree_with_fractional_index: Arc::new(AtomicBool::new(false)), } } } @@ -38,6 +42,10 @@ impl Configure { self.tree_position_jitter .load(std::sync::atomic::Ordering::Relaxed), )), + tree_with_fractional_index: Arc::new(AtomicBool::new( + self.tree_with_fractional_index + .load(std::sync::atomic::Ordering::Relaxed), + )), } } @@ -55,6 +63,11 @@ impl Configure { .store(record, std::sync::atomic::Ordering::Relaxed); } + pub fn set_with_fractional_index(&self, with_fractional_index: bool) { + self.tree_with_fractional_index + .store(with_fractional_index, std::sync::atomic::Ordering::Relaxed); + } + pub fn set_fractional_index_jitter(&self, jitter: u8) { self.tree_position_jitter .store(jitter, std::sync::atomic::Ordering::Relaxed); diff --git a/crates/loro-internal/src/handler/tree.rs b/crates/loro-internal/src/handler/tree.rs index b7c6c423..4d772c8f 100644 --- a/crates/loro-internal/src/handler/tree.rs +++ b/crates/loro-internal/src/handler/tree.rs @@ -210,7 +210,6 @@ impl HandlerTrait for TreeHandler { self.inner.attached_handler() } - // TODO: fn get_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(t) => { diff --git a/crates/loro-internal/src/loro.rs b/crates/loro-internal/src/loro.rs index 59d47264..cde05faf 100644 --- a/crates/loro-internal/src/loro.rs +++ b/crates/loro-internal/src/loro.rs @@ -173,6 +173,12 @@ impl LoroDoc { self.config.set_merge_interval(interval); } + /// Set whether to use fractional index for Tree Position. + #[inline] + pub fn set_with_fractional_index(&self, with_fractional_index: bool) { + self.config.set_with_fractional_index(with_fractional_index); + } + /// Set the jitter of the tree position(Fractional Index). /// /// The jitter is used to avoid conflicts when multiple users are creating the node at the same position. diff --git a/crates/loro-internal/src/state.rs b/crates/loro-internal/src/state.rs index 3a562158..2b252f71 100644 --- a/crates/loro-internal/src/state.rs +++ b/crates/loro-internal/src/state.rs @@ -3,7 +3,7 @@ use std::{ collections::BTreeMap, io::Write, sync::{ - atomic::{AtomicU64, AtomicU8, Ordering}, + atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering}, Arc, Mutex, RwLock, Weak, }, }; @@ -310,8 +310,18 @@ impl State { Self::RichtextState(Box::new(RichtextState::new(idx, config))) } - pub fn new_tree(idx: ContainerIdx, peer: PeerID, jitter: Arc) -> Self { - Self::TreeState(Box::new(TreeState::new(idx, peer, jitter))) + pub fn new_tree( + idx: ContainerIdx, + peer: PeerID, + jitter: Arc, + with_fractional_index: Arc, + ) -> Self { + Self::TreeState(Box::new(TreeState::new( + idx, + peer, + jitter, + with_fractional_index, + ))) } pub fn new_unknown(idx: ContainerIdx) -> Self { @@ -1476,6 +1486,7 @@ fn create_state_(idx: ContainerIdx, config: &Configure, peer: u64) -> State { idx, peer, config.tree_position_jitter.clone(), + config.tree_with_fractional_index.clone(), ))), ContainerType::MovableList => State::MovableListState(Box::new(MovableListState::new(idx))), #[cfg(feature = "counter")] diff --git a/crates/loro-internal/src/state/tree_state.rs b/crates/loro-internal/src/state/tree_state.rs index acdc88f0..45091428 100644 --- a/crates/loro-internal/src/state/tree_state.rs +++ b/crates/loro-internal/src/state/tree_state.rs @@ -13,7 +13,7 @@ use serde::Serialize; use std::collections::VecDeque; use std::fmt::Debug; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::{Arc, Mutex, Weak}; use super::{ContainerState, DiffApplyContext}; @@ -41,8 +41,10 @@ pub struct TreeState { idx: ContainerIdx, trees: FxHashMap, children: TreeChildrenCache, + /// Whether the tree has fractional index. If false, the fractional index is always [`FractionalIndex::default`] + with_fractional_index: Arc, rng: Option, - jitter: u8, + jitter: Arc, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -597,16 +599,22 @@ impl NodePosition { } impl TreeState { - pub fn new(idx: ContainerIdx, peer_id: PeerID, config: Arc) -> Self { - let jitter = config.load(Ordering::Relaxed); + pub fn new( + idx: ContainerIdx, + peer_id: PeerID, + jitter_config: Arc, + with_fractional_index: Arc, + ) -> Self { + let jitter = jitter_config.load(Ordering::Relaxed); let use_jitter = jitter != 1; Self { idx, trees: FxHashMap::default(), children: Default::default(), + with_fractional_index, rng: use_jitter.then_some(rand::rngs::StdRng::seed_from_u64(peer_id)), - jitter, + jitter: jitter_config, } } @@ -677,10 +685,6 @@ impl TreeState { !self.is_node_deleted(&target) } - pub fn contains_internal(&self, target: &TreeID) -> bool { - self.trees.contains_key(target) - } - /// Get the parent of the node, if the node is deleted or does not exist, return None pub fn parent(&self, target: &TreeID) -> TreeParentId { self.trees @@ -823,11 +827,15 @@ impl TreeState { parent: &TreeParentId, index: usize, ) -> FractionalIndexGenResult { + if !self.with_fractional_index.load(Ordering::Relaxed) { + return FractionalIndexGenResult::Ok(FractionalIndex::default()); + } + if let Some(rng) = self.rng.as_mut() { self.children .entry(*parent) .or_default() - .generate_fi_at_jitter(index, target, rng, self.jitter) + .generate_fi_at_jitter(index, target, rng, self.jitter.load(Ordering::Relaxed)) } else { self.children .entry(*parent) @@ -936,28 +944,26 @@ impl ContainerState for TreeState { }); } // Otherwise, it's a normal move inside deleted nodes, no event is needed + } else if was_alive { + // normal move + ans.push(TreeDiffItem { + target, + action: TreeExternalDiff::Move { + parent: parent.into_node().ok(), + index: self.get_index_by_tree_id(&target).unwrap(), + position: position.clone(), + }, + }); } else { - if was_alive { - // normal move - ans.push(TreeDiffItem { - target, - action: TreeExternalDiff::Move { - parent: parent.into_node().ok(), - index: self.get_index_by_tree_id(&target).unwrap(), - position: position.clone(), - }, - }); - } else { - // create event - ans.push(TreeDiffItem { - target, - action: TreeExternalDiff::Create { - parent: parent.into_node().ok(), - index: self.get_index_by_tree_id(&target).unwrap(), - position: position.clone(), - }, - }); - } + // create event + ans.push(TreeDiffItem { + target, + action: TreeExternalDiff::Create { + parent: parent.into_node().ok(), + index: self.get_index_by_tree_id(&target).unwrap(), + position: position.clone(), + }, + }); } } } else { @@ -1490,8 +1496,12 @@ mod snapshot { peers.push(PeerID::from_le_bytes(buf)); } - let mut tree = - TreeState::new(idx, ctx.peer, ctx.configure.tree_position_jitter.clone()); + let mut tree = TreeState::new( + idx, + ctx.peer, + ctx.configure.tree_position_jitter.clone(), + ctx.configure.tree_with_fractional_index.clone(), + ); let encoded: EncodedTree = serde_columnar::from_bytes(bytes)?; let fractional_indexes = PositionArena::decode(&encoded.fractional_indexes).unwrap(); let fractional_indexes = fractional_indexes.parse_to_positions(); diff --git a/crates/loro/src/lib.rs b/crates/loro/src/lib.rs index 0b3282cb..f9fe2758 100644 --- a/crates/loro/src/lib.rs +++ b/crates/loro/src/lib.rs @@ -155,6 +155,12 @@ impl LoroDoc { self.doc.set_change_merge_interval(interval); } + /// Set whether to use fractional index for Tree Position. + #[inline] + pub fn set_with_fractional_index(&self, with_fractional_index: bool) { + self.doc.set_with_fractional_index(with_fractional_index); + } + /// Set the jitter of the tree position(Fractional Index). /// /// The jitter is used to avoid conflicts when multiple users are creating the node at the same position. From dd6bb10fff2009fd563f37dce4ffbe080ef32c4a Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Thu, 5 Sep 2024 20:27:31 +0800 Subject: [PATCH 2/3] Refactor(wasm) rename Loro to LoroDoc (#443) * chore: reduce wasm size * refactor(wasm): rename Loro to LoroDoc --- crates/loro-internal/src/loro.rs | 1 - crates/loro-wasm/src/lib.rs | 393 +++++++++++++++-------------- loro-js/src/index.ts | 99 ++++---- loro-js/tests/basic.test.ts | 72 +++--- loro-js/tests/checkout.test.ts | 10 +- loro-js/tests/counter.test.ts | 14 +- loro-js/tests/event.test.ts | 34 +-- loro-js/tests/issue.test.ts | 8 +- loro-js/tests/json.test.ts | 10 +- loro-js/tests/misc.test.ts | 32 +-- loro-js/tests/movable_list.test.ts | 32 +-- loro-js/tests/richtext.test.ts | 46 ++-- loro-js/tests/tree.test.ts | 10 +- loro-js/tests/type.test.ts | 6 +- loro-js/tests/undo.test.ts | 16 +- loro-js/tests/version.test.ts | 20 +- 16 files changed, 405 insertions(+), 398 deletions(-) diff --git a/crates/loro-internal/src/loro.rs b/crates/loro-internal/src/loro.rs index cde05faf..bb62350c 100644 --- a/crates/loro-internal/src/loro.rs +++ b/crates/loro-internal/src/loro.rs @@ -6,7 +6,6 @@ use rle::HasLength; use std::{ borrow::Cow, cmp::Ordering, - f32::consts::E, sync::{ atomic::{ AtomicBool, diff --git a/crates/loro-wasm/src/lib.rs b/crates/loro-wasm/src/lib.rs index 3238cd56..8f7d9f16 100644 --- a/crates/loro-wasm/src/lib.rs +++ b/crates/loro-wasm/src/lib.rs @@ -23,8 +23,8 @@ use loro_internal::{ obs::SubID, undo::{UndoItemMeta, UndoOrRedo}, version::Frontiers, - ContainerType, DiffEvent, FxHashMap, HandlerTrait, LoroDoc, LoroValue, MovableListHandler, - UndoManager as InnerUndoManager, VersionVector as InternalVersionVector, + ContainerType, DiffEvent, FxHashMap, HandlerTrait, LoroDoc as LoroDocInner, LoroValue, + MovableListHandler, UndoManager as InnerUndoManager, VersionVector as InternalVersionVector, }; use rle::HasLength; use serde::{Deserialize, Serialize}; @@ -62,9 +62,9 @@ type JsResult = Result; /// /// @example /// ```ts -/// import { Loro } import "loro-crdt" +/// import { LoroDoc } import "loro-crdt" /// -/// const loro = new Loro(); +/// const loro = new LoroDoc(); /// const text = loro.getText("text"); /// const list = loro.getList("list"); /// const map = loro.getMap("Map"); @@ -73,7 +73,7 @@ type JsResult = Result; /// // When FinalizationRegistry is unavailable, it's the users' responsibility to free the document. #[wasm_bindgen] -pub struct Loro(Arc); +pub struct LoroDoc(Arc); #[wasm_bindgen] extern "C" { @@ -85,7 +85,7 @@ extern "C" { pub type JsContainerID; #[wasm_bindgen(typescript_type = "ContainerID | string")] pub type JsIntoContainerID; - #[wasm_bindgen(typescript_type = "Transaction | Loro")] + #[wasm_bindgen(typescript_type = "Transaction | LoroDoc")] pub type JsTransaction; #[wasm_bindgen(typescript_type = "string | undefined")] pub type JsOrigin; @@ -306,13 +306,13 @@ impl ChangeMeta { } #[wasm_bindgen] -impl Loro { +impl LoroDoc { /// Create a new loro document. /// /// New document will have random peer id. #[wasm_bindgen(constructor)] pub fn new() -> Self { - let doc = LoroDoc::new(); + let doc = LoroDocInner::new(); doc.start_auto_commit(); Self(Arc::new(doc)) } @@ -367,7 +367,7 @@ impl Loro { /// /// @example /// ```ts - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// doc.configTextStyle({ /// bold: { expand: "after" }, /// link: { expand: "before" } @@ -421,15 +421,15 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } import "loro-crdt" + /// import { LoroDoc } import "loro-crdt" /// /// const bytes = /* The bytes encoded from other loro document *\/; - /// const loro = Loro.fromSnapshot(bytes); + /// const loro = LoroDoc.fromSnapshot(bytes); /// ``` /// #[wasm_bindgen(js_name = "fromSnapshot")] - pub fn from_snapshot(snapshot: &[u8]) -> JsResult { - let doc = LoroDoc::from_snapshot(snapshot)?; + pub fn from_snapshot(snapshot: &[u8]) -> JsResult { + let doc = LoroDocInner::from_snapshot(snapshot)?; doc.start_auto_commit(); Ok(Self(Arc::new(doc))) } @@ -445,9 +445,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// const frontiers = doc.frontiers(); /// text.insert(0, "Hello World!"); @@ -471,9 +471,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// const frontiers = doc.frontiers(); /// text.insert(0, "Hello World!"); @@ -496,9 +496,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// doc.detach(); /// console.log(doc.is_detached()); // true /// ``` @@ -524,9 +524,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// const frontiers = doc.frontiers(); /// text.insert(0, "Hello World!"); @@ -554,9 +554,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// const frontiers = doc.frontiers(); /// text.insert(0, "Hello World!"); @@ -622,9 +622,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// ``` #[wasm_bindgen(js_name = "getText")] @@ -646,9 +646,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// ``` #[wasm_bindgen(js_name = "getMap", skip_typescript)] @@ -669,9 +669,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// ``` #[wasm_bindgen(js_name = "getList", skip_typescript)] @@ -692,9 +692,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getMovableList("list"); /// ``` #[wasm_bindgen(skip_typescript)] @@ -727,9 +727,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// ``` #[wasm_bindgen(js_name = "getTree", skip_typescript)] @@ -748,9 +748,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// let text = doc.getText("text"); /// const textId = text.id; /// text = doc.getContainerById(textId); @@ -918,9 +918,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// // get all updates of the doc @@ -989,15 +989,15 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// // get all updates of the doc /// const updates = doc.exportFrom(); /// const snapshot = doc.exportSnapshot(); - /// const doc2 = new Loro(); + /// const doc2 = new LoroDoc(); /// // import snapshot /// doc2.import(snapshot); /// // or import updates @@ -1014,14 +1014,14 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// const updates = doc.exportFrom(); /// const snapshot = doc.exportSnapshot(); - /// const doc2 = new Loro(); + /// const doc2 = new LoroDoc(); /// doc2.importUpdateBatch([snapshot, updates]); /// ``` #[wasm_bindgen(js_name = "importUpdateBatch")] @@ -1043,9 +1043,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, "Hello"); /// const text = list.insertContainer(0, new LoroText()); @@ -1077,9 +1077,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// doc.subscribe((event)=>{ /// console.log(event); @@ -1104,9 +1104,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// const subscription = doc.subscribe((event)=>{ /// console.log(event); @@ -1132,9 +1132,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// const changes = doc.getAllChanges(); @@ -1264,9 +1264,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// const frontiers = doc.frontiers(); @@ -1288,9 +1288,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// const version = doc.version(); @@ -1306,9 +1306,9 @@ impl Loro { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("key", 1); /// console.log(doc.getByPath("map/key")); // 1 @@ -1329,7 +1329,7 @@ impl Loro { /// /// @example /// ```ts - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "123"); /// const pos0 = text.getCursor(0, 0); @@ -1370,7 +1370,7 @@ impl Loro { } #[allow(unused)] -fn call_subscriber(ob: observer::Observer, e: DiffEvent, doc: &Arc) { +fn call_subscriber(ob: observer::Observer, e: DiffEvent, doc: &Arc) { // We convert the event to js object here, so that we don't need to worry about GC. // In the future, when FinalizationRegistry[1] is stable, we can use `--weak-ref`[2] feature // in wasm-bindgen to avoid this. @@ -1384,7 +1384,7 @@ fn call_subscriber(ob: observer::Observer, e: DiffEvent, doc: &Arc) { } #[allow(unused)] -fn call_after_micro_task(ob: observer::Observer, event: DiffEvent, doc: &Arc) { +fn call_after_micro_task(ob: observer::Observer, event: DiffEvent, doc: &Arc) { let promise = Promise::resolve(&JsValue::NULL); type C = Closure; let drop_handler: Rc>> = Rc::new(RefCell::new(None)); @@ -1402,13 +1402,13 @@ fn call_after_micro_task(ob: observer::Observer, event: DiffEvent, doc: &Arc Self { Self::new() } } -fn diff_event_to_js_value(event: DiffEvent, doc: &Arc) -> JsValue { +fn diff_event_to_js_value(event: DiffEvent, doc: &Arc) -> JsValue { let obj = js_sys::Object::new(); Reflect::set(&obj, &"by".into(), &event.event_meta.by.to_string().into()).unwrap(); let origin: &str = &event.event_meta.origin; @@ -1441,7 +1441,10 @@ fn diff_event_to_js_value(event: DiffEvent, doc: &Arc) -> JsValue { /// path: Path; /// } /// -fn container_diff_to_js_value(event: &loro_internal::ContainerDiff, doc: &Arc) -> JsValue { +fn container_diff_to_js_value( + event: &loro_internal::ContainerDiff, + doc: &Arc, +) -> JsValue { let obj = js_sys::Object::new(); Reflect::set(&obj, &"target".into(), &event.id.to_string().into()).unwrap(); Reflect::set(&obj, &"diff".into(), &resolved_diff_to_js(&event.diff, doc)).unwrap(); @@ -1498,7 +1501,7 @@ fn convert_container_path_to_js_value(path: &[(ContainerID, Index)]) -> JsValue #[wasm_bindgen] pub struct LoroText { handler: TextHandler, - doc: Option>, + doc: Option>, delta_cache: Option<(usize, JsValue)>, } @@ -1535,9 +1538,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// text.iter((str) => (console.log(str), true)); @@ -1554,9 +1557,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// text.update("Hello World"); @@ -1569,9 +1572,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// ``` @@ -1584,9 +1587,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// text.slice(0, 2); // "He" @@ -1602,9 +1605,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// text.charAt(0); // "H" @@ -1621,9 +1624,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// text.splice(2, 3, "llo"); // "llo" @@ -1639,9 +1642,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insertUtf8(0, "Hello"); /// ``` @@ -1655,9 +1658,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "Hello"); /// text.delete(1, 3); @@ -1673,9 +1676,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insertUtf8(0, "Hello"); /// text.deleteUtf8(1, 3); @@ -1698,9 +1701,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// doc.configTextStyle({bold: {expand: "after"}}); /// const text = doc.getText("text"); /// text.insert(0, "Hello World!"); @@ -1721,9 +1724,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// doc.configTextStyle({bold: {expand: "after"}}); /// const text = doc.getText("text"); /// text.insert(0, "Hello World!"); @@ -1750,9 +1753,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// doc.configTextStyle({bold: {expand: "after"}}); /// text.insert(0, "Hello World!"); @@ -1838,9 +1841,9 @@ impl LoroText { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// doc.configTextStyle({bold: {expand: "after"}}); /// text.insert(0, "Hello World!"); @@ -1922,7 +1925,7 @@ impl Default for LoroText { #[wasm_bindgen] pub struct LoroMap { handler: MapHandler, - doc: Option>, + doc: Option>, } #[wasm_bindgen] @@ -1950,9 +1953,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("foo", "bar"); /// map.set("foo", "baz"); @@ -1968,9 +1971,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("foo", "bar"); /// map.delete("foo"); @@ -1988,9 +1991,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("foo", "bar"); /// const bar = map.get("foo"); @@ -2013,9 +2016,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("foo", "bar"); /// const bar = map.get("foo"); @@ -2033,9 +2036,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("foo", "bar"); /// map.set("baz", "bar"); @@ -2054,9 +2057,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("foo", "bar"); /// map.set("baz", "bar"); @@ -2075,9 +2078,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("foo", "bar"); /// map.set("baz", "bar"); @@ -2107,9 +2110,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("foo", "bar"); /// const text = map.setContainer("text", new LoroText()); @@ -2125,9 +2128,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("foo", "bar"); /// const text = map.setContainer("text", new LoroText()); @@ -2155,9 +2158,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.subscribe((event)=>{ /// console.log(event); @@ -2186,9 +2189,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// const subscription = map.subscribe((event)=>{ /// console.log(event); @@ -2209,9 +2212,9 @@ impl LoroMap { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const map = doc.getMap("map"); /// map.set("foo", "bar"); /// console.log(map.size); // 1 @@ -2272,7 +2275,7 @@ impl Default for LoroMap { #[wasm_bindgen] pub struct LoroList { handler: ListHandler, - doc: Option>, + doc: Option>, } #[wasm_bindgen] @@ -2298,9 +2301,9 @@ impl LoroList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// list.insert(1, "foo"); @@ -2318,9 +2321,9 @@ impl LoroList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// list.delete(0, 1); @@ -2335,9 +2338,9 @@ impl LoroList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// console.log(list.get(0)); // 100 @@ -2368,9 +2371,9 @@ impl LoroList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// list.insert(1, "foo"); @@ -2401,9 +2404,9 @@ impl LoroList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// const text = list.insertContainer(1, new LoroText()); @@ -2420,9 +2423,9 @@ impl LoroList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// const text = list.insertContainer(1, new LoroText()); @@ -2449,9 +2452,9 @@ impl LoroList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.subscribe((event)=>{ /// console.log(event); @@ -2479,9 +2482,9 @@ impl LoroList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// const subscription = list.subscribe((event)=>{ /// console.log(event); @@ -2502,9 +2505,9 @@ impl LoroList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// list.insert(1, "foo"); @@ -2600,7 +2603,7 @@ impl Default for LoroList { #[wasm_bindgen] pub struct LoroMovableList { handler: MovableListHandler, - doc: Option>, + doc: Option>, } impl Default for LoroMovableList { @@ -2632,9 +2635,9 @@ impl LoroMovableList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// list.insert(1, "foo"); @@ -2652,9 +2655,9 @@ impl LoroMovableList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// list.delete(0, 1); @@ -2669,9 +2672,9 @@ impl LoroMovableList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// console.log(list.get(0)); // 100 @@ -2702,9 +2705,9 @@ impl LoroMovableList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// list.insert(1, "foo"); @@ -2735,9 +2738,9 @@ impl LoroMovableList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// const text = list.insertContainer(1, new LoroText()); @@ -2754,9 +2757,9 @@ impl LoroMovableList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// const text = list.insertContainer(1, new LoroText()); @@ -2783,9 +2786,9 @@ impl LoroMovableList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.subscribe((event)=>{ /// console.log(event); @@ -2813,9 +2816,9 @@ impl LoroMovableList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// const subscription = list.subscribe((event)=>{ /// console.log(event); @@ -2836,9 +2839,9 @@ impl LoroMovableList { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const list = doc.getList("list"); /// list.insert(0, 100); /// list.insert(1, "foo"); @@ -2966,7 +2969,7 @@ impl LoroMovableList { #[wasm_bindgen] pub struct LoroTree { handler: TreeHandler, - doc: Option>, + doc: Option>, } extern crate alloc; @@ -2976,7 +2979,7 @@ extern crate alloc; pub struct LoroTreeNode { id: TreeID, tree: TreeHandler, - doc: Option>, + doc: Option>, } fn parse_js_parent(parent: &JsParentTreeID) -> JsResult> { @@ -3008,7 +3011,7 @@ fn parse_js_tree_id(target: &JsTreeID) -> JsResult { #[wasm_bindgen] impl LoroTreeNode { - fn from_tree(id: TreeID, tree: TreeHandler, doc: Option>) -> Self { + fn from_tree(id: TreeID, tree: TreeHandler, doc: Option>) -> Self { Self { id, tree, doc } } @@ -3026,9 +3029,9 @@ impl LoroTreeNode { /// /// @example /// ```typescript - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// let doc = new Loro(); + /// let doc = new LoroDoc(); /// let tree = doc.getTree("tree"); /// let root = tree.createNode(); /// let node = root.createNode(); @@ -3057,7 +3060,7 @@ impl LoroTreeNode { /// /// @example /// ```ts - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// const root = tree.createChildNode(); /// const node = root.createChildNode(); @@ -3084,9 +3087,9 @@ impl LoroTreeNode { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// const root = tree.createNode(); /// const node = root.createNode(); @@ -3106,9 +3109,9 @@ impl LoroTreeNode { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// const root = tree.createNode(); /// const node = root.createNode(); @@ -3214,9 +3217,9 @@ impl LoroTree { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// const root = tree.createNode(); /// const node = tree.createNode(undefined, 0); @@ -3247,9 +3250,9 @@ impl LoroTree { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// const root = tree.createNode(); /// const node = root.createNode(); @@ -3281,9 +3284,9 @@ impl LoroTree { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// const root = tree.createNode(); /// const node = root.createNode(); @@ -3372,9 +3375,9 @@ impl LoroTree { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// const root = tree.createNode(); /// root.data.set("color", "red"); @@ -3390,9 +3393,9 @@ impl LoroTree { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// const root = tree.createNode(); /// const node = root.createNode(); @@ -3442,9 +3445,9 @@ impl LoroTree { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// tree.subscribe((event)=>{ /// // event.type: "create" | "delete" | "move" @@ -3473,9 +3476,9 @@ impl LoroTree { /// /// @example /// ```ts - /// import { Loro } from "loro-crdt"; + /// import { LoroDoc } from "loro-crdt"; /// - /// const doc = new Loro(); + /// const doc = new LoroDoc(); /// const tree = doc.getTree("tree"); /// const subscription = tree.subscribe((event)=>{ /// console.log(event); @@ -3555,7 +3558,7 @@ impl Default for LoroTree { /// @example /// ```ts /// -/// const doc = new Loro(); +/// const doc = new LoroDoc(); /// const text = doc.getText("text"); /// text.insert(0, "123"); /// const pos0 = text.getCursor(0, 0); @@ -3625,7 +3628,7 @@ impl Cursor { fn loro_value_to_js_value_or_container( value: ValueOrHandler, - doc: Option>, + doc: Option>, ) -> JsValue { match value { ValueOrHandler::Value(v) => { @@ -3655,7 +3658,7 @@ fn loro_value_to_js_value_or_container( #[derive(Debug)] pub struct UndoManager { undo: InnerUndoManager, - doc: Arc, + doc: Arc, } #[wasm_bindgen] @@ -3681,7 +3684,7 @@ impl UndoManager { /// The function will have a meta data value that was attached to the given stack item when /// `onPush` was called. #[wasm_bindgen(constructor)] - pub fn new(doc: &Loro, config: JsUndoConfig) -> Self { + pub fn new(doc: &LoroDoc, config: JsUndoConfig) -> Self { let max_undo_steps = Reflect::get(&config, &JsValue::from_str("maxUndoSteps")) .unwrap_or(JsValue::from_f64(100.0)) .as_f64() @@ -3766,7 +3769,7 @@ impl UndoManager { } /// Check if the undo manager is bound to the given document. - pub fn checkBinding(&self, doc: &Loro) -> bool { + pub fn checkBinding(&self, doc: &LoroDoc) -> bool { Arc::ptr_eq(&self.doc, &doc.0) } @@ -4040,7 +4043,7 @@ impl Container { /// - changeNum #[wasm_bindgen(js_name = "decodeImportBlobMeta")] pub fn decode_import_blob_meta(blob: &[u8]) -> JsResult { - let meta: ImportBlobMetadata = LoroDoc::decode_import_blob_meta(blob)?; + let meta: ImportBlobMetadata = LoroDocInner::decode_import_blob_meta(blob)?; Ok(meta.into()) } @@ -4052,9 +4055,9 @@ const TYPES: &'static str = r#" * It is most commonly used to specify the type of sub-container to be created. * @example * ```ts -* import { Loro } from "loro-crdt"; +* import { LoroDoc } from "loro-crdt"; * -* const doc = new Loro(); +* const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, 100); * const containerType = "Text"; @@ -4069,9 +4072,9 @@ export type PeerID = `${number}`; * * @example * ```ts -* import { Loro } from "loro-crdt"; +* import { LoroDoc } from "loro-crdt"; * -* const doc = new Loro(); +* const doc = new LoroDoc(); * const list = doc.getList("list"); * const containerId = list.id; * ``` @@ -4085,15 +4088,15 @@ export type ContainerID = */ export type TreeID = `${number}@${PeerID}`; -interface Loro { +interface LoroDoc { /** * Export updates from the specific version to the current version * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "Hello"); * // get all updates of the doc @@ -4112,9 +4115,9 @@ interface Loro { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * let text = doc.getText("text"); * const textId = text.id; * text = doc.getContainerById(textId); @@ -4246,7 +4249,7 @@ interface LoroText { * @example * ```ts * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "123"); * const pos0 = text.getCursor(0, 0); @@ -4284,7 +4287,7 @@ interface LoroList { * @example * ```ts * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const text = doc.getList("list"); * text.insert(0, "1"); * const pos0 = text.getCursor(0, 0); @@ -4334,7 +4337,7 @@ interface LoroMovableList { * @example * ```ts * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const text = doc.getMovableList("text"); * text.insert(0, "1"); * const pos0 = text.getCursor(0, 0); diff --git a/loro-js/src/index.ts b/loro-js/src/index.ts index ce3f6e64..61dd88d0 100644 --- a/loro-js/src/index.ts +++ b/loro-js/src/index.ts @@ -3,7 +3,7 @@ import { Container, ContainerID, Delta, - Loro, + LoroDoc, LoroList, LoroMap, LoroText, @@ -13,6 +13,11 @@ import { TreeID, Value, } from "loro-wasm"; + +/** + * @deprecated Please use LoroDoc + */ +export class Loro extends LoroDoc {} export { Awareness } from "./awareness"; export type Frontiers = OpId[]; @@ -121,14 +126,14 @@ export function isContainerId(s: string): s is ContainerID { return s.startsWith("cid:"); } -export { Loro }; +export { LoroDoc }; /** Whether the value is a container. * * # Example * * ```ts - * const doc = new Loro(); + * const doc = new LoroDoc(); * const map = doc.getMap("map"); * const list = doc.getList("list"); * const text = doc.getText("text"); @@ -157,7 +162,7 @@ export function isContainer(value: any): value is Container { * # Example * * ```ts - * const doc = new Loro(); + * const doc = new LoroDoc(); * const map = doc.getMap("map"); * const list = doc.getList("list"); * const text = doc.getText("text"); @@ -189,7 +194,7 @@ export function getType( } declare module "loro-wasm" { - interface Loro { + interface LoroDoc { subscribe(listener: Listener): number; } @@ -210,7 +215,7 @@ declare module "loro-wasm" { setOnPop(listener?: UndoConfig["onPop"]): void; } - interface Loro< + interface LoroDoc< T extends Record = Record, > { /** @@ -221,9 +226,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const map = doc.getMap("map"); * ``` */ @@ -238,9 +243,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getList("list"); * ``` */ @@ -255,9 +260,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getList("list"); * ``` */ @@ -272,9 +277,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const tree = doc.getTree("tree"); * ``` */ @@ -292,9 +297,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, 100); * list.insert(1, "foo"); @@ -309,9 +314,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro, LoroText } from "loro-crdt"; + * import { LoroDoc, LoroText } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, 100); * const text = list.insertContainer(1, new LoroText()); @@ -328,9 +333,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, 100); * console.log(list.get(0)); // 100 @@ -343,9 +348,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, 100); * list.insert(1, "foo"); @@ -368,9 +373,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro, LoroText } from "loro-crdt"; + * import { LoroDoc, LoroText } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getMovableList("list"); * list.insert(0, 100); * list.insert(1, "foo"); @@ -385,9 +390,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getMovableList("list"); * list.insert(0, 100); * const text = list.insertContainer(1, new LoroText()); @@ -404,10 +409,10 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); - * const list = doc.getMoableList("list"); + * const doc = new LoroDoc(); + * const list = doc.getMovableList("list"); * list.insert(0, 100); * console.log(list.get(0)); // 100 * console.log(list.get(1)); // undefined @@ -419,9 +424,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getMovableList("list"); * list.insert(0, 100); * list.insert(1, "foo"); @@ -447,9 +452,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, 100); * list.insert(1, "foo"); @@ -464,9 +469,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const list = doc.getMovableList("list"); * list.insert(0, 100); * const text = list.setContainer(0, new LoroText()); @@ -492,9 +497,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("foo", "bar"); * const bar = map.get("foo"); @@ -506,9 +511,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("foo", "bar"); * const text = map.setContainer("text", new LoroText()); @@ -528,9 +533,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("foo", "bar"); * const bar = map.get("foo"); @@ -544,9 +549,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("foo", "bar"); * map.set("foo", "baz"); @@ -579,9 +584,9 @@ declare module "loro-wasm" { * * @example * ```ts - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * const doc = new Loro(); + * const doc = new LoroDoc(); * const tree = doc.getTree("tree"); * const root = tree.createNode(); * const node = tree.createNode(undefined, 0); @@ -617,9 +622,9 @@ declare module "loro-wasm" { * * @example * ```typescript - * import { Loro } from "loro-crdt"; + * import { LoroDoc } from "loro-crdt"; * - * let doc = new Loro(); + * let doc = new LoroDoc(); * let tree = doc.getTree("tree"); * let root = tree.createNode(); * let node = root.createNode(); diff --git a/loro-js/tests/basic.test.ts b/loro-js/tests/basic.test.ts index ccca652d..7342c321 100644 --- a/loro-js/tests/basic.test.ts +++ b/loro-js/tests/basic.test.ts @@ -3,7 +3,7 @@ import { Container, getType, isContainer, - Loro, + LoroDoc, LoroList, LoroMap, LoroText, @@ -11,7 +11,7 @@ import { } from "../src"; it("basic example", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getList("list"); list.insert(0, "A"); list.insert(1, "B"); @@ -53,7 +53,7 @@ it("basic example", () => { }); it("get or create on Map", () => { - const docA = new Loro(); + const docA = new LoroDoc(); const map = docA.getMap("map"); const container = map.getOrCreateContainer("list", new LoroList()); container.insert(0, 1); @@ -66,8 +66,8 @@ it("get or create on Map", () => { }); it("basic sync example", () => { - const docA = new Loro(); - const docB = new Loro(); + const docA = new LoroDoc(); + const docB = new LoroDoc(); const listA = docA.getList("list"); listA.insert(0, "A"); listA.insert(1, "B"); @@ -91,14 +91,14 @@ it("basic sync example", () => { }); it("basic events", () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.subscribe((event) => {}); const list = doc.getList("list"); }); describe("list", () => { it("insert containers", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getList("list"); const map = list.insertContainer(0, new LoroMap()); map.set("key", "value"); @@ -108,7 +108,7 @@ describe("list", () => { }); it("toArray", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getList("list"); list.insert(0, 1); list.insert(1, 2); @@ -123,7 +123,7 @@ describe("list", () => { describe("map", () => { it("get child container", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const map = doc.getMap("map"); const list = map.setContainer("key", new LoroList()); list.insert(0, 1); @@ -132,7 +132,7 @@ describe("map", () => { }); it("set large int", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const map = doc.getMap("map"); map.set("key", 2147483699); expect(map.get("key")).toBe(2147483699); @@ -141,12 +141,12 @@ describe("map", () => { describe("import", () => { it("pending", () => { - const a = new Loro(); + const a = new LoroDoc(); a.getText("text").insert(0, "a"); - const b = new Loro(); + const b = new LoroDoc(); b.import(a.exportFrom()); b.getText("text").insert(1, "b"); - const c = new Loro(); + const c = new LoroDoc(); c.import(b.exportFrom()); c.getText("text").insert(2, "c"); @@ -161,9 +161,9 @@ describe("import", () => { }); it("import by frontiers", () => { - const a = new Loro(); + const a = new LoroDoc(); a.getText("text").insert(0, "a"); - const b = new Loro(); + const b = new LoroDoc(); b.import(a.exportFrom()); b.getText("text").insert(1, "b"); b.getList("list").insert(0, [1, 2]); @@ -173,18 +173,18 @@ describe("import", () => { }); it("from snapshot", () => { - const a = new Loro(); + const a = new LoroDoc(); a.getText("text").insert(0, "hello"); const bytes = a.exportSnapshot(); - const b = Loro.fromSnapshot(bytes); + const b = LoroDoc.fromSnapshot(bytes); b.getText("text").insert(0, "123"); expect(b.toJSON()).toStrictEqual({ text: "123hello" }); }); it("importBatch Error #181", () => { - const docA = new Loro(); + const docA = new LoroDoc(); const updateA = docA.exportSnapshot(); - const docB = new Loro(); + const docB = new LoroDoc(); docB.importUpdateBatch([updateA]); docB.getText("text").insert(0, "hello"); docB.commit(); @@ -193,7 +193,7 @@ describe("import", () => { describe("map", () => { it("keys", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const map = doc.getMap("map"); map.set("foo", "bar"); map.set("baz", "bar"); @@ -202,7 +202,7 @@ describe("map", () => { }); it("values", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const map = doc.getMap("map"); map.set("foo", "bar"); map.set("baz", "bar"); @@ -211,7 +211,7 @@ describe("map", () => { }); it("entries", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const map = doc.getMap("map"); map.set("foo", "bar"); map.set("baz", "bar"); @@ -225,7 +225,7 @@ describe("map", () => { }); it("entries should return container handlers", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const map = doc.getMap("map"); map.setContainer("text", new LoroText()); map.set("foo", "bar"); @@ -235,7 +235,7 @@ describe("map", () => { }); it("handlers should still be usable after doc is dropped", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); const list = doc.getList("list"); const map = doc.getMap("map"); @@ -249,9 +249,9 @@ it("handlers should still be usable after doc is dropped", () => { }); it("get change with given lamport", () => { - const doc1 = new Loro(); + const doc1 = new LoroDoc(); doc1.setPeerId(1); - const doc2 = new Loro(); + const doc2 = new LoroDoc(); doc2.setPeerId(2); doc1.getText("text").insert(0, "01234"); doc2.import(doc1.exportFrom()); @@ -304,7 +304,7 @@ it("isContainer", () => { expect(isContainer({})).toBeFalsy(); expect(isContainer(undefined)).toBeFalsy(); expect(isContainer(null)).toBeFalsy(); - const doc = new Loro(); + const doc = new LoroDoc(); const t = doc.getText("t"); expect(isContainer(t)).toBeTruthy(); expect(isContainer(doc.getMap("m"))).toBeTruthy(); @@ -318,7 +318,7 @@ it("isContainer", () => { it("getValueType", () => { // Type tests - const doc = new Loro(); + const doc = new LoroDoc(); const t = doc.getText("t"); expectTypeOf(getType(t)).toEqualTypeOf<"Text">(); expect(getType(t)).toBe("Text"); @@ -346,7 +346,7 @@ it("getValueType", () => { }); it("enable timestamp", () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.setPeerId(1); doc.getText("123").insert(0, "123"); doc.commit(); @@ -365,7 +365,7 @@ it("enable timestamp", () => { }); it("commit with specified timestamp", () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.setPeerId(1); doc.getText("123").insert(0, "123"); doc.commit(undefined, 111); @@ -375,7 +375,7 @@ it("commit with specified timestamp", () => { it("can control the mergeable interval", () => { { - const doc = new Loro(); + const doc = new LoroDoc(); doc.setPeerId(1); doc.getText("123").insert(0, "1"); doc.commit(undefined, 110); @@ -385,7 +385,7 @@ it("can control the mergeable interval", () => { } { - const doc = new Loro(); + const doc = new LoroDoc(); doc.setPeerId(1); doc.setChangeMergeInterval(10); doc.getText("123").insert(0, "1"); @@ -398,7 +398,7 @@ it("can control the mergeable interval", () => { }); it("get container parent", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const m = doc.getMap("m"); expect(m.parent()).toBeUndefined(); const list = m.setContainer("t", new LoroList()); @@ -429,7 +429,7 @@ it("prelim support", () => { text.delete(1, 1); expect(list.toJSON()).toStrictEqual([{ "3": 2, "9": 9, text: "Hello" }]); } - const doc = new Loro(); + const doc = new LoroDoc(); const rootMap = doc.getMap("map"); rootMap.setContainer("test", map); // new way to create sub-container @@ -452,7 +452,7 @@ it("prelim support", () => { }); it("get elem by path", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const map = doc.getMap("map"); map.set("key", 1); expect(doc.getByPath("map/key")).toBe(1); @@ -463,7 +463,7 @@ it("get elem by path", () => { }); it("fork", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const map = doc.getMap("map"); map.set("key", 1); const doc2 = doc.fork(); diff --git a/loro-js/tests/checkout.test.ts b/loro-js/tests/checkout.test.ts index 5b23998c..44f002fa 100644 --- a/loro-js/tests/checkout.test.ts +++ b/loro-js/tests/checkout.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from "vitest"; -import { Loro } from "../src"; +import { LoroDoc } from "../src"; describe("Checkout", () => { it("simple checkout", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.setPeerId(0n); const text = doc.getText("text"); text.insert(0, "H"); @@ -31,7 +31,7 @@ describe("Checkout", () => { }); it("Chinese char", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); text.insert(0, "你好世界"); doc.commit(); @@ -55,13 +55,13 @@ describe("Checkout", () => { }); it("two clients", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); text.insert(0, "0"); doc.commit(); const v0 = doc.frontiers(); - const docB = new Loro(); + const docB = new LoroDoc(); docB.import(doc.exportFrom()); expect(docB.cmpWithFrontiers(v0)).toBe(0); text.insert(1, "0"); diff --git a/loro-js/tests/counter.test.ts b/loro-js/tests/counter.test.ts index 7223b244..5eb961d8 100644 --- a/loro-js/tests/counter.test.ts +++ b/loro-js/tests/counter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { CounterDiff, Loro } from "../src"; +import { CounterDiff, LoroDoc } from "../src"; function oneMs(): Promise { return new Promise((r) => setTimeout(r)); @@ -7,7 +7,7 @@ function oneMs(): Promise { describe("counter", () => { it("increment", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const counter = doc.getCounter("counter"); counter.increment(1); counter.increment(2); @@ -16,7 +16,7 @@ describe("counter", () => { }); it("encode", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); const counter = doc.getCounter("counter"); counter.increment(1); counter.increment(2); @@ -25,13 +25,13 @@ describe("counter", () => { const updates = doc.exportFrom(); const snapshot = doc.exportSnapshot(); const json = doc.exportJsonUpdates(); - const doc2 = new Loro(); + const doc2 = new LoroDoc(); doc2.import(updates); expect(doc2.toJSON()).toStrictEqual(doc.toJSON()); - const doc3 = new Loro(); + const doc3 = new LoroDoc(); doc3.import(snapshot); expect(doc3.toJSON()).toStrictEqual(doc.toJSON()); - const doc4 = new Loro(); + const doc4 = new LoroDoc(); doc4.importJsonUpdates(json); expect(doc4.toJSON()).toStrictEqual(doc.toJSON()); }); @@ -39,7 +39,7 @@ describe("counter", () => { describe("counter event", () => { it("event", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); let triggered = false; doc.subscribe((e) => { triggered = true; diff --git a/loro-js/tests/event.test.ts b/loro-js/tests/event.test.ts index 65d74aec..f67ca43c 100644 --- a/loro-js/tests/event.test.ts +++ b/loro-js/tests/event.test.ts @@ -3,7 +3,7 @@ import { Delta, getType, ListDiff, - Loro, + LoroDoc, LoroEventBatch, LoroList, LoroMap, @@ -14,7 +14,7 @@ import { describe("event", () => { it("target", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); let lastEvent: undefined | LoroEventBatch; loro.subscribe((event) => { expect(event.by).toBe("local"); @@ -29,7 +29,7 @@ describe("event", () => { }); it("path", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); let lastEvent: undefined | LoroEventBatch; loro.subscribe((event) => { lastEvent = event; @@ -52,7 +52,7 @@ describe("event", () => { }); it("text diff", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); let lastEvent: undefined | LoroEventBatch; loro.subscribe((event) => { lastEvent = event; @@ -75,7 +75,7 @@ describe("event", () => { }); it("list diff", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); let lastEvent: undefined | LoroEventBatch; loro.subscribe((event) => { lastEvent = event; @@ -98,7 +98,7 @@ describe("event", () => { }); it("map diff", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); let lastEvent: undefined | LoroEventBatch; loro.subscribe((event) => { lastEvent = event; @@ -129,7 +129,7 @@ describe("event", () => { }); it("tree", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); let lastEvent: undefined | LoroEventBatch; loro.subscribe((event) => { lastEvent = event; @@ -144,7 +144,7 @@ describe("event", () => { describe("subscribe container events", () => { it("text", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); const text = loro.getText("text"); let ran = 0; const sub = text.subscribe((event) => { @@ -179,7 +179,7 @@ describe("event", () => { }); it("map subscribe deep", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); const map = loro.getMap("map"); let times = 0; const sub = map.subscribe((event) => { @@ -208,7 +208,7 @@ describe("event", () => { }); it("list subscribe deep", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); const list = loro.getList("list"); let times = 0; const sub = list.subscribe((event) => { @@ -236,7 +236,7 @@ describe("event", () => { describe("text event length should be utf16", () => { it("test", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); const text = loro.getText("text"); let string = ""; text.subscribe((event) => { @@ -285,7 +285,7 @@ describe("event", () => { describe("handler in event", () => { it("test", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); const list = loro.getList("list"); let first = true; loro.subscribe((e) => { @@ -304,7 +304,7 @@ describe("event", () => { }); it("diff can contain containers", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getList("list"); let ran = false; doc.subscribe((event) => { @@ -330,11 +330,11 @@ describe("event", () => { }); it("remote event", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getList("list"); list.insert(0, 123); { - const doc2 = new Loro(); + const doc2 = new LoroDoc(); let triggered = false; doc2.subscribe((event) => { expect(event.by).toBe("import"); @@ -345,7 +345,7 @@ describe("event", () => { expect(triggered).toBeTruthy(); } { - const doc2 = new Loro(); + const doc2 = new LoroDoc(); let triggered = false; doc2.subscribe((event) => { expect(event.by).toBe("import"); @@ -358,7 +358,7 @@ describe("event", () => { }); it("checkout event", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getList("list"); list.insert(0, 123); doc.commit(); diff --git a/loro-js/tests/issue.test.ts b/loro-js/tests/issue.test.ts index 20bf0d2c..5828246a 100644 --- a/loro-js/tests/issue.test.ts +++ b/loro-js/tests/issue.test.ts @@ -1,14 +1,14 @@ import { describe, expect, expectTypeOf, it } from "vitest"; -import { Loro } from "../src"; +import { LoroDoc } from "../src"; import { Container, LoroText, OpId } from "../src"; import { setDebug } from "loro-wasm"; it("#211", () => { - const loro1 = new Loro(); + const loro1 = new LoroDoc(); loro1.setPeerId(0n); const text1 = loro1.getText("text"); - const loro2 = new Loro(); + const loro2 = new LoroDoc(); loro2.setPeerId(1n); const text2 = loro2.getText("text"); @@ -59,7 +59,7 @@ it("#211", () => { show(text1, loro1, text2, loro2); }); -function show(text1: LoroText, loro1: Loro, text2: LoroText, loro2: Loro) { +function show(text1: LoroText, loro1: LoroDoc, text2: LoroText, loro2: LoroDoc) { // console.log(` #0 has content: ${JSON.stringify(text1.toString())}`); // console.log(` #0 has frontiers: ${showFrontiers(loro1.frontiers())}`); // console.log(` #1 has content: ${JSON.stringify(text2.toString())}`); diff --git a/loro-js/tests/json.test.ts b/loro-js/tests/json.test.ts index 217abcf0..682f1b78 100644 --- a/loro-js/tests/json.test.ts +++ b/loro-js/tests/json.test.ts @@ -1,12 +1,12 @@ import { expect, it } from "vitest"; import { - Loro, + LoroDoc, LoroMap, TextOp, } from "../src"; it("json encoding", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); text.insert(0, "123"); const map = doc.getMap("map"); @@ -26,7 +26,7 @@ it("json encoding", () => { text.mark({ start: 0, end: 3 }, "bold", true); const json = doc.exportJsonUpdates(); // console.log(json.changes[0].ops); - const doc2 = new Loro(); + const doc2 = new LoroDoc(); doc2.importJsonUpdates(json); }); @@ -134,13 +134,13 @@ it("json decoding", () => { } ] }`; - const doc = new Loro(); + const doc = new LoroDoc(); doc.importJsonUpdates(v15Json); // console.log(doc.exportJsonUpdates()); }); it("test some type correctness", () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.setPeerId(0); doc.getText("text").insert(0, "123"); doc.commit(); diff --git a/loro-js/tests/misc.test.ts b/loro-js/tests/misc.test.ts index c1511add..67253603 100644 --- a/loro-js/tests/misc.test.ts +++ b/loro-js/tests/misc.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { Loro, LoroList, LoroMap, LoroText, VersionVector } from "../src"; +import { LoroDoc, LoroList, LoroMap, LoroText, VersionVector } from "../src"; import { expectTypeOf } from "vitest"; function assertEquals(a: any, b: any) { @@ -8,7 +8,7 @@ function assertEquals(a: any, b: any) { describe("transaction", () => { it("transaction", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); const text = loro.getText("text"); let count = 0; const sub = loro.subscribe(() => { @@ -26,7 +26,7 @@ describe("transaction", () => { }); it("transaction origin", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); const text = loro.getText("text"); let count = 0; const sub = loro.subscribe((event: { origin: string }) => { @@ -48,7 +48,7 @@ describe("transaction", () => { describe("subscribe", () => { it("subscribe_lock", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); const text = loro.getText("text"); const list = loro.getList("list"); let count = 0; @@ -80,7 +80,7 @@ describe("subscribe", () => { }); it("subscribe_lock2", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); const text = loro.getText("text"); let count = 0; const sub = loro.subscribe(() => { @@ -101,7 +101,7 @@ describe("subscribe", () => { }); it("subscribe", async () => { - const loro = new Loro(); + const loro = new LoroDoc(); const text = loro.getText("text"); let count = 0; const sub = loro.subscribe(() => { @@ -125,8 +125,8 @@ describe("subscribe", () => { describe("sync", () => { it("two insert at beginning", async () => { - const a = new Loro(); - const b = new Loro(); + const a = new LoroDoc(); + const b = new LoroDoc(); let a_version: undefined | VersionVector = undefined; let b_version: undefined | VersionVector = undefined; a.subscribe((e) => { @@ -153,11 +153,11 @@ describe("sync", () => { }); it("sync", () => { - const loro = new Loro(); + const loro = new LoroDoc(); const text = loro.getText("text"); text.insert(0, "hello world"); - const loro_bk = new Loro(); + const loro_bk = new LoroDoc(); loro_bk.import(loro.exportFrom(undefined)); assertEquals(loro_bk.toJSON(), loro.toJSON()); const text_bk = loro_bk.getText("text"); @@ -172,7 +172,7 @@ describe("sync", () => { }); describe("wasm", () => { - const loro = new Loro(); + const loro = new LoroDoc(); const a = loro.getText("ha"); a.insert(0, "hello world"); a.delete(6, 5); @@ -208,14 +208,14 @@ describe("wasm", () => { describe("type", () => { it("test map type", () => { - const loro = new Loro<{ map: LoroMap<{ name: "he" }> }>(); + const loro = new LoroDoc<{ map: LoroMap<{ name: "he" }> }>(); const map = loro.getMap("map"); const v = map.get("name"); expectTypeOf(v).toEqualTypeOf<"he">(); }); it("test recursive map type", () => { - const loro = new Loro<{ map: LoroMap<{ map: LoroMap<{ name: "he" }> }> }>(); + const loro = new LoroDoc<{ map: LoroMap<{ map: LoroMap<{ name: "he" }> }> }>(); const map = loro.getMap("map"); map.setContainer("map", new LoroMap()); @@ -225,7 +225,7 @@ describe("type", () => { }); it("works for list type", () => { - const loro = new Loro<{ list: LoroList }>(); + const loro = new LoroDoc<{ list: LoroList }>(); const list = loro.getList("list"); list.insert(0, "123"); const v0 = list.get(0); @@ -233,7 +233,7 @@ describe("type", () => { }); it("test binary type", () => { - const loro = new Loro<{ list: LoroList }>(); + const loro = new LoroDoc<{ list: LoroList }>(); const list = loro.getList("list"); list.insert(0, new Uint8Array(10)); const v0 = list.get(0); @@ -245,7 +245,7 @@ describe("type", () => { describe("list stable position", () => { it("basic tests", () => { - const loro = new Loro(); + const loro = new LoroDoc(); const list = loro.getList("list"); list.insert(0, "a"); const pos0 = list.getCursor(0); diff --git a/loro-js/tests/movable_list.test.ts b/loro-js/tests/movable_list.test.ts index 11b120bd..546e5922 100644 --- a/loro-js/tests/movable_list.test.ts +++ b/loro-js/tests/movable_list.test.ts @@ -2,7 +2,7 @@ import { describe, expect, expectTypeOf, it } from "vitest"; import { Delta, ListDiff, - Loro, + LoroDoc, LoroList, LoroMap, LoroMovableList, @@ -12,7 +12,7 @@ import { describe("movable list", () => { it("should work like list", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getMovableList("list"); expect(list.length).toBe(0); list.push("a"); @@ -24,7 +24,7 @@ describe("movable list", () => { }); it("can be synced", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getMovableList("list"); list.push("a"); list.push("b"); @@ -32,7 +32,7 @@ describe("movable list", () => { expect(list.toArray()).toEqual(["a", "b", "c"]); list.set(2, "d"); list.move(0, 1); - const doc2 = new Loro(); + const doc2 = new LoroDoc(); const list2 = doc2.getMovableList("list"); expect(list2.length).toBe(0); doc2.import(doc.exportFrom()); @@ -43,7 +43,7 @@ describe("movable list", () => { }); it("should support move", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getMovableList("list"); list.push("a"); list.push("b"); @@ -54,7 +54,7 @@ describe("movable list", () => { }); it("should support set", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getMovableList("list"); list.push("a"); list.push("b"); @@ -65,7 +65,7 @@ describe("movable list", () => { }); it.todo("should support get cursor", () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.setPeerId(1); const list = doc.getMovableList("list"); list.push("a"); @@ -88,7 +88,7 @@ describe("movable list", () => { }); it("inserts sub-container", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getMovableList("list"); list.push("a"); list.push("b"); @@ -105,7 +105,7 @@ describe("movable list", () => { }); it("can be inserted into a list as an attached container", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getMovableList("list"); list.push("a"); list.push("b"); @@ -121,12 +121,12 @@ describe("movable list", () => { }); it("length should be correct when there are concurrent move", () => { - const docA = new Loro(); + const docA = new LoroDoc(); const list = docA.getMovableList("list"); list.push("a"); list.push("b"); list.push("c"); - const docB = new Loro(); + const docB = new LoroDoc(); const listB = docB.getMovableList("list"); docB.import(docA.exportFrom()); listB.move(0, 1); @@ -137,13 +137,13 @@ describe("movable list", () => { }); it("concurrent set the one with larger peer id win", () => { - const docA = new Loro(); + const docA = new LoroDoc(); docA.setPeerId(0); const listA = docA.getMovableList("list"); listA.push("a"); listA.push("b"); listA.push("c"); - const docB = new Loro(); + const docB = new LoroDoc(); docB.setPeerId(1); const listB = docB.getMovableList("list"); docB.import(docA.exportFrom()); @@ -158,7 +158,7 @@ describe("movable list", () => { }); it("can be subscribe", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getMovableList("list"); list.push("a"); list.push("b"); @@ -198,7 +198,7 @@ describe("movable list", () => { }); it("has the right type", () => { - const doc = new Loro< + const doc = new LoroDoc< { list: LoroMovableList> } >(); const list = doc.getMovableList("list"); @@ -209,7 +209,7 @@ describe("movable list", () => { }); it("set container", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const list = doc.getMovableList("list"); list.insert(0, 100); const text = list.setContainer(0, new LoroText()); diff --git a/loro-js/tests/richtext.test.ts b/loro-js/tests/richtext.test.ts index abfc3a41..89625151 100644 --- a/loro-js/tests/richtext.test.ts +++ b/loro-js/tests/richtext.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from "vitest"; -import { Delta, Loro, TextDiff } from "../src"; +import { Delta, LoroDoc, TextDiff } from "../src"; import { Cursor, OpId, PeerID, setDebug } from "loro-wasm"; describe("richtext", () => { it("mark", () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.configTextStyle({ bold: { expand: "after" }, link: { expand: "before" }, @@ -26,7 +26,7 @@ describe("richtext", () => { }); it("insert after emoji", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); text.insert(0, "👨‍👩‍👦"); text.insert(8, "a"); @@ -34,7 +34,7 @@ describe("richtext", () => { }); it("emit event correctly", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); let triggered = false; text.subscribe((e) => { @@ -63,7 +63,7 @@ describe("richtext", () => { }); it("emit event from merging doc correctly", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); let called = false; text.subscribe((event) => { @@ -83,7 +83,7 @@ describe("richtext", () => { } }); - const docB = new Loro(); + const docB = new LoroDoc(); const textB = docB.getText("text"); textB.insert(0, "Hello World!"); textB.mark({ start: 0, end: 5 }, "bold", true); @@ -93,7 +93,7 @@ describe("richtext", () => { }); it("Delete emoji", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); text.insert(0, "012345👨‍👩‍👦6789"); doc.commit(); @@ -116,13 +116,13 @@ describe("richtext", () => { }); it("apply delta", async () => { - const doc1 = new Loro(); + const doc1 = new LoroDoc(); doc1.configTextStyle({ link: { expand: "none" }, bold: { expand: "after" }, }); const text1 = doc1.getText("text"); - const doc2 = new Loro(); + const doc2 = new LoroDoc(); doc2.configTextStyle({ link: { expand: "none" }, bold: { expand: "after" }, @@ -154,7 +154,7 @@ describe("richtext", () => { }); it("custom richtext type", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.configTextStyle({ myStyle: { expand: "none", @@ -178,7 +178,7 @@ describe("richtext", () => { }); it("allow overlapped styles", () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.configTextStyle({ comment: { expand: "none" }, }); @@ -206,7 +206,7 @@ describe("richtext", () => { }); it("Cursor example", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); text.insert(0, "123"); const pos0 = text.getCursor(0, 0); @@ -222,7 +222,7 @@ describe("richtext", () => { }); it("Get and query cursor", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); doc.setPeerId("1"); text.insert(0, "123"); @@ -243,7 +243,7 @@ describe("richtext", () => { const bytes = pos0!.encode(); // Sending pos0 over the network const pos0decoded = Cursor.decode(bytes); - const docA = new Loro(); + const docA = new LoroDoc(); docA.import(doc.exportFrom()); { const ans = docA.getCursorPos(pos0decoded!); @@ -272,7 +272,7 @@ describe("richtext", () => { }); it("Styles should not affect cursor pos", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); text.insert(0, "Hello"); const pos3 = text.getCursor(3); @@ -282,13 +282,13 @@ describe("richtext", () => { }); it("Insert cursed str", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText("text"); text.insert(0, `“aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`); }); it("Insert/delete by utf8 index", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText('t'); text.insert(0, "你好"); text.insertUtf8(3, "a"); @@ -303,28 +303,28 @@ describe("richtext", () => { }); it("Slice", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText('t'); text.insert(0, "你好"); expect(text.slice(0, 1)).toStrictEqual("你"); }); it("Slice emoji", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText('t'); text.insert(0, "😡😡😡"); expect(text.slice(0, 2)).toStrictEqual("😡"); }); it("CharAt", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText('t'); text.insert(0, "你好"); expect(text.charAt(1)).toStrictEqual("好"); }); it("Splice", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText('t'); text.insert(0, "你好"); expect(text.splice(1, 1, "我")).toStrictEqual("好"); @@ -332,7 +332,7 @@ describe("richtext", () => { }); it("Text iter", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText('t'); text.insert(0, "你好"); let str = ""; @@ -344,7 +344,7 @@ describe("richtext", () => { }); it("Text update", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const text = doc.getText('t'); text.insert(0, "Hello 😊Bro"); text.update("Hello World Bro😊"); diff --git a/loro-js/tests/tree.test.ts b/loro-js/tests/tree.test.ts index d6a2d07f..bd10a30e 100644 --- a/loro-js/tests/tree.test.ts +++ b/loro-js/tests/tree.test.ts @@ -1,12 +1,12 @@ import { assert, describe, expect, it} from "vitest"; -import { Loro, LoroTree, LoroTreeNode } from "../src"; +import { LoroDoc, LoroTree, LoroTreeNode } from "../src"; function assertEquals(a: any, b: any) { expect(a).toStrictEqual(b); } describe("loro tree", () => { - const loro = new Loro(); + const loro = new LoroDoc(); const tree = loro.getTree("root"); it("create", () => { @@ -76,7 +76,7 @@ describe("loro tree", () => { }); it("toArray", ()=>{ - const loro2 = new Loro(); + const loro2 = new LoroDoc(); const tree2 = loro2.getTree("root"); const root = tree2.createNode(); tree2.createNode(root.id); @@ -119,7 +119,7 @@ describe("loro tree", () => { }); describe("loro tree node", ()=>{ - const loro = new Loro(); + const loro = new LoroDoc(); const tree = loro.getTree("root"); it("create", () => { @@ -184,4 +184,4 @@ describe("loro tree node", ()=>{ function one_ms(): Promise { return new Promise((resolve) => setTimeout(resolve, 1)); -} \ No newline at end of file +} diff --git a/loro-js/tests/type.test.ts b/loro-js/tests/type.test.ts index 244ba772..3a73be32 100644 --- a/loro-js/tests/type.test.ts +++ b/loro-js/tests/type.test.ts @@ -1,5 +1,5 @@ import { - Loro, + LoroDoc, LoroList, LoroMap, LoroMovableList, @@ -16,7 +16,7 @@ test("Container should not match Value", () => { }); test("A non-numeric string is not a valid peer id", () => { - const doc = new Loro(); + const doc = new LoroDoc(); expectTypeOf(doc.peerIdStr).toMatchTypeOf(); expectTypeOf("123" as const).toMatchTypeOf(); expectTypeOf("a123" as const).not.toMatchTypeOf(); @@ -34,7 +34,7 @@ test("Expect container type", () => { }); test("doc type and container type", () => { - const doc = new Loro<{ + const doc = new LoroDoc<{ text: LoroText; map: LoroMap<{ name?: string; diff --git a/loro-js/tests/undo.test.ts b/loro-js/tests/undo.test.ts index c2cad12e..ff242408 100644 --- a/loro-js/tests/undo.test.ts +++ b/loro-js/tests/undo.test.ts @@ -1,9 +1,9 @@ -import { Cursor, Loro, UndoManager } from "../src"; +import { Cursor, LoroDoc, UndoManager } from "../src"; import { describe, expect, test } from "vitest"; describe("undo", () => { test("basic text undo", () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.setPeerId(1); const undo = new UndoManager(doc, { maxUndoSteps: 100, mergeInterval: 0 }); expect(undo.canRedo()).toBeFalsy(); @@ -41,7 +41,7 @@ describe("undo", () => { }); test("merge", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); const undo = new UndoManager(doc, { maxUndoSteps: 100, mergeInterval: 50 }); for (let i = 0; i < 10; i++) { doc.getText("text").insert(i, i.toString()); @@ -66,7 +66,7 @@ describe("undo", () => { }); test("max undo steps", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const undo = new UndoManager(doc, { maxUndoSteps: 100, mergeInterval: 0 }); for (let i = 0; i < 200; i++) { doc.getText("text").insert(0, "0"); @@ -80,7 +80,7 @@ describe("undo", () => { }); test("Skip chosen events", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const undo = new UndoManager(doc, { maxUndoSteps: 100, mergeInterval: 0, @@ -130,7 +130,7 @@ describe("undo", () => { }); test("undo event's origin", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); let undoing = false; let ran = false; doc.subscribe((e) => { @@ -151,7 +151,7 @@ describe("undo", () => { }); test("undo event listener", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); let pushReturn: null | number = null; let expectedValue: null | number = null; @@ -199,7 +199,7 @@ describe("undo", () => { }); test("undo cursor transform", async () => { - const doc = new Loro(); + const doc = new LoroDoc(); let cursors: Cursor[] = []; let poppedCursors: Cursor[] = []; const undo = new UndoManager(doc , { diff --git a/loro-js/tests/version.test.ts b/loro-js/tests/version.test.ts index ccd354de..b7795f04 100644 --- a/loro-js/tests/version.test.ts +++ b/loro-js/tests/version.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { decodeImportBlobMeta, - Loro, + LoroDoc, LoroMap, OpId, VersionVector, @@ -9,14 +9,14 @@ import { describe("Frontiers", () => { it("two clients", () => { - const doc = new Loro(); + const doc = new LoroDoc(); doc.setPeerId(0); const text = doc.getText("text"); text.insert(0, "0"); doc.commit(); const v0 = doc.frontiers(); - const docB = new Loro(); + const docB = new LoroDoc(); docB.setPeerId(1); docB.import(doc.exportFrom()); expect(docB.cmpWithFrontiers(v0)).toBe(0); @@ -34,9 +34,9 @@ describe("Frontiers", () => { }); it("cmp frontiers", () => { - const doc1 = new Loro(); + const doc1 = new LoroDoc(); doc1.setPeerId(1); - const doc2 = new Loro(); + const doc2 = new LoroDoc(); doc2.setPeerId(2n); doc1.getText("text").insert(0, "01234"); @@ -85,7 +85,7 @@ describe("Frontiers", () => { }); it("peer id repr should be consistent", () => { - const doc = new Loro(); + const doc = new LoroDoc(); const id = doc.peerIdStr; doc.getText("text").insert(0, "hello"); doc.commit(); @@ -105,9 +105,9 @@ it("peer id repr should be consistent", () => { }); describe("Version", () => { - const a = new Loro(); + const a = new LoroDoc(); a.setPeerId(0n); - const b = new Loro(); + const b = new LoroDoc(); b.setPeerId(1n); a.getText("text").insert(0, "ha"); b.getText("text").insert(0, "yo"); @@ -155,7 +155,7 @@ describe("Version", () => { }); it("get import blob metadata", () => { - const doc0 = new Loro(); + const doc0 = new LoroDoc(); doc0.setPeerId(0n); const text = doc0.getText("text"); text.insert(0, "0"); @@ -172,7 +172,7 @@ it("get import blob metadata", () => { expect(meta.startFrontiers.length).toBe(0); } - const doc1 = new Loro(); + const doc1 = new LoroDoc(); doc1.setPeerId(1); doc1.getText("text").insert(0, "123"); doc1.import(doc0.exportFrom()); From 07671ea9fd073d6d32f4969667ecd5a58e184f90 Mon Sep 17 00:00:00 2001 From: Leon Zhao Date: Mon, 9 Sep 2024 16:16:02 +0800 Subject: [PATCH 3/3] feat: add old parent and old index in tree diff (#452) * feat: add old parent in tree diff * chore: enable ci * feat: add old_index to tree diff * fix: new fractional index config * fix: cargo fix * fix: add FractionalIndexNotEnabled error * fix: move config to tree state * fix: error string --------- Co-authored-by: Zixuan Chen --- .github/workflows/rust.yml | 2 +- crates/fuzz/fuzz/Cargo.lock | 1 + crates/fuzz/src/actor.rs | 6 +- crates/fuzz/src/container/tree.rs | 15 +- crates/loro-common/src/error.rs | 2 + crates/loro-common/src/lib.rs | 2 +- crates/loro-internal/benches/tree.rs | 31 ++- crates/loro-internal/examples/tree.rs | 16 +- crates/loro-internal/src/configure.rs | 27 +-- crates/loro-internal/src/delta/tree.rs | 11 +- crates/loro-internal/src/diff_calc/tree.rs | 2 +- crates/loro-internal/src/handler.rs | 17 +- crates/loro-internal/src/handler/tree.rs | 220 +++++++++++------- crates/loro-internal/src/lib.rs | 1 + crates/loro-internal/src/loro.rs | 18 +- crates/loro-internal/src/oplog.rs | 2 +- .../loro-internal/src/oplog/change_store.rs | 10 +- crates/loro-internal/src/state.rs | 31 +-- .../src/state/container_store.rs | 14 +- .../src/state/movable_list_state.rs | 3 +- crates/loro-internal/src/state/tree_state.rs | 205 ++++++++++------ crates/loro-internal/src/utils/kv_wrapper.rs | 8 +- crates/loro-internal/src/value.rs | 40 +++- crates/loro-internal/tests/test.rs | 15 +- crates/loro-internal/tests/tree.rs | 25 ++ crates/loro-wasm/src/lib.rs | 63 +++-- crates/loro/src/change_meta.rs | 2 +- crates/loro/src/lib.rs | 99 ++++---- loro-js/src/index.ts | 8 +- loro-js/tests/tree.test.ts | 24 +- 30 files changed, 549 insertions(+), 371 deletions(-) create mode 100644 crates/loro-internal/tests/tree.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 13d71fc7..3ffed61b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -4,7 +4,7 @@ on: push: branches: ["main"] pull_request: - branches: ["main"] + branches: ["main", "dev"] types: [opened, synchronize, reopened, ready_for_review] env: diff --git a/crates/fuzz/fuzz/Cargo.lock b/crates/fuzz/fuzz/Cargo.lock index eccb7525..ef6e54b2 100644 --- a/crates/fuzz/fuzz/Cargo.lock +++ b/crates/fuzz/fuzz/Cargo.lock @@ -780,6 +780,7 @@ name = "loro_fractional_index" version = "0.16.2" dependencies = [ "imbl", + "once_cell", "rand", "serde", "smallvec", diff --git a/crates/fuzz/src/actor.rs b/crates/fuzz/src/actor.rs index b9c32759..7aaf106c 100644 --- a/crates/fuzz/src/actor.rs +++ b/crates/fuzz/src/actor.rs @@ -24,8 +24,6 @@ use super::{ container::MapActor, }; -const DEFAULT_WITH_FRACTIONAL_INDEX: bool = false; - #[derive(Debug)] pub struct Undo { pub undo: UndoManager, @@ -47,7 +45,6 @@ impl Actor { pub fn new(id: PeerID) -> Self { let loro = LoroDoc::new(); loro.set_peer_id(id).unwrap(); - loro.set_with_fractional_index(DEFAULT_WITH_FRACTIONAL_INDEX); let undo = UndoManager::new(&loro); let tracker = Arc::new(Mutex::new(ContainerTracker::Map(MapTracker::empty( ContainerID::new_root("sys:root", ContainerType::Map), @@ -122,6 +119,9 @@ impl Actor { } if let Some(idx) = idx { + if let Container::Tree(tree) = &idx { + tree.set_enable_fractional_index(0); + } self.add_new_container(idx); } } diff --git a/crates/fuzz/src/container/tree.rs b/crates/fuzz/src/container/tree.rs index 5d9b39c5..83da2058 100644 --- a/crates/fuzz/src/container/tree.rs +++ b/crates/fuzz/src/container/tree.rs @@ -10,7 +10,6 @@ use loro::{ event::Diff, Container, ContainerID, ContainerType, LoroDoc, LoroError, LoroTree, LoroValue, TreeExternalDiff, TreeID, }; -use tracing::{debug, trace}; use crate::{ actions::{Actionable, FromGenericAction, GenericAction}, @@ -117,6 +116,7 @@ impl TreeActor { ); let root = loro.get_tree("tree"); + root.set_enable_fractional_index(0); Self { loro, containers: vec![root], @@ -184,7 +184,7 @@ impl Actionable for TreeAction { } *parent = (nodes[parent_idx].peer, nodes[parent_idx].counter); *index %= tree - .children_num(Some(TreeID::new(parent.0, parent.1))) + .children_num(TreeID::new(parent.0, parent.1)) .unwrap_or(0) + 1; } @@ -426,7 +426,7 @@ impl ApplyDiff for TreeTracker { index, position, } => { - self.create_node(target, parent, position.to_string(), index); + self.create_node(target, &parent.tree_id(), position.to_string(), index); } TreeExternalDiff::Delete { .. } => { let node = self.find_node_by_id(target).unwrap(); @@ -442,9 +442,10 @@ impl ApplyDiff for TreeTracker { parent, index, position, + .. } => { let Some(node) = self.find_node_by_id(target) else { - self.create_node(target, parent, position.to_string(), index); + self.create_node(target, &parent.tree_id(), position.to_string(), index); continue; }; @@ -456,10 +457,10 @@ impl ApplyDiff for TreeTracker { let index = self.tree.iter().position(|n| n.id == target).unwrap(); self.tree.remove(index) }; - node.parent = *parent; + node.parent = parent.tree_id(); node.position = position.to_string(); - if let Some(parent) = parent { - let parent = self.find_node_by_id_mut(*parent).unwrap(); + if let Some(parent) = parent.tree_id() { + let parent = self.find_node_by_id_mut(parent).unwrap(); parent.children.insert(*index, node); } else { if self.find_node_by_id_mut(target).is_some() { diff --git a/crates/loro-common/src/error.rs b/crates/loro-common/src/error.rs index 5fb95463..da727337 100644 --- a/crates/loro-common/src/error.rs +++ b/crates/loro-common/src/error.rs @@ -88,6 +88,8 @@ pub enum LoroTreeError { TreeNodeNotExist(TreeID), #[error("The index({index}) should be <= the length of children ({len})")] IndexOutOfBound { len: usize, index: usize }, + #[error("Fractional index is not enabled, you should enable it first by `LoroTree::set_enable_fractional_index`")] + FractionalIndexNotEnabled, } #[cfg(feature = "wasm")] diff --git a/crates/loro-common/src/lib.rs b/crates/loro-common/src/lib.rs index 891657cf..3b1bbbfd 100644 --- a/crates/loro-common/src/lib.rs +++ b/crates/loro-common/src/lib.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, io::Write, str::Bytes, sync::Arc}; +use std::{fmt::Display, io::Write, sync::Arc}; use arbitrary::Arbitrary; use enum_as_inner::EnumAsInner; diff --git a/crates/loro-internal/benches/tree.rs b/crates/loro-internal/benches/tree.rs index a0e94f35..94581a04 100644 --- a/crates/loro-internal/benches/tree.rs +++ b/crates/loro-internal/benches/tree.rs @@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; mod tree { use super::*; use criterion::{AxisScale, BenchmarkId, PlotConfiguration}; - use loro_internal::LoroDoc; + use loro_internal::{LoroDoc, TreeParentId}; use rand::{rngs::StdRng, Rng}; pub fn tree_move(c: &mut Criterion) { @@ -22,7 +22,7 @@ mod tree { let loro = LoroDoc::new_auto_commit(); let tree = loro.get_tree("tree"); for idx in 0..*i { - tree.create_at(None, idx as usize).unwrap(); + tree.create_at(TreeParentId::Root, idx as usize).unwrap(); } }) }, @@ -39,15 +39,16 @@ mod tree { let mut ids = vec![]; for _ in 0..SIZE { let pos = rng.gen::() % (ids.len() + 1); - ids.push(tree.create_at(None, pos).unwrap()); + ids.push(tree.create_at(TreeParentId::Root, pos).unwrap()); } b.iter(|| { for _ in 0..*i { - tree.create_at(None, 0).unwrap(); + tree.create_at(TreeParentId::Root, 0).unwrap(); let i = rng.gen::() % SIZE; let j = rng.gen::() % SIZE; - tree.mov(ids[i], ids[j]).unwrap_or_default(); + tree.mov(ids[i], TreeParentId::Node(ids[j])) + .unwrap_or_default(); } }) }, @@ -62,14 +63,14 @@ mod tree { let mut versions = vec![]; let size = 1000; for _ in 0..size { - ids.push(tree.create(None).unwrap()) + ids.push(tree.create(TreeParentId::Root).unwrap()) } let mut rng: StdRng = rand::SeedableRng::seed_from_u64(0); let mut n = 1000; while n > 0 { let i = rng.gen::() % size; let j = rng.gen::() % size; - if tree.mov(ids[i], ids[j]).is_ok() { + if tree.mov(ids[i], TreeParentId::Node(ids[j])).is_ok() { versions.push(loro.oplog_frontiers()); n -= 1; }; @@ -90,11 +91,13 @@ mod tree { let tree = loro.get_tree("tree"); let mut ids = vec![]; let mut versions = vec![]; - let id1 = tree.create(None).unwrap(); + let id1 = tree.create(TreeParentId::Root).unwrap(); ids.push(id1); versions.push(loro.oplog_frontiers()); for _ in 1..depth { - let id = tree.create(*ids.last().unwrap()).unwrap(); + let id = tree + .create(TreeParentId::Node(*ids.last().unwrap())) + .unwrap(); ids.push(id); versions.push(loro.oplog_frontiers()); } @@ -118,7 +121,7 @@ mod tree { let mut ids = vec![]; let size = 1000; for _ in 0..size { - ids.push(tree_a.create(None).unwrap()) + ids.push(tree_a.create(TreeParentId::Root).unwrap()) } doc_b.import(&doc_a.export_snapshot()).unwrap(); let mut rng: StdRng = rand::SeedableRng::seed_from_u64(0); @@ -128,10 +131,14 @@ mod tree { let i = rng.gen::() % size; let j = rng.gen::() % size; if t % 2 == 0 { - tree_a.mov(ids[i], ids[j]).unwrap_or_default(); + tree_a + .mov(ids[i], TreeParentId::Node(ids[j])) + .unwrap_or_default(); doc_b.import(&doc_a.export_from(&doc_b.oplog_vv())).unwrap(); } else { - tree_b.mov(ids[i], ids[j]).unwrap_or_default(); + tree_b + .mov(ids[i], TreeParentId::Node(ids[j])) + .unwrap_or_default(); doc_a.import(&doc_b.export_from(&doc_a.oplog_vv())).unwrap(); } } diff --git a/crates/loro-internal/examples/tree.rs b/crates/loro-internal/examples/tree.rs index 309ef7d3..e89b826a 100644 --- a/crates/loro-internal/examples/tree.rs +++ b/crates/loro-internal/examples/tree.rs @@ -1,6 +1,6 @@ use std::time::Instant; -use loro_internal::LoroDoc; +use loro_internal::{LoroDoc, TreeParentId}; use rand::{rngs::StdRng, Rng}; #[allow(unused)] @@ -10,11 +10,13 @@ fn checkout() { let tree = loro.get_tree("tree"); let mut ids = vec![]; let mut versions = vec![]; - let id1 = tree.create_at(None, 0).unwrap(); + let id1 = tree.create_at(TreeParentId::Root, 0).unwrap(); ids.push(id1); versions.push(loro.oplog_frontiers()); for _ in 1..depth { - let id = tree.create_at(*ids.last().unwrap(), 0).unwrap(); + let id = tree + .create_at(TreeParentId::Node(*ids.last().unwrap()), 0) + .unwrap(); ids.push(id); versions.push(loro.oplog_frontiers()); } @@ -34,7 +36,7 @@ fn mov() { let mut ids = vec![]; let size = 10000; for _ in 0..size { - ids.push(tree.create_at(None, 0).unwrap()) + ids.push(tree.create_at(TreeParentId::Root, 0).unwrap()) } let mut rng: StdRng = rand::SeedableRng::seed_from_u64(0); let n = 100000; @@ -42,8 +44,8 @@ fn mov() { for _ in 0..n { let i = rng.gen::() % size; let j = rng.gen::() % size; - let children_num = tree.children_num(Some(ids[j])).unwrap_or(0); - tree.move_to(ids[i], ids[j], children_num) + let children_num = tree.children_num(&TreeParentId::Node(ids[j])).unwrap_or(0); + tree.move_to(ids[i], TreeParentId::Node(ids[j]), children_num) .unwrap_or_default(); } println!("encode snapshot size {:?}", loro.export_snapshot().len()); @@ -59,7 +61,7 @@ fn create() { let loro = LoroDoc::default(); let tree = loro.get_tree("tree"); for _ in 0..size { - tree.create_at(None, 0).unwrap(); + tree.create_at(TreeParentId::Root, 0).unwrap(); } println!("encode snapshot size {:?}\n", loro.export_snapshot().len()); println!( diff --git a/crates/loro-internal/src/configure.rs b/crates/loro-internal/src/configure.rs index 69364823..e46f102b 100644 --- a/crates/loro-internal/src/configure.rs +++ b/crates/loro-internal/src/configure.rs @@ -5,11 +5,6 @@ pub struct Configure { pub(crate) text_style_config: Arc>, record_timestamp: Arc, pub(crate) merge_interval: Arc, - /// Whether the tree has fractional index. `false` by default. If false, the fractional index is always [`FractionalIndex::default`] and - /// `tree_position_jitter` is not used. - pub(crate) tree_with_fractional_index: Arc, - /// do not use `jitter` by default - pub(crate) tree_position_jitter: Arc, } impl Default for Configure { @@ -18,8 +13,6 @@ impl Default for Configure { text_style_config: Arc::new(RwLock::new(StyleConfigMap::default_rich_text_config())), record_timestamp: Arc::new(AtomicBool::new(false)), merge_interval: Arc::new(AtomicI64::new(1000 * 1000)), - tree_position_jitter: Arc::new(AtomicU8::new(0)), - tree_with_fractional_index: Arc::new(AtomicBool::new(false)), } } } @@ -38,14 +31,6 @@ impl Configure { self.merge_interval .load(std::sync::atomic::Ordering::Relaxed), )), - tree_position_jitter: Arc::new(AtomicU8::new( - self.tree_position_jitter - .load(std::sync::atomic::Ordering::Relaxed), - )), - tree_with_fractional_index: Arc::new(AtomicBool::new( - self.tree_with_fractional_index - .load(std::sync::atomic::Ordering::Relaxed), - )), } } @@ -63,16 +48,6 @@ impl Configure { .store(record, std::sync::atomic::Ordering::Relaxed); } - pub fn set_with_fractional_index(&self, with_fractional_index: bool) { - self.tree_with_fractional_index - .store(with_fractional_index, std::sync::atomic::Ordering::Relaxed); - } - - pub fn set_fractional_index_jitter(&self, jitter: u8) { - self.tree_position_jitter - .store(jitter, std::sync::atomic::Ordering::Relaxed); - } - pub fn merge_interval(&self) -> i64 { self.merge_interval .load(std::sync::atomic::Ordering::Relaxed) @@ -90,7 +65,7 @@ pub struct DefaultRandom; #[cfg(test)] use std::sync::atomic::AtomicU64; use std::sync::{ - atomic::{AtomicBool, AtomicI64, AtomicU8}, + atomic::{AtomicBool, AtomicI64}, Arc, RwLock, }; #[cfg(test)] diff --git a/crates/loro-internal/src/delta/tree.rs b/crates/loro-internal/src/delta/tree.rs index 82974aa5..53722dc5 100644 --- a/crates/loro-internal/src/delta/tree.rs +++ b/crates/loro-internal/src/delta/tree.rs @@ -21,16 +21,21 @@ pub struct TreeDiffItem { #[derive(Debug, Clone, PartialEq)] pub enum TreeExternalDiff { Create { - parent: Option, + parent: TreeParentId, index: usize, position: FractionalIndex, }, Move { - parent: Option, + parent: TreeParentId, index: usize, position: FractionalIndex, + old_parent: TreeParentId, + old_index: usize, + }, + Delete { + old_parent: TreeParentId, + old_index: usize, }, - Delete, } impl TreeDiff { diff --git a/crates/loro-internal/src/diff_calc/tree.rs b/crates/loro-internal/src/diff_calc/tree.rs index d882d21a..3e15f8bb 100644 --- a/crates/loro-internal/src/diff_calc/tree.rs +++ b/crates/loro-internal/src/diff_calc/tree.rs @@ -50,7 +50,7 @@ impl DiffCalculatorTrait for TreeDiffCalculator { fn apply_change( &mut self, - _oplog: &OpLog, + oplog: &OpLog, op: crate::op::RichOp, _vv: Option<&crate::VersionVector>, ) { diff --git a/crates/loro-internal/src/handler.rs b/crates/loro-internal/src/handler.rs index 4018a998..d2804805 100644 --- a/crates/loro-internal/src/handler.rs +++ b/crates/loro-internal/src/handler.rs @@ -11,7 +11,7 @@ use crate::{ diff::{myers_diff, DiffHandler, OperateProxy}, event::{Diff, TextDiffItem}, op::ListSlice, - state::{ContainerState, IndexType, State}, + state::{ContainerState, IndexType, State, TreeParentId}, txn::EventHint, utils::{string_slice::StringSlice, utf16::count_utf16_len}, }; @@ -1171,7 +1171,7 @@ impl Handler { index: _, position, } => { - if let Some(p) = parent.as_mut() { + if let TreeParentId::Node(p) = &mut parent { remap_tree_id(p, container_remap) } remap_tree_id(&mut target, container_remap); @@ -1206,14 +1206,16 @@ impl Handler { mut parent, index: _, position, + old_parent: _, + old_index: _, } => { - if let Some(p) = parent.as_mut() { + if let TreeParentId::Node(p) = &mut parent { remap_tree_id(p, container_remap) } remap_tree_id(&mut target, container_remap); x.move_at_with_target_for_apply_diff(parent, position, target)?; } - TreeExternalDiff::Delete => { + TreeExternalDiff::Delete { .. } => { remap_tree_id(&mut target, container_remap); if x.contains(target) { x.delete(target)?; @@ -3939,6 +3941,7 @@ mod test { use super::{HandlerTrait, TextDelta}; use crate::loro::LoroDoc; + use crate::state::TreeParentId; use crate::version::Frontiers; use crate::{fx_map, ToJson}; use loro_common::ID; @@ -4104,7 +4107,7 @@ mod test { loro.set_peer_id(1).unwrap(); let tree = loro.get_tree("root"); let id = loro - .with_txn(|txn| tree.create_with_txn(txn, None, 0)) + .with_txn(|txn| tree.create_with_txn(txn, TreeParentId::Root, 0)) .unwrap(); loro.with_txn(|txn| { let meta = tree.get_meta(id)?; @@ -4134,11 +4137,11 @@ mod test { let tree = loro.get_tree("root"); let text = loro.get_text("text"); loro.with_txn(|txn| { - let id = tree.create_with_txn(txn, None, 0)?; + let id = tree.create_with_txn(txn, TreeParentId::Root, 0)?; let meta = tree.get_meta(id)?; meta.insert_with_txn(txn, "a", 1.into())?; text.insert_with_txn(txn, 0, "abc")?; - let _id2 = tree.create_with_txn(txn, None, 0)?; + let _id2 = tree.create_with_txn(txn, TreeParentId::Root, 0)?; meta.insert_with_txn(txn, "b", 2.into())?; Ok(id) }) diff --git a/crates/loro-internal/src/handler/tree.rs b/crates/loro-internal/src/handler/tree.rs index 4d772c8f..b68a214f 100644 --- a/crates/loro-internal/src/handler/tree.rs +++ b/crates/loro-internal/src/handler/tree.rs @@ -93,8 +93,8 @@ impl TreeInner { self.children_links.get(&parent).map(|x| x.len()) } - fn is_parent(&self, target: TreeID, parent: Option) -> bool { - self.parent_links.get(&target) == Some(&parent) + fn is_parent(&self, target: &TreeID, parent: &Option) -> bool { + self.parent_links.get(target) == Some(parent) } fn get_index_by_tree_id(&self, target: &TreeID) -> Option { @@ -164,7 +164,9 @@ impl HandlerTrait for TreeHandler { let mut q = children .map(|c| { VecDeque::from_iter( - c.iter().enumerate().zip(std::iter::repeat(None::)), + c.iter() + .enumerate() + .zip(std::iter::repeat(TreeParentId::Root)), ) }) .unwrap_or_default(); @@ -179,7 +181,7 @@ impl HandlerTrait for TreeHandler { if let Some(children) = t.value.children_links.get(&Some(*target)) { for (idx, child) in children.iter().enumerate() { - q.push_back(((idx, child), Some(real_id))); + q.push_back(((idx, child), TreeParentId::Node(real_id))); } } } @@ -290,27 +292,25 @@ impl TreeHandler { crate::op::RawOpContent::Tree(Arc::new(TreeOp::Delete { target })), EventHint::Tree(smallvec![TreeDiffItem { target, - action: TreeExternalDiff::Delete, + action: TreeExternalDiff::Delete { + old_parent: self.get_node_parent(&target).unwrap(), + old_index: self.get_index_by_tree_id(&target).unwrap(), + }, }]), &inner.state, ) } - pub fn create>>(&self, parent: T) -> LoroResult { - let parent = parent.into(); - let index: usize = self.children_num(parent).unwrap_or(0); + pub fn create(&self, parent: TreeParentId) -> LoroResult { + let index: usize = self.children_num(&parent).unwrap_or(0); self.create_at(parent, index) } - pub fn create_at>>( - &self, - parent: T, - index: usize, - ) -> LoroResult { + pub fn create_at(&self, parent: TreeParentId, index: usize) -> LoroResult { match &self.inner { MaybeDetached::Detached(t) => { let t = &mut t.try_lock().unwrap().value; - Ok(t.create(parent.into(), index)) + Ok(t.create(parent.tree_id(), index)) } MaybeDetached::Attached(a) => { a.with_txn(|txn| self.create_with_txn(txn, parent, index)) @@ -321,23 +321,33 @@ impl TreeHandler { /// For undo/redo, Specify the TreeID of the created node pub(crate) fn create_at_with_target_for_apply_diff( &self, - parent: Option, + parent: TreeParentId, position: FractionalIndex, target: TreeID, ) -> LoroResult { let MaybeDetached::Attached(a) = &self.inner else { unreachable!(); }; + if let Some(p) = self.get_node_parent(&target) { if p == parent { return Ok(false); // If parent is deleted, we need to create the node, so this op from move_apply_diff - } else if !p.is_some_and(|p| !self.contains(p)) { - return self.move_at_with_target_for_apply_diff(parent, position, target); + } + match p { + TreeParentId::Node(p) => { + if self.contains(p) { + return self.move_at_with_target_for_apply_diff(parent, position, target); + } + } + TreeParentId::Root => { + return self.move_at_with_target_for_apply_diff(parent, position, target); + } + TreeParentId::Deleted | TreeParentId::Unexist => {} } } - let with_event = !parent.is_some_and(|p| !self.contains(p)); + let with_event = !parent.tree_id().is_some_and(|p| !self.contains(p)); if !with_event { return Ok(false); } @@ -349,7 +359,7 @@ impl TreeHandler { let index = self .get_index_by_fractional_index( - parent, + &parent, &NodePosition { position: position.clone(), idlp: self.next_idlp(), @@ -365,7 +375,7 @@ impl TreeHandler { inner.container_idx, crate::op::RawOpContent::Tree(Arc::new(TreeOp::Create { target, - parent, + parent: parent.tree_id(), position: position.clone(), })), EventHint::Tree(smallvec![TreeDiffItem { @@ -379,11 +389,13 @@ impl TreeHandler { &inner.state, )?; - Ok(self.children(Some(target)).unwrap_or_default()) + Ok(self + .children(&TreeParentId::Node(target)) + .unwrap_or_default()) })?; for child in children { let position = self.get_position_by_tree_id(&child).unwrap(); - self.create_at_with_target_for_apply_diff(Some(target), position, child)?; + self.create_at_with_target_for_apply_diff(TreeParentId::Node(target), position, child)?; } Ok(true) } @@ -391,7 +403,7 @@ impl TreeHandler { /// For undo/redo, Specify the TreeID of the created node pub(crate) fn move_at_with_target_for_apply_diff( &self, - parent: Option, + parent: TreeParentId, position: FractionalIndex, target: TreeID, ) -> LoroResult { @@ -412,14 +424,14 @@ impl TreeHandler { let index = self .get_index_by_fractional_index( - parent, + &parent, &NodePosition { position: position.clone(), idlp: self.next_idlp(), }, ) .unwrap_or(0); - let with_event = !parent.is_some_and(|p| !self.contains(p)); + let with_event = !parent.tree_id().is_some_and(|p| !self.contains(p)); if !with_event { return Ok(false); @@ -436,7 +448,7 @@ impl TreeHandler { inner.container_idx, crate::op::RawOpContent::Tree(Arc::new(TreeOp::Move { target, - parent, + parent: parent.tree_id(), position: position.clone(), })), EventHint::Tree(smallvec![TreeDiffItem { @@ -445,6 +457,9 @@ impl TreeHandler { parent, index, position: position.clone(), + // the old parent should be exist, so we can unwrap + old_parent: self.get_node_parent(&target).unwrap(), + old_index: self.get_index_by_tree_id(&target).unwrap(), }, }]), &inner.state, @@ -453,17 +468,16 @@ impl TreeHandler { Ok(true) } - pub(crate) fn create_with_txn>>( + pub(crate) fn create_with_txn( &self, txn: &mut Transaction, - parent: T, + parent: TreeParentId, index: usize, ) -> LoroResult { let inner = self.inner.try_attached_state()?; - let parent: Option = parent.into(); let target = TreeID::from_id(txn.next_id()); - match self.generate_position_at(&target, parent, index) { + match self.generate_position_at(&target, &parent, index) { FractionalIndexGenResult::Ok(position) => { self.create_with_position(inner, txn, target, parent, index, position) } @@ -473,26 +487,25 @@ impl TreeHandler { self.create_with_position(inner, txn, id, parent, index, position)?; continue; } - self.mov_with_position(inner, txn, id, parent, index + i, position)?; + self.mov_with_position(inner, txn, id, parent, index + i, position, index + i)?; } Ok(target) } } } - pub fn mov>>(&self, target: TreeID, parent: T) -> LoroResult<()> { - let parent = parent.into(); + pub fn mov(&self, target: TreeID, parent: TreeParentId) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(_) => { - let mut index: usize = self.children_num(parent).unwrap_or(0); - if self.is_parent(target, parent) { + let mut index: usize = self.children_num(&parent).unwrap_or(0); + if self.is_parent(&target, &parent) { index -= 1; } self.move_to(target, parent, index) } MaybeDetached::Attached(a) => { - let mut index = self.children_num(parent).unwrap_or(0); - if self.is_parent(target, parent) { + let mut index = self.children_num(&parent).unwrap_or(0); + if self.is_parent(&target, &parent) { index -= 1; } a.with_txn(|txn| self.mov_with_txn(txn, target, parent, index)) @@ -501,11 +514,11 @@ impl TreeHandler { } pub fn mov_after(&self, target: TreeID, other: TreeID) -> LoroResult<()> { - let parent: Option = self + let parent = self .get_node_parent(&other) .ok_or(LoroTreeError::TreeNodeNotExist(other))?; let mut index = self.get_index_by_tree_id(&other).unwrap() + 1; - if self.is_parent(target, parent) && self.get_index_by_tree_id(&target).unwrap() < index { + if self.is_parent(&target, &parent) && self.get_index_by_tree_id(&target).unwrap() < index { index -= 1; } self.move_to(target, parent, index) @@ -516,7 +529,7 @@ impl TreeHandler { .get_node_parent(&other) .ok_or(LoroTreeError::TreeNodeNotExist(other))?; let mut index = self.get_index_by_tree_id(&other).unwrap(); - if self.is_parent(target, parent) + if self.is_parent(&target, &parent) && index > 1 && self.get_index_by_tree_id(&target).unwrap() < index { @@ -525,16 +538,11 @@ impl TreeHandler { self.move_to(target, parent, index) } - pub fn move_to>>( - &self, - target: TreeID, - parent: T, - index: usize, - ) -> LoroResult<()> { + pub fn move_to(&self, target: TreeID, parent: TreeParentId, index: usize) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(t) => { let mut t = t.try_lock().unwrap(); - t.value.mov(target, parent.into(), index) + t.value.mov(target, parent.tree_id(), index) } MaybeDetached::Attached(a) => { a.with_txn(|txn| self.mov_with_txn(txn, target, parent, index)) @@ -542,19 +550,18 @@ impl TreeHandler { } } - pub(crate) fn mov_with_txn>>( + pub(crate) fn mov_with_txn( &self, txn: &mut Transaction, target: TreeID, - parent: T, + parent: TreeParentId, index: usize, ) -> LoroResult<()> { - let parent = parent.into(); let inner = self.inner.try_attached_state()?; - let mut children_len = self.children_num(parent).unwrap_or(0); + let mut children_len = self.children_num(&parent).unwrap_or(0); let mut already_in_parent = false; // check the input is valid - if self.is_parent(target, parent) { + if self.is_parent(&target, &parent) { // If the position after moving is same as the current position , do nothing if let Some(current_index) = self.get_index_by_tree_id(&target) { if current_index == index { @@ -573,17 +580,18 @@ impl TreeHandler { } .into()); } + let old_index = self.get_index_by_tree_id(&target).unwrap(); if already_in_parent { - self.delete_position(parent, target); + self.delete_position(&parent, &target); } - match self.generate_position_at(&target, parent, index) { + match self.generate_position_at(&target, &parent, index) { FractionalIndexGenResult::Ok(position) => { - self.mov_with_position(inner, txn, target, parent, index, position) + self.mov_with_position(inner, txn, target, parent, index, position, old_index) } FractionalIndexGenResult::Rearrange(ids) => { for (i, (id, position)) in ids.into_iter().enumerate() { - self.mov_with_position(inner, txn, id, parent, index + i, position)?; + self.mov_with_position(inner, txn, id, parent, index + i, position, old_index)?; } Ok(()) } @@ -596,7 +604,7 @@ impl TreeHandler { inner: &BasicHandler, txn: &mut Transaction, tree_id: TreeID, - parent: Option, + parent: TreeParentId, index: usize, position: FractionalIndex, ) -> LoroResult { @@ -604,7 +612,7 @@ impl TreeHandler { inner.container_idx, crate::op::RawOpContent::Tree(Arc::new(TreeOp::Create { target: tree_id, - parent, + parent: parent.tree_id(), position: position.clone(), })), EventHint::Tree(smallvec![TreeDiffItem { @@ -626,15 +634,16 @@ impl TreeHandler { inner: &BasicHandler, txn: &mut Transaction, target: TreeID, - parent: Option, + parent: TreeParentId, index: usize, position: FractionalIndex, + old_index: usize, ) -> LoroResult<()> { txn.apply_local_op( inner.container_idx, crate::op::RawOpContent::Tree(Arc::new(TreeOp::Move { target, - parent, + parent: parent.tree_id(), position: position.clone(), })), EventHint::Tree(smallvec![TreeDiffItem { @@ -643,6 +652,8 @@ impl TreeHandler { parent, index, position, + old_parent: self.get_node_parent(&target).unwrap(), + old_index, }, }]), &inner.state, @@ -671,46 +682,42 @@ impl TreeHandler { } /// Get the parent of the node, if the node is deleted or does not exist, return None - pub fn get_node_parent(&self, target: &TreeID) -> Option> { + pub fn get_node_parent(&self, target: &TreeID) -> Option { match &self.inner { MaybeDetached::Detached(t) => { let t = t.try_lock().unwrap(); - t.value.get_parent(target) + t.value.get_parent(target).map(TreeParentId::from) } MaybeDetached::Attached(a) => a.with_state(|state| { let a = state.as_tree_state().unwrap(); - match a.parent(target) { - TreeParentId::Root => Some(None), - TreeParentId::Node(parent) => Some(Some(parent)), - TreeParentId::Deleted | TreeParentId::Unexist => None, - } + a.parent(target) }), } } // TODO: iterator - pub fn children(&self, parent: Option) -> Option> { + pub fn children(&self, parent: &TreeParentId) -> Option> { match &self.inner { MaybeDetached::Detached(t) => { let t = t.try_lock().unwrap(); - t.value.get_children(parent) + t.value.get_children(parent.tree_id()) } MaybeDetached::Attached(a) => a.with_state(|state| { let a = state.as_tree_state().unwrap(); - a.children(&TreeParentId::from(parent)) + a.children(parent) }), } } - pub fn children_num(&self, parent: Option) -> Option { + pub fn children_num(&self, parent: &TreeParentId) -> Option { match &self.inner { MaybeDetached::Detached(t) => { let t = t.try_lock().unwrap(); - t.value.children_num(parent) + t.value.children_num(parent.tree_id()) } MaybeDetached::Attached(a) => a.with_state(|state| { let a = state.as_tree_state().unwrap(); - a.children_num(&TreeParentId::from(parent)) + a.children_num(parent) }), } } @@ -741,15 +748,15 @@ impl TreeHandler { } } - pub fn is_parent(&self, target: TreeID, parent: Option) -> bool { + pub fn is_parent(&self, target: &TreeID, parent: &TreeParentId) -> bool { match &self.inner { MaybeDetached::Detached(t) => { let t = t.try_lock().unwrap(); - t.value.is_parent(target, parent) + t.value.is_parent(target, &parent.tree_id()) } MaybeDetached::Attached(a) => a.with_state(|state| { let a = state.as_tree_state().unwrap(); - a.is_parent(&TreeParentId::from(parent), &target) + a.is_parent(parent, target) }), } } @@ -768,7 +775,7 @@ impl TreeHandler { } pub fn roots(&self) -> Vec { - self.children(None).unwrap_or_default() + self.children(&TreeParentId::Root).unwrap_or_default() } #[allow(non_snake_case)] @@ -787,7 +794,7 @@ impl TreeHandler { fn generate_position_at( &self, target: &TreeID, - parent: Option, + parent: &TreeParentId, index: usize, ) -> FractionalIndexGenResult { let MaybeDetached::Attached(a) = &self.inner else { @@ -795,7 +802,7 @@ impl TreeHandler { }; a.with_state(|state| { let a = state.as_tree_state_mut().unwrap(); - a.generate_position_at(target, &TreeParentId::from(parent), index) + a.generate_position_at(target, parent, index) }) } @@ -825,20 +832,20 @@ impl TreeHandler { } } - fn delete_position(&self, parent: Option, target: TreeID) { + fn delete_position(&self, parent: &TreeParentId, target: &TreeID) { let MaybeDetached::Attached(a) = &self.inner else { unreachable!() }; a.with_state(|state| { let a = state.as_tree_state_mut().unwrap(); - a.delete_position(&TreeParentId::from(parent), target) + a.delete_position(parent, &target) }) } // use for apply diff pub(crate) fn get_index_by_fractional_index( &self, - parent: Option, + parent: &TreeParentId, node_position: &NodePosition, ) -> Option { match &self.inner { @@ -847,7 +854,7 @@ impl TreeHandler { } MaybeDetached::Attached(a) => a.with_state(|state| { let a = state.as_tree_state().unwrap(); - a.get_index_by_position(&TreeParentId::from(parent), node_position) + a.get_index_by_position(parent, node_position) }), } } @@ -860,4 +867,51 @@ impl TreeHandler { MaybeDetached::Attached(a) => a.with_txn(|txn| Ok(txn.next_idlp())).unwrap(), } } + + pub fn is_fractional_index_enabled(&self) -> bool { + match &self.inner { + MaybeDetached::Detached(_) => { + unreachable!() + } + MaybeDetached::Attached(a) => a.with_state(|state| { + let a = state.as_tree_state().unwrap(); + a.is_fractional_index_enabled() + }), + } + } + + /// Set whether to generate fractional index for Tree Position. The LoroDoc is set to disable fractional index by default. + /// + /// The jitter is used to avoid conflicts when multiple users are creating the node at the same position. + /// value 0 is default, which means no jitter, any value larger than 0 will enable jitter. + /// + /// Generally speaking, jitter will affect the growth rate of document size. + /// [Read more about it](https://www.loro.dev/blog/movable-tree#implementation-and-encoding-size) + pub fn set_enable_fractional_index(&self, jitter: u8) { + match &self.inner { + MaybeDetached::Detached(_) => { + unreachable!() + } + MaybeDetached::Attached(a) => a.with_state(|state| { + let a = state.as_tree_state_mut().unwrap(); + a.enable_generate_fractional_index(jitter); + }), + } + } + + /// Disable the fractional index generation for Tree Position when + /// you don't need the Tree's siblings to be sorted. The fractional index will be always default. + /// + /// The LoroDoc is set to disable fractional index by default. + pub fn set_disable_fractional_index(&self) { + match &self.inner { + MaybeDetached::Detached(_) => { + unreachable!() + } + MaybeDetached::Attached(a) => a.with_state(|state| { + let a = state.as_tree_state_mut().unwrap(); + a.disable_generate_fractional_index(); + }), + } + } } diff --git a/crates/loro-internal/src/lib.rs b/crates/loro-internal/src/lib.rs index 83a8ade8..1edcdfed 100644 --- a/crates/loro-internal/src/lib.rs +++ b/crates/loro-internal/src/lib.rs @@ -21,6 +21,7 @@ pub use loro::LoroDoc; pub use loro_common; pub use oplog::OpLog; pub use state::DocState; +pub use state::TreeParentId; pub use undo::UndoManager; pub mod awareness; pub mod cursor; diff --git a/crates/loro-internal/src/loro.rs b/crates/loro-internal/src/loro.rs index bb62350c..445651f3 100644 --- a/crates/loro-internal/src/loro.rs +++ b/crates/loro-internal/src/loro.rs @@ -31,7 +31,7 @@ use crate::{ decode_snapshot, export_fast_snapshot, export_snapshot, json_schema::json::JsonSchema, parse_header_and_body, EncodeMode, ParsedHeaderAndBody, }, - event::{str_to_path, EventTriggerKind, Index, InternalDocDiff, Path}, + event::{str_to_path, EventTriggerKind, Index, InternalDocDiff}, handler::{Handler, MovableListHandler, TextHandler, TreeHandler, ValueOrHandler}, id::PeerID, obs::{Observer, SubID, Subscriber}, @@ -172,22 +172,6 @@ impl LoroDoc { self.config.set_merge_interval(interval); } - /// Set whether to use fractional index for Tree Position. - #[inline] - pub fn set_with_fractional_index(&self, with_fractional_index: bool) { - self.config.set_with_fractional_index(with_fractional_index); - } - - /// Set the jitter of the tree position(Fractional Index). - /// - /// The jitter is used to avoid conflicts when multiple users are creating the node at the same position. - /// value 0 is default, which means no jitter, any value larger than 0 will enable jitter. - /// Generally speaking, jitter will affect the growth rate of document size. - #[inline] - pub fn set_fractional_index_jitter(&self, jitter: u8) { - self.config.set_fractional_index_jitter(jitter); - } - #[inline] pub fn config_text_style(&self, text_style: StyleConfigMap) { *self.config.text_style_config.try_write().unwrap() = text_style; diff --git a/crates/loro-internal/src/oplog.rs b/crates/loro-internal/src/oplog.rs index ed70e433..023b0163 100644 --- a/crates/loro-internal/src/oplog.rs +++ b/crates/loro-internal/src/oplog.rs @@ -7,7 +7,7 @@ use std::cell::RefCell; use std::cmp::Ordering; use std::rc::Rc; use std::sync::Mutex; -use tracing::{debug, instrument, trace, trace_span}; +use tracing::{debug, trace, trace_span}; use self::change_store::iter::MergedChangeIter; use self::pending_changes::PendingChanges; diff --git a/crates/loro-internal/src/oplog/change_store.rs b/crates/loro-internal/src/oplog/change_store.rs index df2184b6..34c83730 100644 --- a/crates/loro-internal/src/oplog/change_store.rs +++ b/crates/loro-internal/src/oplog/change_store.rs @@ -1320,8 +1320,8 @@ impl ChangesBlockBytes { #[cfg(test)] mod test { use crate::{ - oplog::convert_change_to_remote, ListHandler, LoroDoc, MovableListHandler, TextHandler, - TreeHandler, + oplog::convert_change_to_remote, state::TreeParentId, ListHandler, LoroDoc, + MovableListHandler, TextHandler, TreeHandler, }; use super::*; @@ -1399,10 +1399,10 @@ mod test { let tree = map .insert_container("tree", TreeHandler::new_detached()) .unwrap(); - let node_id = tree.create(None)?; + let node_id = tree.create(TreeParentId::Root)?; tree.get_meta(node_id)?.insert("key", "value")?; - let node_b = tree.create(None)?; - tree.move_to(node_b, None, 0).unwrap(); + let node_b = tree.create(TreeParentId::Root)?; + tree.move_to(node_b, TreeParentId::Root, 0).unwrap(); let movable_list = map .insert_container("movable", MovableListHandler::new_detached()) diff --git a/crates/loro-internal/src/state.rs b/crates/loro-internal/src/state.rs index 2b252f71..6575efab 100644 --- a/crates/loro-internal/src/state.rs +++ b/crates/loro-internal/src/state.rs @@ -1,9 +1,8 @@ use std::{ borrow::Cow, - collections::BTreeMap, io::Write, sync::{ - atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering}, + atomic::{AtomicU64, Ordering}, Arc, Mutex, RwLock, Weak, }, }; @@ -15,7 +14,7 @@ use fxhash::{FxHashMap, FxHashSet}; use itertools::Itertools; use loro_common::{ContainerID, LoroError, LoroResult}; use loro_delta::DeltaItem; -use tracing::{info_span, instrument, trace}; +use tracing::{info_span, instrument}; use crate::{ configure::{Configure, DefaultRandom, SecureRandomGenerator}, @@ -49,9 +48,8 @@ pub(crate) use self::movable_list_state::{IndexType, MovableListState}; pub(crate) use list_state::ListState; pub(crate) use map_state::MapState; pub(crate) use richtext_state::RichtextState; -pub(crate) use tree_state::{ - get_meta_value, FractionalIndexGenResult, NodePosition, TreeParentId, TreeState, -}; +pub use tree_state::TreeParentId; +pub(crate) use tree_state::{get_meta_value, FractionalIndexGenResult, NodePosition, TreeState}; use self::{container_store::ContainerWrapper, unknown_state::UnknownState}; @@ -310,18 +308,8 @@ impl State { Self::RichtextState(Box::new(RichtextState::new(idx, config))) } - pub fn new_tree( - idx: ContainerIdx, - peer: PeerID, - jitter: Arc, - with_fractional_index: Arc, - ) -> Self { - Self::TreeState(Box::new(TreeState::new( - idx, - peer, - jitter, - with_fractional_index, - ))) + pub fn new_tree(idx: ContainerIdx, peer: PeerID) -> Self { + Self::TreeState(Box::new(TreeState::new(idx, peer))) } pub fn new_unknown(idx: ContainerIdx) -> Self { @@ -1482,12 +1470,7 @@ fn create_state_(idx: ContainerIdx, config: &Configure, peer: u64) -> State { idx, config.text_style_config.clone(), ))), - ContainerType::Tree => State::TreeState(Box::new(TreeState::new( - idx, - peer, - config.tree_position_jitter.clone(), - config.tree_with_fractional_index.clone(), - ))), + ContainerType::Tree => State::TreeState(Box::new(TreeState::new(idx, peer))), ContainerType::MovableList => State::MovableListState(Box::new(MovableListState::new(idx))), #[cfg(feature = "counter")] ContainerType::Counter => { diff --git a/crates/loro-internal/src/state/container_store.rs b/crates/loro-internal/src/state/container_store.rs index e0f8d75d..eb4b4707 100644 --- a/crates/loro-internal/src/state/container_store.rs +++ b/crates/loro-internal/src/state/container_store.rs @@ -1,16 +1,12 @@ -#[cfg(feature = "counter")] -use super::counter_state::CounterState; -use super::{ContainerCreationContext, MovableListState, State, TreeState}; +use super::{ContainerCreationContext, State}; use crate::{ arena::SharedArena, configure::Configure, container::idx::ContainerIdx, - state::{FastStateSnapshot, RichtextState}, }; use bytes::Bytes; -use fxhash::FxHashMap; use inner_store::InnerStore; -use loro_common::{ContainerID, ContainerType, LoroResult, LoroValue}; +use loro_common::{LoroResult, LoroValue}; use std::sync::{atomic::AtomicU64, Arc}; pub(crate) use container_wrapper::ContainerWrapper; @@ -443,7 +439,7 @@ mod encode { #[cfg(test)] mod test { use super::*; - use crate::{ListHandler, LoroDoc, MapHandler, MovableListHandler}; + use crate::{state::TreeParentId, ListHandler, LoroDoc, MapHandler, MovableListHandler}; fn decode_container_store(bytes: Bytes) -> ContainerStore { let mut new_store = ContainerStore::new( @@ -468,8 +464,8 @@ mod test { list.push("item1").unwrap(); let tree = doc.get_tree("tree"); - let root = tree.create(None).unwrap(); - tree.create_at(Some(root), 0).unwrap(); + let root = tree.create(TreeParentId::Root).unwrap(); + tree.create_at(TreeParentId::Node(root), 0).unwrap(); let movable_list = doc.get_movable_list("movable_list"); movable_list.insert(0, "movable_item").unwrap(); diff --git a/crates/loro-internal/src/state/movable_list_state.rs b/crates/loro-internal/src/state/movable_list_state.rs index 95746049..51dd1247 100644 --- a/crates/loro-internal/src/state/movable_list_state.rs +++ b/crates/loro-internal/src/state/movable_list_state.rs @@ -18,8 +18,7 @@ use crate::{ handler::ValueOrHandler, op::{ListSlice, Op, RawOp}, state::movable_list_state::inner::PushElemInfo, - txn::Transaction, - ApplyDiff, DocState, ListDiff, + txn::Transaction, DocState, ListDiff, }; use self::{ diff --git a/crates/loro-internal/src/state/tree_state.rs b/crates/loro-internal/src/state/tree_state.rs index 45091428..3c64f48b 100644 --- a/crates/loro-internal/src/state/tree_state.rs +++ b/crates/loro-internal/src/state/tree_state.rs @@ -13,7 +13,6 @@ use serde::Serialize; use std::collections::VecDeque; use std::fmt::Debug; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::{Arc, Mutex, Weak}; use super::{ContainerState, DiffApplyContext}; @@ -33,6 +32,16 @@ use crate::{ op::RawOp, }; +#[derive(Clone, Debug, Default, EnumAsInner)] +pub enum TreeFractionalIndexConfigInner { + GenerateFractionalIndex { + jitter: u8, + rng: Box, + }, + #[default] + AlwaysDefault, +} + /// The state of movable tree. /// /// using flat representation @@ -41,10 +50,8 @@ pub struct TreeState { idx: ContainerIdx, trees: FxHashMap, children: TreeChildrenCache, - /// Whether the tree has fractional index. If false, the fractional index is always [`FractionalIndex::default`] - with_fractional_index: Arc, - rng: Option, - jitter: Arc, + fractional_index_config: TreeFractionalIndexConfigInner, + peer_id: PeerID, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -88,8 +95,28 @@ impl From> for TreeParentId { } } +impl From for TreeParentId { + fn from(id: TreeID) -> Self { + if TreeID::is_deleted_root(&id) { + TreeParentId::Deleted + } else { + TreeParentId::Node(id) + } + } +} + +impl From<&TreeID> for TreeParentId { + fn from(id: &TreeID) -> Self { + if TreeID::is_deleted_root(id) { + TreeParentId::Deleted + } else { + TreeParentId::Node(*id) + } + } +} + impl TreeParentId { - fn id(&self) -> Option { + pub fn tree_id(&self) -> Option { match self { TreeParentId::Node(id) => Some(*id), TreeParentId::Root => None, @@ -599,22 +626,13 @@ impl NodePosition { } impl TreeState { - pub fn new( - idx: ContainerIdx, - peer_id: PeerID, - jitter_config: Arc, - with_fractional_index: Arc, - ) -> Self { - let jitter = jitter_config.load(Ordering::Relaxed); - let use_jitter = jitter != 1; - + pub fn new(idx: ContainerIdx, peer_id: PeerID) -> Self { Self { idx, trees: FxHashMap::default(), children: Default::default(), - with_fractional_index, - rng: use_jitter.then_some(rand::rngs::StdRng::seed_from_u64(peer_id)), - jitter: jitter_config, + fractional_index_config: TreeFractionalIndexConfigInner::default(), + peer_id, } } @@ -638,7 +656,7 @@ impl TreeState { } if let Some(old_parent) = self.trees.get(&target).map(|x| x.parent) { // remove old position - self.delete_position(&old_parent, target); + self.delete_position(&old_parent, &target); } let entry = self.children.entry(parent).or_default(); @@ -686,11 +704,8 @@ impl TreeState { } /// Get the parent of the node, if the node is deleted or does not exist, return None - pub fn parent(&self, target: &TreeID) -> TreeParentId { - self.trees - .get(target) - .map(|x| x.parent) - .unwrap_or(TreeParentId::Unexist) + pub fn parent(&self, target: &TreeID) -> Option { + self.trees.get(target).map(|x| x.parent) } /// If the node exists and is not deleted, return false. @@ -720,7 +735,11 @@ impl TreeState { let children = self.children.get(&root); let mut q = children .map(|x| { - VecDeque::from_iter(x.iter().enumerate().zip(std::iter::repeat(None::))) + VecDeque::from_iter( + x.iter() + .enumerate() + .zip(std::iter::repeat(TreeParentId::Root)), + ) }) .unwrap_or_default(); @@ -737,7 +756,7 @@ impl TreeState { .iter() .enumerate() .map(|(index, (position, this_target))| { - ((index, (position, this_target)), Some(target)) + ((index, (position, this_target)), TreeParentId::Node(target)) }), ); } @@ -758,7 +777,7 @@ impl TreeState { for (index, (position, target)) in children.iter().enumerate() { ans.push(TreeNode { id: *target, - parent: root.id(), + parent: root, position: position.position.clone(), index, }); @@ -815,9 +834,9 @@ impl TreeState { } /// Delete the position cache of the node - pub(crate) fn delete_position(&mut self, parent: &TreeParentId, target: TreeID) { + pub(crate) fn delete_position(&mut self, parent: &TreeParentId, target: &TreeID) { if let Some(x) = self.children.get_mut(parent) { - x.delete_child(&target); + x.delete_child(target); } } @@ -827,21 +846,49 @@ impl TreeState { parent: &TreeParentId, index: usize, ) -> FractionalIndexGenResult { - if !self.with_fractional_index.load(Ordering::Relaxed) { - return FractionalIndexGenResult::Ok(FractionalIndex::default()); + match &mut self.fractional_index_config { + TreeFractionalIndexConfigInner::GenerateFractionalIndex { jitter, rng } => { + if *jitter == 0 { + self.children + .entry(*parent) + .or_default() + .generate_fi_at(index, target) + } else { + self.children + .entry(*parent) + .or_default() + .generate_fi_at_jitter(index, target, rng.as_mut(), *jitter) + } + } + TreeFractionalIndexConfigInner::AlwaysDefault => { + FractionalIndexGenResult::Ok(FractionalIndex::default()) + } } + } - if let Some(rng) = self.rng.as_mut() { - self.children - .entry(*parent) - .or_default() - .generate_fi_at_jitter(index, target, rng, self.jitter.load(Ordering::Relaxed)) - } else { - self.children - .entry(*parent) - .or_default() - .generate_fi_at(index, target) + pub(crate) fn is_fractional_index_enabled(&self) -> bool { + !matches!( + self.fractional_index_config, + TreeFractionalIndexConfigInner::AlwaysDefault + ) + } + + pub(crate) fn enable_generate_fractional_index(&mut self, jitter: u8) { + if let TreeFractionalIndexConfigInner::GenerateFractionalIndex { + jitter: old_jitter, .. + } = &mut self.fractional_index_config + { + *old_jitter = jitter; + return; } + self.fractional_index_config = TreeFractionalIndexConfigInner::GenerateFractionalIndex { + jitter, + rng: Box::new(rand::rngs::StdRng::seed_from_u64(self.peer_id)), + }; + } + + pub(crate) fn disable_generate_fractional_index(&mut self) { + self.fractional_index_config = TreeFractionalIndexConfigInner::AlwaysDefault; } pub(crate) fn get_position(&self, target: &TreeID) -> Option { @@ -849,7 +896,7 @@ impl TreeState { } pub(crate) fn get_index_by_tree_id(&self, target: &TreeID) -> Option { - let parent = self.parent(target); + let parent = self.parent(target)?; (!parent.is_deleted()) .then(|| { self.children @@ -922,13 +969,15 @@ impl ContainerState for TreeState { ans.push(TreeDiffItem { target, action: TreeExternalDiff::Create { - parent: parent.into_node().ok(), + parent: *parent, index, position: position.clone(), }, }); } TreeInternalDiff::Move { parent, position } => { + let old_parent = self.trees.get(&target).unwrap().parent; + let old_index = self.get_index_by_tree_id(&target).unwrap(); if need_check { let was_alive = !self.is_node_deleted(&target); if self @@ -940,7 +989,10 @@ impl ContainerState for TreeState { // delete event ans.push(TreeDiffItem { target, - action: TreeExternalDiff::Delete, + action: TreeExternalDiff::Delete { + old_parent, + old_index, + }, }); } // Otherwise, it's a normal move inside deleted nodes, no event is needed @@ -949,9 +1001,11 @@ impl ContainerState for TreeState { ans.push(TreeDiffItem { target, action: TreeExternalDiff::Move { - parent: parent.into_node().ok(), + parent: *parent, index: self.get_index_by_tree_id(&target).unwrap(), position: position.clone(), + old_parent, + old_index, }, }); } else { @@ -959,7 +1013,7 @@ impl ContainerState for TreeState { ans.push(TreeDiffItem { target, action: TreeExternalDiff::Create { - parent: parent.into_node().ok(), + parent: *parent, index: self.get_index_by_tree_id(&target).unwrap(), position: position.clone(), }, @@ -974,9 +1028,11 @@ impl ContainerState for TreeState { ans.push(TreeDiffItem { target, action: TreeExternalDiff::Move { - parent: parent.into_node().ok(), + parent: *parent, index, position: position.clone(), + old_parent, + old_index, }, }); }; @@ -986,15 +1042,17 @@ impl ContainerState for TreeState { if need_check && self.is_node_deleted(&target) { send_event = false; } - - self.mov(target, *parent, last_move_op, position.clone(), false) - .unwrap(); if send_event { ans.push(TreeDiffItem { target, - action: TreeExternalDiff::Delete, + action: TreeExternalDiff::Delete { + old_parent: self.trees.get(&target).unwrap().parent, + old_index: self.get_index_by_tree_id(&target).unwrap(), + }, }); } + self.mov(target, *parent, last_move_op, position.clone(), false) + .unwrap(); } TreeInternalDiff::MoveInDelete { parent, position } => { self.mov(target, *parent, last_move_op, position.clone(), false) @@ -1005,7 +1063,10 @@ impl ContainerState for TreeState { if !self.is_node_deleted(&target) { ans.push(TreeDiffItem { target, - action: TreeExternalDiff::Delete, + action: TreeExternalDiff::Delete { + old_parent: self.trees.get(&target).unwrap().parent, + old_index: self.get_index_by_tree_id(&target).unwrap(), + }, }); } // delete it from state @@ -1041,7 +1102,9 @@ impl ContainerState for TreeState { // create associated metadata container match &diff.action { TreeInternalDiff::Create { parent, position } - | TreeInternalDiff::Move { parent, position } => { + | TreeInternalDiff::Move { + parent, position, .. + } => { if need_check { self.mov(target, *parent, last_move_op, Some(position.clone()), true) .unwrap_or_default(); @@ -1130,7 +1193,7 @@ impl ContainerState for TreeState { let diff = TreeDiffItem { target: *node, action: TreeExternalDiff::Create { - parent: node_parent.into_node().ok(), + parent: node_parent, index, position: position.position.clone(), }, @@ -1241,7 +1304,7 @@ pub(crate) fn get_meta_value(nodes: &mut Vec, state: &mut DocState) { pub(crate) struct TreeNode { pub(crate) id: TreeID, - pub(crate) parent: Option, + pub(crate) parent: TreeParentId, pub(crate) position: FractionalIndex, pub(crate) index: usize, } @@ -1252,6 +1315,7 @@ impl TreeNode { t.insert("id".to_string(), self.id.to_string().into()); let p = self .parent + .tree_id() .map(|p| p.to_string().into()) .unwrap_or(LoroValue::Null); t.insert("parent".to_string(), p); @@ -1431,16 +1495,12 @@ mod snapshot { let n = state.trees.get(&node.id).unwrap(); let last_set_id = n.last_move_op; nodes.push(EncodedTreeNode { - parent_idx_plus_two: node - .parent - .map(|p| { - if p.is_deleted_root() { - 1 - } else { - id_to_idx.get(&p).unwrap() + 2 - } - }) - .unwrap_or(0), + parent_idx_plus_two: match node.parent { + TreeParentId::Deleted => 1, + TreeParentId::Root => 0, + TreeParentId::Node(id) => id_to_idx.get(&id).unwrap() + 2, + TreeParentId::Unexist => unreachable!(), + }, last_set_peer_idx: peers.register(&last_set_id.peer), last_set_counter: last_set_id.counter, last_set_lamport: last_set_id.lamport, @@ -1496,12 +1556,7 @@ mod snapshot { peers.push(PeerID::from_le_bytes(buf)); } - let mut tree = TreeState::new( - idx, - ctx.peer, - ctx.configure.tree_position_jitter.clone(), - ctx.configure.tree_with_fractional_index.clone(), - ); + let mut tree = TreeState::new(idx, ctx.peer); let encoded: EncodedTree = serde_columnar::from_bytes(bytes)?; let fractional_indexes = PositionArena::decode(&encoded.fractional_indexes).unwrap(); let fractional_indexes = fractional_indexes.parse_to_positions(); @@ -1556,10 +1611,10 @@ mod snapshot { doc.set_peer_id(0).unwrap(); doc.start_auto_commit(); let tree = doc.get_tree("tree"); - let a = tree.create(None).unwrap(); - let b = tree.create(None).unwrap(); - let _c = tree.create(None).unwrap(); - tree.mov(b, a).unwrap(); + let a = tree.create(TreeParentId::Root).unwrap(); + let b = tree.create(TreeParentId::Root).unwrap(); + let _c = tree.create(TreeParentId::Root).unwrap(); + tree.mov(b, TreeParentId::Node(a)).unwrap(); let (bytes, value) = { let mut doc_state = doc.app_state().lock().unwrap(); let tree_state = doc_state.get_tree("tree").unwrap(); diff --git a/crates/loro-internal/src/utils/kv_wrapper.rs b/crates/loro-internal/src/utils/kv_wrapper.rs index af7cfbe1..17a27dd8 100644 --- a/crates/loro-internal/src/utils/kv_wrapper.rs +++ b/crates/loro-internal/src/utils/kv_wrapper.rs @@ -1,12 +1,6 @@ -use std::{ - collections::BTreeMap, - ops::Bound, - sync::{Arc, Mutex}, -}; +use std::sync::{Arc, Mutex}; use bytes::Bytes; -use fxhash::FxHashMap; -use loro_common::ContainerID; use loro_kv_store::MemKvStore; use crate::kv_store::KvStore; diff --git a/crates/loro-internal/src/value.rs b/crates/loro-internal/src/value.rs index f9e1624f..8988eaa3 100644 --- a/crates/loro-internal/src/value.rs +++ b/crates/loro-internal/src/value.rs @@ -558,8 +558,12 @@ pub mod wasm { position, } => { js_sys::Reflect::set(&obj, &"action".into(), &"create".into()).unwrap(); - js_sys::Reflect::set(&obj, &"parent".into(), &JsValue::from(*parent)) - .unwrap(); + js_sys::Reflect::set( + &obj, + &"parent".into(), + &JsValue::from(parent.tree_id()), + ) + .unwrap(); js_sys::Reflect::set(&obj, &"index".into(), &(*index).into()).unwrap(); js_sys::Reflect::set( &obj, @@ -568,18 +572,34 @@ pub mod wasm { ) .unwrap(); } - TreeExternalDiff::Delete { .. } => { + TreeExternalDiff::Delete { + old_parent, + old_index, + } => { js_sys::Reflect::set(&obj, &"action".into(), &"delete".into()).unwrap(); + js_sys::Reflect::set( + &obj, + &"old_parent".into(), + &JsValue::from(old_parent.tree_id()), + ) + .unwrap(); + js_sys::Reflect::set(&obj, &"old_index".into(), &(*old_index).into()) + .unwrap(); } TreeExternalDiff::Move { parent, index, position, - .. + old_parent, + old_index, } => { js_sys::Reflect::set(&obj, &"action".into(), &"move".into()).unwrap(); - js_sys::Reflect::set(&obj, &"parent".into(), &JsValue::from(*parent)) - .unwrap(); + js_sys::Reflect::set( + &obj, + &"parent".into(), + &JsValue::from(parent.tree_id()), + ) + .unwrap(); js_sys::Reflect::set(&obj, &"index".into(), &(*index).into()).unwrap(); js_sys::Reflect::set( &obj, @@ -587,6 +607,14 @@ pub mod wasm { &position.to_string().into(), ) .unwrap(); + js_sys::Reflect::set( + &obj, + &"old_parent".into(), + &JsValue::from(old_parent.tree_id()), + ) + .unwrap(); + js_sys::Reflect::set(&obj, &"old_index".into(), &(*old_index).into()) + .unwrap(); } } array.push(&obj); diff --git a/crates/loro-internal/tests/test.rs b/crates/loro-internal/tests/test.rs index a499c0a4..8ff83b3e 100644 --- a/crates/loro-internal/tests/test.rs +++ b/crates/loro-internal/tests/test.rs @@ -8,6 +8,7 @@ use loro_internal::{ handler::{Handler, TextDelta, ValueOrHandler}, version::Frontiers, ApplyDiff, HandlerTrait, ListHandler, LoroDoc, MapHandler, TextHandler, ToJson, TreeHandler, + TreeParentId, }; use serde_json::json; @@ -724,11 +725,11 @@ fn tree_checkout() { doc_a.subscribe_root(Arc::new(|_e| {})); doc_a.set_peer_id(1).unwrap(); let tree = doc_a.get_tree("root"); - let id1 = tree.create(None).unwrap(); - let id2 = tree.create(id1).unwrap(); + let id1 = tree.create(TreeParentId::Root).unwrap(); + let id2 = tree.create(TreeParentId::Node(id1)).unwrap(); let v1_state = tree.get_deep_value(); let v1 = doc_a.oplog_frontiers(); - let _id3 = tree.create(id2).unwrap(); + let _id3 = tree.create(TreeParentId::Node(id2)).unwrap(); let v2_state = tree.get_deep_value(); let v2 = doc_a.oplog_frontiers(); tree.delete(id2).unwrap(); @@ -757,7 +758,7 @@ fn tree_checkout() { ); doc_a.attach(); - tree.create(None).unwrap(); + tree.create(TreeParentId::Root).unwrap(); } #[test] @@ -853,8 +854,8 @@ fn missing_event_when_checkout() { let doc2 = LoroDoc::new_auto_commit(); let tree = doc2.get_tree("tree"); - let node = tree.create_at(None, 0).unwrap(); - let _ = tree.create_at(None, 0).unwrap(); + let node = tree.create_at(TreeParentId::Root, 0).unwrap(); + let _ = tree.create_at(TreeParentId::Root, 0).unwrap(); let meta = tree.get_meta(node).unwrap(); meta.insert("a", 0).unwrap(); doc.import(&doc2.export_from(&doc.oplog_vv())).unwrap(); @@ -930,7 +931,7 @@ fn insert_attach_container() -> LoroResult<()> { #[test] fn tree_attach() { let tree = TreeHandler::new_detached(); - let id = tree.create(None).unwrap(); + let id = tree.create(TreeParentId::Root).unwrap(); tree.get_meta(id).unwrap().insert("key", "value").unwrap(); let doc = LoroDoc::new_auto_commit(); doc.get_list("list").insert_container(0, tree).unwrap(); diff --git a/crates/loro-internal/tests/tree.rs b/crates/loro-internal/tests/tree.rs new file mode 100644 index 00000000..20edb584 --- /dev/null +++ b/crates/loro-internal/tests/tree.rs @@ -0,0 +1,25 @@ +use loro_internal::{LoroDoc, TreeParentId}; + +#[test] +fn tree_index() { + let doc = LoroDoc::new_auto_commit(); + doc.set_peer_id(0).unwrap(); + let tree = doc.get_tree("tree"); + let root = tree.create(TreeParentId::Root).unwrap(); + let child = tree.create(root.into()).unwrap(); + let child2 = tree.create_at(root.into(), 0).unwrap(); + // sort with OpID + assert_eq!(tree.get_index_by_tree_id(&child).unwrap(), 0); + assert_eq!(tree.get_index_by_tree_id(&child2).unwrap(), 1); + + let doc = LoroDoc::new_auto_commit(); + doc.set_peer_id(0).unwrap(); + let tree = doc.get_tree("tree"); + tree.set_enable_fractional_index(0); + let root = tree.create(TreeParentId::Root).unwrap(); + let child = tree.create(root.into()).unwrap(); + let child2 = tree.create_at(root.into(), 0).unwrap(); + // sort with fractional index + assert_eq!(tree.get_index_by_tree_id(&child).unwrap(), 1); + assert_eq!(tree.get_index_by_tree_id(&child2).unwrap(), 0); +} diff --git a/crates/loro-wasm/src/lib.rs b/crates/loro-wasm/src/lib.rs index 8f7d9f16..f463cd5c 100644 --- a/crates/loro-wasm/src/lib.rs +++ b/crates/loro-wasm/src/lib.rs @@ -24,7 +24,8 @@ use loro_internal::{ undo::{UndoItemMeta, UndoOrRedo}, version::Frontiers, ContainerType, DiffEvent, FxHashMap, HandlerTrait, LoroDoc as LoroDocInner, LoroValue, - MovableListHandler, UndoManager as InnerUndoManager, VersionVector as InternalVersionVector, + MovableListHandler, TreeParentId, UndoManager as InnerUndoManager, + VersionVector as InternalVersionVector, }; use rle::HasLength; use serde::{Deserialize, Serialize}; @@ -340,16 +341,6 @@ impl LoroDoc { self.0.set_change_merge_interval(interval as i64); } - /// Set the jitter of the tree position(Fractional Index). - /// - /// The jitter is used to avoid conflicts when multiple users are creating the node at the same position. - /// value 0 is default, which means no jitter, any value larger than 0 will enable jitter. - /// Generally speaking, jitter will affect the growth rate of document size. - #[wasm_bindgen(js_name = "setFractionalIndexJitter")] - pub fn set_fractional_index_jitter(&self, jitter: u8) { - self.0.set_fractional_index_jitter(jitter); - } - /// Set the rich text format configuration of the document. /// /// You need to config it if you use rich text `mark` method. @@ -3043,9 +3034,9 @@ impl LoroTreeNode { #[wasm_bindgen(js_name = "createNode", skip_typescript)] pub fn create_node(&self, index: Option) -> JsResult { let id = if let Some(index) = index { - self.tree.create_at(Some(self.id), index)? + self.tree.create_at(TreeParentId::Node(self.id), index)? } else { - self.tree.create(Some(self.id))? + self.tree.create(TreeParentId::Node(self.id))? }; let node = LoroTreeNode::from_tree(id, self.tree.clone(), self.doc.clone()); Ok(node) @@ -3075,9 +3066,10 @@ impl LoroTreeNode { pub fn mov(&self, parent: &JsTreeNodeOrUndefined, index: Option) -> JsResult<()> { let parent: Option = parse_js_tree_node(parent)?; if let Some(index) = index { - self.tree.move_to(self.id, parent.map(|x| x.id), index)? + self.tree + .move_to(self.id, parent.map(|x| x.id).into(), index)? } else { - self.tree.mov(self.id, parent.map(|x| x.id))?; + self.tree.mov(self.id, parent.map(|x| x.id).into())?; } Ok(()) @@ -3169,9 +3161,15 @@ impl LoroTreeNode { /// - The object returned is a new js object each time because it need to cross /// the WASM boundary. #[wasm_bindgen] - pub fn parent(&self) -> Option { - let parent = self.tree.get_node_parent(&self.id).flatten(); - parent.map(|p| LoroTreeNode::from_tree(p, self.tree.clone(), self.doc.clone())) + pub fn parent(&self) -> JsResult> { + let parent = self + .tree + .get_node_parent(&self.id) + .ok_or(JsValue::from_str(&format!("TreeID({}) not found", self.id)))?; + let ans = parent + .tree_id() + .map(|p| LoroTreeNode::from_tree(p, self.tree.clone(), self.doc.clone())); + Ok(ans) } /// Get the children of this node. @@ -3180,7 +3178,7 @@ impl LoroTreeNode { /// the WASM boundary. #[wasm_bindgen(skip_typescript)] pub fn children(&self) -> JsValue { - let Some(children) = self.tree.children(Some(self.id)) else { + let Some(children) = self.tree.children(&TreeParentId::Node(self.id)) else { return JsValue::undefined(); }; let children = children.into_iter().map(|c| { @@ -3236,9 +3234,9 @@ impl LoroTree { ) -> JsResult { let parent: Option = parse_js_parent(parent)?; let id = if let Some(index) = index { - self.handler.create_at(parent, index)? + self.handler.create_at(parent.into(), index)? } else { - self.handler.create(parent)? + self.handler.create(parent.into())? }; let node = LoroTreeNode::from_tree(id, self.handler.clone(), self.doc.clone()); Ok(node) @@ -3272,9 +3270,9 @@ impl LoroTree { let parent = parse_js_parent(parent)?; if let Some(index) = index { - self.handler.move_to(target, parent, index)? + self.handler.move_to(target, parent.into(), index)? } else { - self.handler.mov(target, parent)? + self.handler.mov(target, parent.into())? }; Ok(()) @@ -3533,6 +3531,25 @@ impl LoroTree { JsValue::UNDEFINED.into() } } + + /// Set whether to generate fractional index for Tree Position. + /// + /// The jitter is used to avoid conflicts when multiple users are creating the node at the same position. + /// value 0 is default, which means no jitter, any value larger than 0 will enable jitter. + /// + /// Generally speaking, jitter will affect the growth rate of document size. + /// [Read more about it](https://www.loro.dev/blog/movable-tree#implementation-and-encoding-size) + #[wasm_bindgen(js_name = "setEnableFractionalIndex")] + pub fn set_enable_fractional_index(&self, jitter: u8) { + self.handler.set_enable_fractional_index(jitter); + } + + /// Disable the fractional index generation for Tree Position when + /// you don't need the Tree's siblings to be sorted. The fractional index will be always default. + #[wasm_bindgen(js_name = "setDisableFractionalIndex")] + pub fn set_disable_fractional_index(&self) { + self.handler.set_disable_fractional_index(); + } } impl Default for LoroTree { diff --git a/crates/loro/src/change_meta.rs b/crates/loro/src/change_meta.rs index 00f5b05f..b5c5fde7 100644 --- a/crates/loro/src/change_meta.rs +++ b/crates/loro/src/change_meta.rs @@ -2,7 +2,7 @@ use std::{cmp::Ordering, sync::Arc}; use loro_internal::{ change::{Change, Lamport, Timestamp}, - id::{Counter, ID}, + id::ID, version::Frontiers, }; diff --git a/crates/loro/src/lib.rs b/crates/loro/src/lib.rs index f9fe2758..8358a5b0 100644 --- a/crates/loro/src/lib.rs +++ b/crates/loro/src/lib.rs @@ -12,11 +12,12 @@ use loro_internal::cursor::Side; use loro_internal::encoding::ImportBlobMetadata; use loro_internal::handler::HandlerTrait; use loro_internal::handler::ValueOrHandler; -use loro_internal::json::JsonChange; +use loro_internal::loro_common::LoroTreeError; use loro_internal::undo::{OnPop, OnPush}; use loro_internal::DocState; use loro_internal::LoroDoc as InnerLoroDoc; use loro_internal::OpLog; +use loro_internal::TreeParentId; use loro_internal::{ handler::Handler as InnerHandler, ListHandler as InnerListHandler, MapHandler as InnerMapHandler, MovableListHandler as InnerMovableListHandler, @@ -155,22 +156,6 @@ impl LoroDoc { self.doc.set_change_merge_interval(interval); } - /// Set whether to use fractional index for Tree Position. - #[inline] - pub fn set_with_fractional_index(&self, with_fractional_index: bool) { - self.doc.set_with_fractional_index(with_fractional_index); - } - - /// Set the jitter of the tree position(Fractional Index). - /// - /// The jitter is used to avoid conflicts when multiple users are creating the node at the same position. - /// value 0 is default, which means no jitter, any value larger than 0 will enable jitter. - /// Generally speaking, jitter will affect the growth rate of document size. - #[inline] - pub fn set_fractional_index_jitter(&self, jitter: u8) { - self.doc.set_fractional_index_jitter(jitter); - } - /// Set the rich text format configuration of the document. /// /// You need to config it if you use rich text `mark` method. @@ -1422,10 +1407,8 @@ impl LoroTree { /// // create a new child /// let child = tree.create(root).unwrap(); /// ``` - pub fn create>>(&self, parent: T) -> LoroResult { - let parent = parent.into(); - let index = self.children_num(parent).unwrap_or(0); - self.handler.create_at(parent, index) + pub fn create>(&self, parent: T) -> LoroResult { + self.handler.create(parent.into()) } /// Get the root nodes of the forest. @@ -1445,17 +1428,18 @@ impl LoroTree { /// /// let doc = LoroDoc::new(); /// let tree = doc.get_tree("tree"); + /// // enable generate fractional index + /// tree.set_enable_fractional_index(0); /// // create a root /// let root = tree.create(None).unwrap(); /// // create a new child at index 0 /// let child = tree.create_at(root, 0).unwrap(); /// ``` - pub fn create_at>>( - &self, - parent: T, - index: usize, - ) -> LoroResult { - self.handler.create_at(parent, index) + pub fn create_at>(&self, parent: T, index: usize) -> LoroResult { + if !self.handler.is_fractional_index_enabled() { + return Err(LoroTreeError::FractionalIndexNotEnabled.into()); + } + self.handler.create_at(parent.into(), index) } /// Move the `target` node to be a child of the `parent` node. @@ -1474,10 +1458,8 @@ impl LoroTree { /// // move `root2` to be a child of `root`. /// tree.mov(root2, root).unwrap(); /// ``` - pub fn mov>>(&self, target: TreeID, parent: T) -> LoroResult<()> { - let parent = parent.into(); - let index = self.children_num(parent).unwrap_or(0); - self.handler.move_to(target, parent, index) + pub fn mov>(&self, target: TreeID, parent: T) -> LoroResult<()> { + self.handler.mov(target, parent.into()) } /// Move the `target` node to be a child of the `parent` node at the given index. @@ -1490,19 +1472,23 @@ impl LoroTree { /// /// let doc = LoroDoc::new(); /// let tree = doc.get_tree("tree"); + /// // enable generate fractional index + /// tree.set_enable_fractional_index(0); /// let root = tree.create(None).unwrap(); /// let root2 = tree.create(None).unwrap(); /// // move `root2` to be a child of `root` at index 0. /// tree.mov_to(root2, root, 0).unwrap(); /// ``` - pub fn mov_to>>( + pub fn mov_to>( &self, target: TreeID, parent: T, to: usize, ) -> LoroResult<()> { - let parent = parent.into(); - self.handler.move_to(target, parent, to) + if !self.handler.is_fractional_index_enabled() { + return Err(LoroTreeError::FractionalIndexNotEnabled.into()); + } + self.handler.move_to(target, parent.into(), to) } /// Move the `target` node to be a child after the `after` node with the same parent. @@ -1514,12 +1500,17 @@ impl LoroTree { /// /// let doc = LoroDoc::new(); /// let tree = doc.get_tree("tree"); + /// // enable generate fractional index + /// tree.set_enable_fractional_index(0); /// let root = tree.create(None).unwrap(); /// let root2 = tree.create(None).unwrap(); /// // move `root` to be a child after `root2`. /// tree.mov_after(root, root2).unwrap(); /// ``` pub fn mov_after(&self, target: TreeID, after: TreeID) -> LoroResult<()> { + if !self.handler.is_fractional_index_enabled() { + return Err(LoroTreeError::FractionalIndexNotEnabled.into()); + } self.handler.mov_after(target, after) } @@ -1532,12 +1523,17 @@ impl LoroTree { /// /// let doc = LoroDoc::new(); /// let tree = doc.get_tree("tree"); + /// // enable generate fractional index + /// tree.set_enable_fractional_index(0); /// let root = tree.create(None).unwrap(); /// let root2 = tree.create(None).unwrap(); /// // move `root` to be a child before `root2`. /// tree.mov_before(root, root2).unwrap(); /// ``` pub fn mov_before(&self, target: TreeID, before: TreeID) -> LoroResult<()> { + if !self.handler.is_fractional_index_enabled() { + return Err(LoroTreeError::FractionalIndexNotEnabled.into()); + } self.handler.mov_before(target, before) } @@ -1582,7 +1578,7 @@ impl LoroTree { /// /// - If the target node does not exist, return `None`. /// - If the target node is a root node, return `Some(None)`. - pub fn parent(&self, target: TreeID) -> Option> { + pub fn parent(&self, target: TreeID) -> Option { self.handler.get_node_parent(&target) } @@ -1599,13 +1595,14 @@ impl LoroTree { /// Return all children of the target node. /// /// If the parent node does not exist, return `None`. - pub fn children(&self, parent: Option) -> Option> { - self.handler.children(parent) + pub fn children>(&self, parent: T) -> Option> { + self.handler.children(&parent.into()) } /// Return the number of children of the target node. - pub fn children_num(&self, parent: Option) -> Option { - self.handler.children_num(parent) + pub fn children_num>(&self, parent: T) -> Option { + let parent: TreeParentId = parent.into(); + self.handler.children_num(&parent) } /// Return container id of the tree. @@ -1639,6 +1636,30 @@ impl LoroTree { pub fn __internal__next_tree_id(&self) -> TreeID { self.handler.__internal__next_tree_id() } + + /// Whether the fractional index is enabled. + pub fn is_fractional_index_enabled(&self) -> bool { + self.handler.is_fractional_index_enabled() + } + + /// Set whether to generate fractional index for Tree Position. + /// + /// The jitter is used to avoid conflicts when multiple users are creating the node at the same position. + /// value 0 is default, which means no jitter, any value larger than 0 will enable jitter. + /// + /// Generally speaking, jitter will affect the growth rate of document size. + /// [Read more about it](https://www.loro.dev/blog/movable-tree#implementation-and-encoding-size) + #[inline] + pub fn set_enable_fractional_index(&self, jitter: u8) { + self.handler.set_enable_fractional_index(jitter); + } + + /// Disable the fractional index generation for Tree Position when + /// you don't need the Tree's siblings to be sorted. The fractional index will be always default. + #[inline] + pub fn set_disable_fractional_index(&self) { + self.handler.set_disable_fractional_index(); + } } impl Default for LoroTree { diff --git a/loro-js/src/index.ts b/loro-js/src/index.ts index 61dd88d0..55a1b09c 100644 --- a/loro-js/src/index.ts +++ b/loro-js/src/index.ts @@ -93,15 +93,17 @@ export type TreeDiffItem = action: "create"; parent: TreeID | undefined; index: number; - position: string; + fractional_index: string; } - | { target: TreeID; action: "delete" } + | { target: TreeID; action: "delete"; old_parent: TreeID | undefined; old_index: number } | { target: TreeID; action: "move"; parent: TreeID | undefined; index: number; - position: string; + fractional_index: string; + old_parent: TreeID | undefined; + old_index: number; }; export type TreeDiff = { diff --git a/loro-js/tests/tree.test.ts b/loro-js/tests/tree.test.ts index bd10a30e..7a930af3 100644 --- a/loro-js/tests/tree.test.ts +++ b/loro-js/tests/tree.test.ts @@ -1,5 +1,5 @@ import { assert, describe, expect, it} from "vitest"; -import { LoroDoc, LoroTree, LoroTreeNode } from "../src"; +import { LoroDoc, LoroTree, LoroTreeNode, TreeDiff } from "../src"; function assertEquals(a: any, b: any) { expect(a).toStrictEqual(b); @@ -8,6 +8,7 @@ function assertEquals(a: any, b: any) { describe("loro tree", () => { const loro = new LoroDoc(); const tree = loro.getTree("root"); + tree.setEnableFractionalIndex(0); it("create", () => { const root = tree.createNode(); @@ -121,6 +122,7 @@ describe("loro tree", () => { describe("loro tree node", ()=>{ const loro = new LoroDoc(); const tree = loro.getTree("root"); + tree.setEnableFractionalIndex(0); it("create", () => { const root = tree.createNode(); @@ -180,6 +182,26 @@ describe("loro tree node", ()=>{ assertEquals(child.index(), 1); assertEquals(child2.index(), 0); }); + + it("old parent", () => { + const root = tree.createNode(); + const child = root.createNode(); + const child2 = root.createNode(); + loro.commit(); + const subID = tree.subscribe((e)=>{ + if(e.events[0].diff.type == "tree"){ + const diff = e.events[0].diff as TreeDiff; + if (diff.diff[0].action == "move"){ + assertEquals(diff.diff[0].old_parent, root.id); + assertEquals(diff.diff[0].old_index, 1); + } + } + }); + child2.move(child); + loro.commit(); + tree.unsubscribe(subID); + assertEquals(child2.parent()!.id, child.id); + }); }); function one_ms(): Promise {