From 87ce16f399157a3e99fc20018cb49633bcc5358f Mon Sep 17 00:00:00 2001 From: Leon Zhao Date: Wed, 15 Jan 2025 11:53:20 +0800 Subject: [PATCH 1/6] feat: export diff batch to ffi --- .vscode/settings.json | 1 + crates/loro-ffi/src/doc.rs | 121 ++++++++++++++++++- crates/loro-ffi/src/event.rs | 176 ++++++++++++++++++++++------ crates/loro-ffi/src/lib.rs | 40 +++++++ crates/loro/src/event.rs | 3 +- crates/loro/tests/loro_rust_test.rs | 1 + 6 files changed, 301 insertions(+), 41 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b972f3533..822ea8213 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -34,6 +34,7 @@ "pointee", "reparent", "RUSTFLAGS", + "serde", "smstring", "sstable", "Stewen", diff --git a/crates/loro-ffi/src/doc.rs b/crates/loro-ffi/src/doc.rs index 28488ac43..79cb8be99 100644 --- a/crates/loro-ffi/src/doc.rs +++ b/crates/loro-ffi/src/doc.rs @@ -13,10 +13,10 @@ use loro::{ }; use crate::{ - event::{DiffEvent, Subscriber}, + event::{DiffBatch, DiffEvent, Subscriber}, AbsolutePosition, Configure, ContainerID, ContainerIdLike, Cursor, Frontiers, Index, LoroCounter, LoroList, LoroMap, LoroMovableList, LoroText, LoroTree, LoroValue, StyleConfigMap, - ValueOrContainer, VersionVector, + ValueOrContainer, VersionVector, VersionVectorDiff, }; /// Decodes the metadata for an imported blob from the provided bytes. @@ -88,10 +88,13 @@ impl LoroDoc { self.doc.set_record_timestamp(record); } - /// Set the interval of mergeable changes, in milliseconds. + /// Set the interval of mergeable changes, **in seconds**. /// /// If two continuous local changes are within the interval, they will be merged into one change. /// The default value is 1000 seconds. + /// + /// By default, we record timestamps in seconds for each change. So if the merge interval is 1, and changes A and B + /// have timestamps of 3 and 4 respectively, then they will be merged into one change #[inline] pub fn set_change_merge_interval(&self, interval: i64) { self.doc.set_change_merge_interval(interval); @@ -249,6 +252,8 @@ impl LoroDoc { } /// Set commit message for the current uncommitted changes + /// + /// It will be persisted. pub fn set_next_commit_message(&self, msg: &str) { self.doc.set_next_commit_message(msg) } @@ -291,6 +296,32 @@ impl LoroDoc { serde_json::to_string(&json).unwrap() } + /// Export the current state with json-string format of the document, without peer compression. + /// + /// Compared to [`export_json_updates`], this method does not compress the peer IDs in the updates. + /// So the operations are easier to be processed by application code. + #[inline] + pub fn export_json_updates_without_peer_compression( + &self, + start_vv: &VersionVector, + end_vv: &VersionVector, + ) -> String { + let json = self + .doc + .export_json_updates_without_peer_compression(&start_vv.into(), &end_vv.into()); + serde_json::to_string(&json).unwrap() + } + + /// Export the readable [`Change`]s in the given [`IdSpan`] + // TODO: swift type + pub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec { + self.doc + .export_json_in_id_span(id_span) + .into_iter() + .map(|x| serde_json::to_string(&x).unwrap()) + .collect() + } + // TODO: add export method /// Export all the ops not included in the given `VersionVector` #[inline] @@ -464,6 +495,58 @@ impl LoroDoc { .map(|x| Arc::new(x) as Arc) } + /// + /// The path can be specified in different ways depending on the container type: + /// + /// For Tree: + /// 1. Using node IDs: `tree/{node_id}/property` + /// 2. Using indices: `tree/0/1/property` + /// + /// For List and MovableList: + /// - Using indices: `list/0` or `list/1/property` + /// + /// For Map: + /// - Using keys: `map/key` or `map/nested/property` + /// + /// For tree structures, index-based paths follow depth-first traversal order. + /// The indices start from 0 and represent the position of a node among its siblings. + /// + /// # Examples + /// ``` + /// # use loro::{LoroDoc, LoroValue}; + /// let doc = LoroDoc::new(); + /// + /// // Tree example + /// let tree = doc.get_tree("tree"); + /// let root = tree.create(None).unwrap(); + /// tree.get_meta(root).unwrap().insert("name", "root").unwrap(); + /// // Access tree by ID or index + /// let name1 = doc.get_by_str_path(&format!("tree/{}/name", root)).unwrap().into_value().unwrap(); + /// let name2 = doc.get_by_str_path("tree/0/name").unwrap().into_value().unwrap(); + /// assert_eq!(name1, name2); + /// + /// // List example + /// let list = doc.get_list("list"); + /// list.insert(0, "first").unwrap(); + /// list.insert(1, "second").unwrap(); + /// // Access list by index + /// let item = doc.get_by_str_path("list/0"); + /// assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "first".into()); + /// + /// // Map example + /// let map = doc.get_map("map"); + /// map.insert("key", "value").unwrap(); + /// // Access map by key + /// let value = doc.get_by_str_path("map/key"); + /// assert_eq!(value.unwrap().into_value().unwrap().into_string().unwrap(), "value".into()); + /// + /// // MovableList example + /// let mlist = doc.get_movable_list("mlist"); + /// mlist.insert(0, "item").unwrap(); + /// // Access movable list by index + /// let item = doc.get_by_str_path("mlist/0"); + /// assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "item".into()); + /// ``` pub fn get_by_str_path(&self, path: &str) -> Option> { self.doc .get_by_str_path(path) @@ -616,6 +699,38 @@ impl LoroDoc { pub fn get_pending_txn_len(&self) -> u32 { self.doc.get_pending_txn_len() as u32 } + + /// Find the operation id spans that between the `from` version and the `to` version. + #[inline] + pub fn find_id_spans_between(&self, from: &Frontiers, to: &Frontiers) -> VersionVectorDiff { + self.doc + .find_id_spans_between(&from.into(), &to.into()) + .into() + } + + /// Revert the current document state back to the target version + /// + /// Internally, it will generate a series of local operations that can revert the + /// current doc to the target version. It will calculate the diff between the current + /// state and the target state, and apply the diff to the current state. + #[inline] + pub fn revert_to(&self, version: &Frontiers) -> LoroResult<()> { + self.doc.revert_to(&version.into()) + } + + /// Apply a diff to the current document state. + /// + /// Internally, it will apply the diff to the current state. + #[inline] + pub fn apply_diff(&self, diff: DiffBatch) -> LoroResult<()> { + self.doc.apply_diff(diff.into()) + } + + /// Calculate the diff between two versions + #[inline] + pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult { + self.doc.diff(&a.into(), &b.into()).map(|x| x.into()) + } } pub trait ChangeAncestorsTraveler: Sync + Send { diff --git a/crates/loro-ffi/src/event.rs b/crates/loro-ffi/src/event.rs index 5609bf96b..0abb5aa2d 100644 --- a/crates/loro-ffi/src/event.rs +++ b/crates/loro-ffi/src/event.rs @@ -1,8 +1,14 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{ + borrow::Cow, + collections::HashMap, + sync::{Arc, Mutex}, +}; -use loro::{EventTriggerKind, TreeID}; +use loro::{EventTriggerKind, FractionalIndex, TreeID}; -use crate::{ContainerID, LoroValue, TreeParentId, ValueOrContainer}; +use crate::{ + convert_trait_to_v_or_container, ContainerID, LoroValue, TreeParentId, ValueOrContainer, +}; pub trait Subscriber: Sync + Send { fn on_diff(&self, diff: DiffEvent); @@ -135,6 +141,76 @@ impl From for TextDelta { } } +impl From for loro::event::ListDiffItem { + fn from(value: ListDiffItem) -> Self { + match value { + ListDiffItem::Insert { insert, is_move } => loro::event::ListDiffItem::Insert { + insert: insert + .into_iter() + .map(convert_trait_to_v_or_container) + .collect(), + is_move, + }, + ListDiffItem::Delete { delete } => loro::event::ListDiffItem::Delete { + delete: delete as usize, + }, + ListDiffItem::Retain { retain } => loro::event::ListDiffItem::Retain { + retain: retain as usize, + }, + } + } +} + +impl From for loro::event::MapDelta<'static> { + fn from(value: MapDelta) -> Self { + loro::event::MapDelta { + updated: value + .updated + .into_iter() + .map(|(k, v)| (Cow::Owned(k), v.map(convert_trait_to_v_or_container))) + .collect(), + } + } +} + +impl From for loro::TreeDiffItem { + fn from(value: TreeDiffItem) -> Self { + let target: TreeID = value.target; + let action = match value.action { + TreeExternalDiff::Create { + parent, + index, + fractional_index, + } => loro::TreeExternalDiff::Create { + parent: parent.into(), + index: index as usize, + position: FractionalIndex::from_hex_string(fractional_index), + }, + TreeExternalDiff::Move { + parent, + index, + fractional_index, + old_parent, + old_index, + } => loro::TreeExternalDiff::Move { + parent: parent.into(), + index: index as usize, + position: FractionalIndex::from_hex_string(fractional_index), + old_parent: old_parent.into(), + old_index: old_index as usize, + }, + TreeExternalDiff::Delete { + old_parent, + old_index, + } => loro::TreeExternalDiff::Delete { + old_parent: old_parent.into(), + old_index: old_index as usize, + }, + }; + loro::TreeDiffItem { target, action } + } +} + pub enum ListDiffItem { /// Insert a new element into the list. Insert { @@ -263,40 +339,9 @@ impl From<&loro::event::Diff<'_>> for Diff { } Diff::List { diff: ans } } - loro::event::Diff::Text(t) => { - let mut ans = Vec::new(); - for item in t.iter() { - match item { - loro::TextDelta::Retain { retain, attributes } => { - ans.push(TextDelta::Retain { - retain: *retain as u32, - attributes: attributes.as_ref().map(|a| { - a.iter() - .map(|(k, v)| (k.to_string(), v.clone().into())) - .collect() - }), - }); - } - loro::TextDelta::Insert { insert, attributes } => { - ans.push(TextDelta::Insert { - insert: insert.to_string(), - attributes: attributes.as_ref().map(|a| { - a.iter() - .map(|(k, v)| (k.to_string(), v.clone().into())) - .collect() - }), - }); - } - loro::TextDelta::Delete { delete } => { - ans.push(TextDelta::Delete { - delete: *delete as u32, - }); - } - } - } - - Diff::Text { diff: ans } - } + loro::event::Diff::Text(t) => Diff::Text { + diff: t.iter().map(|i| i.clone().into()).collect(), + }, loro::event::Diff::Map(m) => { let mut updated = HashMap::new(); for (key, value) in m.updated.iter() { @@ -359,3 +404,60 @@ impl From<&loro::event::Diff<'_>> for Diff { } } } + +impl From for loro::event::Diff<'static> { + fn from(value: Diff) -> Self { + match value { + Diff::List { diff } => { + loro::event::Diff::List(diff.into_iter().map(|i| i.into()).collect()) + } + Diff::Text { diff } => { + loro::event::Diff::Text(diff.into_iter().map(|i| i.into()).collect()) + } + Diff::Map { diff } => loro::event::Diff::Map(diff.into()), + Diff::Tree { diff } => loro::event::Diff::Tree(Cow::Owned(loro::TreeDiff { + diff: diff.diff.into_iter().map(|i| i.into()).collect(), + })), + Diff::Counter { diff } => loro::event::Diff::Counter(diff), + Diff::Unknown => loro::event::Diff::Unknown, + } + } +} + +#[derive(Debug, Default)] +pub struct DiffBatch(Mutex); + +impl DiffBatch { + pub fn new() -> Self { + Self(Default::default()) + } + + pub fn push(&self, cid: ContainerID, diff: Diff) -> Option { + let mut batch = self.0.lock().unwrap(); + if let Err(diff) = batch.push(cid.into(), diff.into()) { + Some((&diff).into()) + } else { + None + } + } + + pub fn diffs(&self) -> Vec<(ContainerID, Diff)> { + let batch = self.0.lock().unwrap(); + batch + .iter() + .map(|(id, diff)| (id.into(), diff.into())) + .collect() + } +} + +impl From for loro::event::DiffBatch { + fn from(value: DiffBatch) -> Self { + value.0.into_inner().unwrap() + } +} + +impl From for DiffBatch { + fn from(value: loro::event::DiffBatch) -> Self { + Self(Mutex::new(value)) + } +} diff --git a/crates/loro-ffi/src/lib.rs b/crates/loro-ffi/src/lib.rs index bd856b2dd..3c0a124a5 100644 --- a/crates/loro-ffi/src/lib.rs +++ b/crates/loro-ffi/src/lib.rs @@ -41,6 +41,7 @@ pub trait ValueOrContainer: Send + Sync { fn is_value(&self) -> bool; fn is_container(&self) -> bool; fn as_value(&self) -> Option; + fn container_type(&self) -> Option; fn as_container(&self) -> Option; fn as_loro_list(&self) -> Option>; fn as_loro_text(&self) -> Option>; @@ -48,6 +49,7 @@ pub trait ValueOrContainer: Send + Sync { fn as_loro_movable_list(&self) -> Option>; fn as_loro_tree(&self) -> Option>; fn as_loro_counter(&self) -> Option>; + fn as_unknown(&self) -> Option>; } impl ValueOrContainer for loro::ValueOrContainer { @@ -59,12 +61,17 @@ impl ValueOrContainer for loro::ValueOrContainer { loro::ValueOrContainer::is_container(self) } + fn container_type(&self) -> Option { + loro::ValueOrContainer::as_container(self).map(|c| c.id().container_type().into()) + } + fn as_value(&self) -> Option { loro::ValueOrContainer::as_value(self) .cloned() .map(LoroValue::from) } + // TODO: pass Container to Swift fn as_container(&self) -> Option { loro::ValueOrContainer::as_container(self).map(|c| c.id().into()) } @@ -122,4 +129,37 @@ impl ValueOrContainer for loro::ValueOrContainer { _ => None, } } + + fn as_unknown(&self) -> Option> { + match self { + loro::ValueOrContainer::Container(Container::Unknown(c)) => { + Some(Arc::new(LoroUnknown { unknown: c.clone() })) + } + _ => None, + } + } +} + +fn convert_trait_to_v_or_container>(i: T) -> loro::ValueOrContainer { + let v = i.as_ref(); + if v.is_value() { + loro::ValueOrContainer::Value(v.as_value().unwrap().into()) + } else { + let container = match v.container_type().unwrap() { + ContainerType::List => Container::List((*v.as_loro_list().unwrap()).clone().list), + ContainerType::Text => Container::Text((*v.as_loro_text().unwrap()).clone().text), + ContainerType::Map => Container::Map((*v.as_loro_map().unwrap()).clone().map), + ContainerType::MovableList => { + Container::MovableList((*v.as_loro_movable_list().unwrap()).clone().list) + } + ContainerType::Tree => Container::Tree((*v.as_loro_tree().unwrap()).clone().tree), + ContainerType::Counter => { + Container::Counter((*v.as_loro_counter().unwrap()).clone().counter) + } + ContainerType::Unknown { kind: _ } => { + Container::Unknown((*v.as_unknown().unwrap()).clone().unknown) + } + }; + loro::ValueOrContainer::Container(container) + } } diff --git a/crates/loro/src/event.rs b/crates/loro/src/event.rs index b663330d9..7711d44c1 100644 --- a/crates/loro/src/event.rs +++ b/crates/loro/src/event.rs @@ -4,7 +4,8 @@ use delta::DeltaRope; use enum_as_inner::EnumAsInner; use loro_common::IdLp; use loro_internal::container::ContainerID; -use loro_internal::delta::{ResolvedMapDelta, ResolvedMapValue, TreeDiff}; +pub use loro_internal::delta::TreeDiff; +use loro_internal::delta::{ResolvedMapDelta, ResolvedMapValue}; use loro_internal::event::{EventTriggerKind, ListDeltaMeta}; use loro_internal::handler::{TextDelta, ValueOrHandler}; use loro_internal::undo::DiffBatch as InnerDiffBatch; diff --git a/crates/loro/tests/loro_rust_test.rs b/crates/loro/tests/loro_rust_test.rs index b25379d91..25c18da78 100644 --- a/crates/loro/tests/loro_rust_test.rs +++ b/crates/loro/tests/loro_rust_test.rs @@ -2960,6 +2960,7 @@ fn test_diff_apply_with_unknown_container() -> LoroResult<()> { Ok(()) } +#[test] fn test_set_merge_interval() { let doc = LoroDoc::new(); doc.set_record_timestamp(true); From 0cf0833aaaa50350dfabf57d142e7cfc27a6e1dc Mon Sep 17 00:00:00 2001 From: Leon Zhao Date: Wed, 15 Jan 2025 12:03:05 +0800 Subject: [PATCH 2/6] fix: clippy --- .../loro-internal/src/container/tree/bool_rle_vec.rs | 2 +- crates/loro-internal/src/handler.rs | 10 ++-------- crates/loro-internal/src/state/tree_state.rs | 4 +--- crates/loro-internal/src/version/frontiers.rs | 2 +- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/crates/loro-internal/src/container/tree/bool_rle_vec.rs b/crates/loro-internal/src/container/tree/bool_rle_vec.rs index 16762cc52..4f6a31cc6 100644 --- a/crates/loro-internal/src/container/tree/bool_rle_vec.rs +++ b/crates/loro-internal/src/container/tree/bool_rle_vec.rs @@ -77,7 +77,7 @@ impl BoolRleVec { self.len -= n; // Remove any trailing zero-length runs - while self.rle_vec.last().map_or(false, |&x| x == 0) { + while self.rle_vec.last().is_some_and(|&x| x == 0) { self.rle_vec.pop(); } } diff --git a/crates/loro-internal/src/handler.rs b/crates/loro-internal/src/handler.rs index 92f59ccd5..0e3dc9848 100644 --- a/crates/loro-internal/src/handler.rs +++ b/crates/loro-internal/src/handler.rs @@ -1615,10 +1615,7 @@ impl TextHandler { match &self.inner { MaybeDetached::Detached(t) => { let mut t = t.try_lock().unwrap(); - let ranges = match t.value.get_text_entity_ranges(pos, len, PosType::Bytes) { - Err(x) => return Err(x), - Ok(x) => x, - }; + let ranges = t.value.get_text_entity_ranges(pos, len, PosType::Bytes)?; for range in ranges.iter().rev() { t.value .drain_by_entity_index(range.entity_start, range.entity_len(), None); @@ -1635,10 +1632,7 @@ impl TextHandler { match &self.inner { MaybeDetached::Detached(t) => { let mut t = t.try_lock().unwrap(); - let ranges = match t.value.get_text_entity_ranges(pos, len, PosType::Unicode) { - Err(x) => return Err(x), - Ok(x) => x, - }; + let ranges = t.value.get_text_entity_ranges(pos, len, PosType::Unicode)?; for range in ranges.iter().rev() { t.value .drain_by_entity_index(range.entity_start, range.entity_len(), None); diff --git a/crates/loro-internal/src/state/tree_state.rs b/crates/loro-internal/src/state/tree_state.rs index a3a4976b7..9cfee5438 100644 --- a/crates/loro-internal/src/state/tree_state.rs +++ b/crates/loro-internal/src/state/tree_state.rs @@ -903,9 +903,7 @@ impl TreeState { /// /// O(1) pub fn is_parent(&self, target: &TreeID, parent: &TreeParentId) -> bool { - self.trees - .get(target) - .map_or(false, |x| x.parent == *parent) + self.trees.get(target).is_some_and(|x| x.parent == *parent) } /// Delete the position cache of the node diff --git a/crates/loro-internal/src/version/frontiers.rs b/crates/loro-internal/src/version/frontiers.rs index 87da4362b..46e89b40c 100644 --- a/crates/loro-internal/src/version/frontiers.rs +++ b/crates/loro-internal/src/version/frontiers.rs @@ -66,7 +66,7 @@ impl InternalMap { fn contains(&self, id: &ID) -> bool { self.0 .get(&id.peer) - .map_or(false, |&counter| counter == id.counter) + .is_some_and(|&counter| counter == id.counter) } fn insert(&mut self, id: ID) { From e0b1973c832275b9ad83fa39e14e29c047104d17 Mon Sep 17 00:00:00 2001 From: Leon Zhao Date: Wed, 15 Jan 2025 12:03:48 +0800 Subject: [PATCH 3/6] fix: name --- crates/loro-ffi/src/event.rs | 2 +- crates/loro-ffi/src/lib.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/loro-ffi/src/event.rs b/crates/loro-ffi/src/event.rs index 0abb5aa2d..250b07910 100644 --- a/crates/loro-ffi/src/event.rs +++ b/crates/loro-ffi/src/event.rs @@ -441,7 +441,7 @@ impl DiffBatch { } } - pub fn diffs(&self) -> Vec<(ContainerID, Diff)> { + pub fn get_diff(&self) -> Vec<(ContainerID, Diff)> { let batch = self.0.lock().unwrap(); batch .iter() diff --git a/crates/loro-ffi/src/lib.rs b/crates/loro-ffi/src/lib.rs index 3c0a124a5..d3365ba13 100644 --- a/crates/loro-ffi/src/lib.rs +++ b/crates/loro-ffi/src/lib.rs @@ -49,7 +49,7 @@ pub trait ValueOrContainer: Send + Sync { fn as_loro_movable_list(&self) -> Option>; fn as_loro_tree(&self) -> Option>; fn as_loro_counter(&self) -> Option>; - fn as_unknown(&self) -> Option>; + fn as_loro_unknown(&self) -> Option>; } impl ValueOrContainer for loro::ValueOrContainer { @@ -130,7 +130,7 @@ impl ValueOrContainer for loro::ValueOrContainer { } } - fn as_unknown(&self) -> Option> { + fn as_loro_unknown(&self) -> Option> { match self { loro::ValueOrContainer::Container(Container::Unknown(c)) => { Some(Arc::new(LoroUnknown { unknown: c.clone() })) @@ -157,7 +157,7 @@ fn convert_trait_to_v_or_container>(i: T) -> loro Container::Counter((*v.as_loro_counter().unwrap()).clone().counter) } ContainerType::Unknown { kind: _ } => { - Container::Unknown((*v.as_unknown().unwrap()).clone().unknown) + Container::Unknown((*v.as_loro_unknown().unwrap()).clone().unknown) } }; loro::ValueOrContainer::Container(container) From 7cd28279a91151526525bd51f38869598ff32b5e Mon Sep 17 00:00:00 2001 From: Leon Zhao Date: Wed, 15 Jan 2025 14:09:20 +0800 Subject: [PATCH 4/6] fix: ffi compatible --- crates/loro-ffi/src/doc.rs | 10 ++++++---- crates/loro-ffi/src/event.rs | 30 ++++++++++++++++++++++++------ crates/loro-ffi/src/lib.rs | 4 ++-- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/crates/loro-ffi/src/doc.rs b/crates/loro-ffi/src/doc.rs index 79cb8be99..0b1ed8afd 100644 --- a/crates/loro-ffi/src/doc.rs +++ b/crates/loro-ffi/src/doc.rs @@ -722,14 +722,16 @@ impl LoroDoc { /// /// Internally, it will apply the diff to the current state. #[inline] - pub fn apply_diff(&self, diff: DiffBatch) -> LoroResult<()> { - self.doc.apply_diff(diff.into()) + pub fn apply_diff(&self, diff: &DiffBatch) -> LoroResult<()> { + self.doc.apply_diff(diff.clone().into()) } /// Calculate the diff between two versions #[inline] - pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult { - self.doc.diff(&a.into(), &b.into()).map(|x| x.into()) + pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult> { + self.doc + .diff(&a.into(), &b.into()) + .map(|x| Arc::new(x.into())) } } diff --git a/crates/loro-ffi/src/event.rs b/crates/loro-ffi/src/event.rs index 250b07910..ffd32f449 100644 --- a/crates/loro-ffi/src/event.rs +++ b/crates/loro-ffi/src/event.rs @@ -424,14 +424,17 @@ impl From for loro::event::Diff<'static> { } } -#[derive(Debug, Default)] -pub struct DiffBatch(Mutex); +#[derive(Debug, Clone, Default)] +pub struct DiffBatch(Arc>); impl DiffBatch { pub fn new() -> Self { Self(Default::default()) } + /// Push a new event to the batch. + /// + /// If the cid already exists in the batch, return Err pub fn push(&self, cid: ContainerID, diff: Diff) -> Option { let mut batch = self.0.lock().unwrap(); if let Err(diff) = batch.push(cid.into(), diff.into()) { @@ -441,23 +444,38 @@ impl DiffBatch { } } - pub fn get_diff(&self) -> Vec<(ContainerID, Diff)> { + /// Returns an iterator over the diffs in this batch, in the order they were added. + /// + /// The iterator yields tuples of `(&ContainerID, &Diff)` where: + /// - `ContainerID` is the ID of the container that was modified + /// - `Diff` contains the actual changes made to that container + /// + /// The order of the diffs is preserved from when they were originally added to the batch. + pub fn get_diff(&self) -> Vec { let batch = self.0.lock().unwrap(); batch .iter() - .map(|(id, diff)| (id.into(), diff.into())) + .map(|(id, diff)| ContainerIDAndDiff { + cid: id.into(), + diff: diff.into(), + }) .collect() } } impl From for loro::event::DiffBatch { fn from(value: DiffBatch) -> Self { - value.0.into_inner().unwrap() + value.0.lock().unwrap().clone() } } impl From for DiffBatch { fn from(value: loro::event::DiffBatch) -> Self { - Self(Mutex::new(value)) + Self(Arc::new(Mutex::new(value))) } } + +pub struct ContainerIDAndDiff { + pub cid: ContainerID, + pub diff: Diff, +} diff --git a/crates/loro-ffi/src/lib.rs b/crates/loro-ffi/src/lib.rs index d3365ba13..902be1dd6 100644 --- a/crates/loro-ffi/src/lib.rs +++ b/crates/loro-ffi/src/lib.rs @@ -24,8 +24,8 @@ pub use container::{ }; mod event; pub use event::{ - ContainerDiff, Diff, DiffEvent, Index, ListDiffItem, MapDelta, PathItem, Subscriber, TextDelta, - TreeDiff, TreeDiffItem, TreeExternalDiff, + ContainerDiff, ContainerIDAndDiff, Diff, DiffBatch, DiffEvent, Index, ListDiffItem, MapDelta, + PathItem, Subscriber, TextDelta, TreeDiff, TreeDiffItem, TreeExternalDiff, }; mod undo; pub use undo::{AbsolutePosition, CursorWithPos, OnPop, OnPush, UndoItemMeta, UndoManager}; From 075d8039587bfa02762587ee40bcd01ffe8decc0 Mon Sep 17 00:00:00 2001 From: Leon Zhao Date: Wed, 15 Jan 2025 14:11:54 +0800 Subject: [PATCH 5/6] fix: export EncodedBlobMode --- crates/loro/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/loro/src/lib.rs b/crates/loro/src/lib.rs index ea1a46aa1..23dd232af 100644 --- a/crates/loro/src/lib.rs +++ b/crates/loro/src/lib.rs @@ -45,8 +45,8 @@ pub use loro_internal::container::richtext::ExpandType; pub use loro_internal::container::{ContainerID, ContainerType, IntoContainerId}; pub use loro_internal::cursor; pub use loro_internal::delta::{TreeDeltaItem, TreeDiff, TreeDiffItem, TreeExternalDiff}; -pub use loro_internal::encoding::ExportMode; pub use loro_internal::encoding::ImportBlobMetadata; +pub use loro_internal::encoding::{EncodedBlobMode, ExportMode}; pub use loro_internal::event::{EventTriggerKind, Index}; pub use loro_internal::handler::TextDelta; pub use loro_internal::json; @@ -221,7 +221,7 @@ impl LoroDoc { /// /// If two continuous local changes are within the interval, they will be merged into one change. /// The default value is 1000 seconds. - /// + /// /// By default, we record timestamps in seconds for each change. So if the merge interval is 1, and changes A and B /// have timestamps of 3 and 4 respectively, then they will be merged into one change #[inline] From 800a6d360f2fecbb1f2b48630d12285d04ee7427 Mon Sep 17 00:00:00 2001 From: Leon Zhao Date: Wed, 15 Jan 2025 14:35:33 +0800 Subject: [PATCH 6/6] fix: rename vv diff --- crates/loro-ffi/src/version.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/loro-ffi/src/version.rs b/crates/loro-ffi/src/version.rs index 42e9835dc..69fb78064 100644 --- a/crates/loro-ffi/src/version.rs +++ b/crates/loro-ffi/src/version.rs @@ -125,16 +125,16 @@ impl Default for Frontiers { pub struct VersionVectorDiff { /// need to add these spans to move from right to left - pub left: HashMap, + pub retreat: HashMap, /// need to add these spans to move from left to right - pub right: HashMap, + pub forward: HashMap, } impl From for VersionVectorDiff { fn from(value: loro::VersionVectorDiff) -> Self { Self { - left: value.retreat.into_iter().collect(), - right: value.forward.into_iter().collect(), + retreat: value.retreat.into_iter().collect(), + forward: value.forward.into_iter().collect(), } } }