mirror of
https://github.com/loro-dev/loro.git
synced 2025-01-22 04:45:46 +00:00
feat: export diff batch to ffi
This commit is contained in:
parent
07500dab34
commit
db2353192a
5 changed files with 300 additions and 41 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -34,6 +34,7 @@
|
|||
"pointee",
|
||||
"reparent",
|
||||
"RUSTFLAGS",
|
||||
"serde",
|
||||
"smstring",
|
||||
"sstable",
|
||||
"Stewen",
|
||||
|
|
|
@ -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<String> {
|
||||
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<dyn ValueOrContainer>)
|
||||
}
|
||||
|
||||
///
|
||||
/// 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<Arc<dyn ValueOrContainer>> {
|
||||
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<DiffBatch> {
|
||||
self.doc.diff(&a.into(), &b.into()).map(|x| x.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ChangeAncestorsTraveler: Sync + Send {
|
||||
|
|
|
@ -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<loro::TextDelta> for TextDelta {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ListDiffItem> 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<MapDelta> 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<TreeDiffItem> 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<Diff> 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<loro::event::DiffBatch>);
|
||||
|
||||
impl DiffBatch {
|
||||
pub fn new() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
|
||||
pub fn push(&self, cid: ContainerID, diff: Diff) -> Option<Diff> {
|
||||
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<DiffBatch> for loro::event::DiffBatch {
|
||||
fn from(value: DiffBatch) -> Self {
|
||||
value.0.into_inner().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<loro::event::DiffBatch> for DiffBatch {
|
||||
fn from(value: loro::event::DiffBatch) -> Self {
|
||||
Self(Mutex::new(value))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ pub trait ValueOrContainer: Send + Sync {
|
|||
fn is_value(&self) -> bool;
|
||||
fn is_container(&self) -> bool;
|
||||
fn as_value(&self) -> Option<LoroValue>;
|
||||
fn container_type(&self) -> Option<ContainerType>;
|
||||
fn as_container(&self) -> Option<ContainerID>;
|
||||
fn as_loro_list(&self) -> Option<Arc<LoroList>>;
|
||||
fn as_loro_text(&self) -> Option<Arc<LoroText>>;
|
||||
|
@ -48,6 +49,7 @@ pub trait ValueOrContainer: Send + Sync {
|
|||
fn as_loro_movable_list(&self) -> Option<Arc<LoroMovableList>>;
|
||||
fn as_loro_tree(&self) -> Option<Arc<LoroTree>>;
|
||||
fn as_loro_counter(&self) -> Option<Arc<LoroCounter>>;
|
||||
fn as_unknown(&self) -> Option<Arc<LoroUnknown>>;
|
||||
}
|
||||
|
||||
impl ValueOrContainer for loro::ValueOrContainer {
|
||||
|
@ -59,12 +61,17 @@ impl ValueOrContainer for loro::ValueOrContainer {
|
|||
loro::ValueOrContainer::is_container(self)
|
||||
}
|
||||
|
||||
fn container_type(&self) -> Option<ContainerType> {
|
||||
loro::ValueOrContainer::as_container(self).map(|c| c.id().container_type().into())
|
||||
}
|
||||
|
||||
fn as_value(&self) -> Option<LoroValue> {
|
||||
loro::ValueOrContainer::as_value(self)
|
||||
.cloned()
|
||||
.map(LoroValue::from)
|
||||
}
|
||||
|
||||
// TODO: pass Container to Swift
|
||||
fn as_container(&self) -> Option<ContainerID> {
|
||||
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<Arc<LoroUnknown>> {
|
||||
match self {
|
||||
loro::ValueOrContainer::Container(Container::Unknown(c)) => {
|
||||
Some(Arc::new(LoroUnknown { unknown: c.clone() }))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_trait_to_v_or_container<T: AsRef<dyn ValueOrContainer>>(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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue