mirror of
https://github.com/loro-dev/loro.git
synced 2025-01-22 21:07:43 +00:00
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 <remch183@outlook.com>
This commit is contained in:
parent
dd6bb10fff
commit
07671ea9fd
30 changed files with 549 additions and 371 deletions
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
|
@ -4,7 +4,7 @@ on:
|
|||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
branches: ["main", "dev"]
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
env:
|
||||
|
|
1
crates/fuzz/fuzz/Cargo.lock
generated
1
crates/fuzz/fuzz/Cargo.lock
generated
|
@ -780,6 +780,7 @@ name = "loro_fractional_index"
|
|||
version = "0.16.2"
|
||||
dependencies = [
|
||||
"imbl",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"serde",
|
||||
"smallvec",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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::<usize>() % (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::<usize>() % SIZE;
|
||||
let j = rng.gen::<usize>() % 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::<usize>() % size;
|
||||
let j = rng.gen::<usize>() % 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::<usize>() % size;
|
||||
let j = rng.gen::<usize>() % 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<usize>() % size;
|
||||
let j = rng.gen::<usize>() % 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!(
|
||||
|
|
|
@ -5,11 +5,6 @@ pub struct Configure {
|
|||
pub(crate) text_style_config: Arc<RwLock<StyleConfigMap>>,
|
||||
record_timestamp: Arc<AtomicBool>,
|
||||
pub(crate) merge_interval: Arc<AtomicI64>,
|
||||
/// 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<AtomicBool>,
|
||||
/// do not use `jitter` by default
|
||||
pub(crate) tree_position_jitter: Arc<AtomicU8>,
|
||||
}
|
||||
|
||||
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)]
|
||||
|
|
|
@ -21,16 +21,21 @@ pub struct TreeDiffItem {
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TreeExternalDiff {
|
||||
Create {
|
||||
parent: Option<TreeID>,
|
||||
parent: TreeParentId,
|
||||
index: usize,
|
||||
position: FractionalIndex,
|
||||
},
|
||||
Move {
|
||||
parent: Option<TreeID>,
|
||||
parent: TreeParentId,
|
||||
index: usize,
|
||||
position: FractionalIndex,
|
||||
old_parent: TreeParentId,
|
||||
old_index: usize,
|
||||
},
|
||||
Delete {
|
||||
old_parent: TreeParentId,
|
||||
old_index: usize,
|
||||
},
|
||||
Delete,
|
||||
}
|
||||
|
||||
impl TreeDiff {
|
||||
|
|
|
@ -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>,
|
||||
) {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -93,8 +93,8 @@ impl TreeInner {
|
|||
self.children_links.get(&parent).map(|x| x.len())
|
||||
}
|
||||
|
||||
fn is_parent(&self, target: TreeID, parent: Option<TreeID>) -> bool {
|
||||
self.parent_links.get(&target) == Some(&parent)
|
||||
fn is_parent(&self, target: &TreeID, parent: &Option<TreeID>) -> bool {
|
||||
self.parent_links.get(target) == Some(parent)
|
||||
}
|
||||
|
||||
fn get_index_by_tree_id(&self, target: &TreeID) -> Option<usize> {
|
||||
|
@ -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::<TreeID>)),
|
||||
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<T: Into<Option<TreeID>>>(&self, parent: T) -> LoroResult<TreeID> {
|
||||
let parent = parent.into();
|
||||
let index: usize = self.children_num(parent).unwrap_or(0);
|
||||
pub fn create(&self, parent: TreeParentId) -> LoroResult<TreeID> {
|
||||
let index: usize = self.children_num(&parent).unwrap_or(0);
|
||||
self.create_at(parent, index)
|
||||
}
|
||||
|
||||
pub fn create_at<T: Into<Option<TreeID>>>(
|
||||
&self,
|
||||
parent: T,
|
||||
index: usize,
|
||||
) -> LoroResult<TreeID> {
|
||||
pub fn create_at(&self, parent: TreeParentId, index: usize) -> LoroResult<TreeID> {
|
||||
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<TreeID>,
|
||||
parent: TreeParentId,
|
||||
position: FractionalIndex,
|
||||
target: TreeID,
|
||||
) -> LoroResult<bool> {
|
||||
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)) {
|
||||
}
|
||||
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<TreeID>,
|
||||
parent: TreeParentId,
|
||||
position: FractionalIndex,
|
||||
target: TreeID,
|
||||
) -> LoroResult<bool> {
|
||||
|
@ -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<T: Into<Option<TreeID>>>(
|
||||
pub(crate) fn create_with_txn(
|
||||
&self,
|
||||
txn: &mut Transaction,
|
||||
parent: T,
|
||||
parent: TreeParentId,
|
||||
index: usize,
|
||||
) -> LoroResult<TreeID> {
|
||||
let inner = self.inner.try_attached_state()?;
|
||||
let parent: Option<TreeID> = 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<T: Into<Option<TreeID>>>(&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<TreeID> = 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<T: Into<Option<TreeID>>>(
|
||||
&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<T: Into<Option<TreeID>>>(
|
||||
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<TreeID>,
|
||||
parent: TreeParentId,
|
||||
index: usize,
|
||||
position: FractionalIndex,
|
||||
) -> LoroResult<TreeID> {
|
||||
|
@ -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<TreeID>,
|
||||
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<Option<TreeID>> {
|
||||
pub fn get_node_parent(&self, target: &TreeID) -> Option<TreeParentId> {
|
||||
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<TreeID>) -> Option<Vec<TreeID>> {
|
||||
pub fn children(&self, parent: &TreeParentId) -> Option<Vec<TreeID>> {
|
||||
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<TreeID>) -> Option<usize> {
|
||||
pub fn children_num(&self, parent: &TreeParentId) -> Option<usize> {
|
||||
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<TreeID>) -> 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<TreeID> {
|
||||
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<TreeID>,
|
||||
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<TreeID>, 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<TreeID>,
|
||||
parent: &TreeParentId,
|
||||
node_position: &NodePosition,
|
||||
) -> Option<usize> {
|
||||
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();
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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<AtomicU8>,
|
||||
with_fractional_index: Arc<AtomicBool>,
|
||||
) -> 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 => {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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<rand::rngs::StdRng>,
|
||||
},
|
||||
#[default]
|
||||
AlwaysDefault,
|
||||
}
|
||||
|
||||
/// The state of movable tree.
|
||||
///
|
||||
/// using flat representation
|
||||
|
@ -41,10 +50,8 @@ pub struct TreeState {
|
|||
idx: ContainerIdx,
|
||||
trees: FxHashMap<TreeID, TreeStateNode>,
|
||||
children: TreeChildrenCache,
|
||||
/// Whether the tree has fractional index. If false, the fractional index is always [`FractionalIndex::default`]
|
||||
with_fractional_index: Arc<AtomicBool>,
|
||||
rng: Option<rand::rngs::StdRng>,
|
||||
jitter: Arc<AtomicU8>,
|
||||
fractional_index_config: TreeFractionalIndexConfigInner,
|
||||
peer_id: PeerID,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -88,8 +95,28 @@ impl From<Option<TreeID>> for TreeParentId {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<TreeID> 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<TreeID> {
|
||||
pub fn tree_id(&self) -> Option<TreeID> {
|
||||
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<AtomicU8>,
|
||||
with_fractional_index: Arc<AtomicBool>,
|
||||
) -> 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<TreeParentId> {
|
||||
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::<TreeID>)))
|
||||
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,29 +846,57 @@ 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.load(Ordering::Relaxed))
|
||||
} else {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<FractionalIndex> {
|
||||
self.trees.get(target).and_then(|x| x.position.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn get_index_by_tree_id(&self, target: &TreeID) -> Option<usize> {
|
||||
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<LoroValue>, state: &mut DocState) {
|
|||
|
||||
pub(crate) struct TreeNode {
|
||||
pub(crate) id: TreeID,
|
||||
pub(crate) parent: Option<TreeID>,
|
||||
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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -558,7 +558,11 @@ pub mod wasm {
|
|||
position,
|
||||
} => {
|
||||
js_sys::Reflect::set(&obj, &"action".into(), &"create".into()).unwrap();
|
||||
js_sys::Reflect::set(&obj, &"parent".into(), &JsValue::from(*parent))
|
||||
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(
|
||||
|
@ -568,17 +572,33 @@ 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))
|
||||
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(
|
||||
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
25
crates/loro-internal/tests/tree.rs
Normal file
25
crates/loro-internal/tests/tree.rs
Normal file
|
@ -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);
|
||||
}
|
|
@ -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<usize>) -> JsResult<LoroTreeNode> {
|
||||
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<usize>) -> JsResult<()> {
|
||||
let parent: Option<LoroTreeNode> = 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<LoroTreeNode> {
|
||||
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<Option<LoroTreeNode>> {
|
||||
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<LoroTreeNode> {
|
||||
let parent: Option<TreeID> = 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 {
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{cmp::Ordering, sync::Arc};
|
|||
|
||||
use loro_internal::{
|
||||
change::{Change, Lamport, Timestamp},
|
||||
id::{Counter, ID},
|
||||
id::ID,
|
||||
version::Frontiers,
|
||||
};
|
||||
|
||||
|
|
|
@ -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<T: Into<Option<TreeID>>>(&self, parent: T) -> LoroResult<TreeID> {
|
||||
let parent = parent.into();
|
||||
let index = self.children_num(parent).unwrap_or(0);
|
||||
self.handler.create_at(parent, index)
|
||||
pub fn create<T: Into<TreeParentId>>(&self, parent: T) -> LoroResult<TreeID> {
|
||||
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<T: Into<Option<TreeID>>>(
|
||||
&self,
|
||||
parent: T,
|
||||
index: usize,
|
||||
) -> LoroResult<TreeID> {
|
||||
self.handler.create_at(parent, index)
|
||||
pub fn create_at<T: Into<TreeParentId>>(&self, parent: T, index: usize) -> LoroResult<TreeID> {
|
||||
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<T: Into<Option<TreeID>>>(&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<T: Into<TreeParentId>>(&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<T: Into<Option<TreeID>>>(
|
||||
pub fn mov_to<T: Into<TreeParentId>>(
|
||||
&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<Option<TreeID>> {
|
||||
pub fn parent(&self, target: TreeID) -> Option<TreeParentId> {
|
||||
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<TreeID>) -> Option<Vec<TreeID>> {
|
||||
self.handler.children(parent)
|
||||
pub fn children<T: Into<TreeParentId>>(&self, parent: T) -> Option<Vec<TreeID>> {
|
||||
self.handler.children(&parent.into())
|
||||
}
|
||||
|
||||
/// Return the number of children of the target node.
|
||||
pub fn children_num(&self, parent: Option<TreeID>) -> Option<usize> {
|
||||
self.handler.children_num(parent)
|
||||
pub fn children_num<T: Into<TreeParentId>>(&self, parent: T) -> Option<usize> {
|
||||
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 {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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<void> {
|
||||
|
|
Loading…
Reference in a new issue