Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: find id spans between #607

Merged
merged 8 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tame-spies-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"loro-crdt": minor
---

feat: find id spans between #607
4 changes: 2 additions & 2 deletions crates/loro-ffi/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ pub struct VersionVectorDiff {
impl From<loro::VersionVectorDiff> for VersionVectorDiff {
fn from(value: loro::VersionVectorDiff) -> Self {
Self {
left: value.left.into_iter().collect(),
right: value.right.into_iter().collect(),
left: value.retreat.into_iter().collect(),
right: value.forward.into_iter().collect(),
}
}
}
Expand Down
9 changes: 4 additions & 5 deletions crates/loro-internal/src/dag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ impl<T: Dag + ?Sized> DagUtils for T {

fn find_path(&self, from: &Frontiers, to: &Frontiers) -> VersionVectorDiff {
let mut ans = VersionVectorDiff::default();
trace!("find_path from={:?} to={:?}", from, to);
if from == to {
return ans;
}
Expand All @@ -112,12 +111,12 @@ impl<T: Dag + ?Sized> DagUtils for T {
let to_span = self.get(to).unwrap();
if from_span.id_start() == to_span.id_start() {
if from.counter < to.counter {
ans.right.insert(
ans.forward.insert(
from.peer,
CounterSpan::new(from.counter + 1, to.counter + 1),
);
} else {
ans.left.insert(
ans.retreat.insert(
from.peer,
CounterSpan::new(to.counter + 1, from.counter + 1),
);
Expand All @@ -128,7 +127,7 @@ impl<T: Dag + ?Sized> DagUtils for T {
if from_span.deps().len() == 1
&& to_span.contains_id(from_span.deps().as_single().unwrap())
{
ans.left.insert(
ans.retreat.insert(
from.peer,
CounterSpan::new(to.counter + 1, from.counter + 1),
);
Expand All @@ -138,7 +137,7 @@ impl<T: Dag + ?Sized> DagUtils for T {
if to_span.deps().len() == 1
&& from_span.contains_id(to_span.deps().as_single().unwrap())
{
ans.right.insert(
ans.forward.insert(
from.peer,
CounterSpan::new(from.counter + 1, to.counter + 1),
);
Expand Down
4 changes: 2 additions & 2 deletions crates/loro-internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ pub use encoding::json_schema::json;
pub use fractional_index::FractionalIndex;
pub use loro_common::{loro_value, to_value};
pub use loro_common::{
Counter, CounterSpan, IdLp, IdSpan, Lamport, LoroEncodeError, LoroError, LoroResult,
LoroTreeError, PeerID, TreeID, ID,
Counter, CounterSpan, IdLp, IdSpan, IdSpanVector, Lamport, LoroEncodeError, LoroError,
LoroResult, LoroTreeError, PeerID, TreeID, ID,
};
pub use loro_common::{LoroBinaryValue, LoroListValue, LoroMapValue, LoroStringValue};
#[cfg(feature = "wasm")]
Expand Down
9 changes: 7 additions & 2 deletions crates/loro-internal/src/loro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::{
IntoContainerId,
},
cursor::{AbsolutePosition, CannotFindRelativePosition, Cursor, PosQueryResult},
dag::Dag,
dag::{Dag, DagUtils},
diff_calc::DiffCalculator,
encoding::{
self, decode_snapshot, export_fast_snapshot, export_fast_updates,
Expand All @@ -49,7 +49,7 @@ use crate::{
txn::Transaction,
undo::DiffBatch,
utils::subscription::{SubscriberSetWithQueue, Subscription},
version::{shrink_frontiers, Frontiers, ImVersionVector, VersionRange},
version::{shrink_frontiers, Frontiers, ImVersionVector, VersionRange, VersionVectorDiff},
ChangeMeta, DocDiff, HandlerTrait, InternalString, ListHandler, LoroError, MapHandler,
VersionVector,
};
Expand Down Expand Up @@ -1635,6 +1635,11 @@ impl LoroDoc {
0
}
}

#[inline]
pub fn find_id_spans_between(&self, from: &Frontiers, to: &Frontiers) -> VersionVectorDiff {
self.oplog().try_lock().unwrap().dag.find_path(from, to)
}
}

#[derive(Debug, thiserror::Error)]
Expand Down
2 changes: 1 addition & 1 deletion crates/loro-internal/src/oplog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ impl OpLog {

let common_ancestors_vv = self.dag.frontiers_to_vv(&common_ancestors).unwrap();
// go from lca to merged_vv
let diff = common_ancestors_vv.diff(&merged_vv).right;
let diff = common_ancestors_vv.diff(&merged_vv).forward;
let mut iter = self.dag.iter_causal(common_ancestors, diff);
let mut node = iter.next();
let mut cur_cnt = 0;
Expand Down
32 changes: 18 additions & 14 deletions crates/loro-internal/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,42 +300,46 @@ impl Deref for VersionVector {

#[derive(Default, Debug, PartialEq, Eq)]
pub struct VersionVectorDiff {
/// need to add these spans to move from right to left
pub left: IdSpanVector,
/// need to add these spans to move from left to right
pub right: IdSpanVector,
/// The spans that the `left` side needs to retreat to reach the `right` side
///
/// these spans are included in the left, but not in the right
pub retreat: IdSpanVector,
/// The spans that the `left` side needs to forward to reach the `right` side
///
/// these spans are included in the right, but not in the left
pub forward: IdSpanVector,
}

impl VersionVectorDiff {
#[inline]
pub fn merge_left(&mut self, span: IdSpan) {
merge(&mut self.left, span);
merge(&mut self.retreat, span);
}

#[inline]
pub fn merge_right(&mut self, span: IdSpan) {
merge(&mut self.right, span);
merge(&mut self.forward, span);
}

#[inline]
pub fn subtract_start_left(&mut self, span: IdSpan) {
subtract_start(&mut self.left, span);
subtract_start(&mut self.retreat, span);
}

#[inline]
pub fn subtract_start_right(&mut self, span: IdSpan) {
subtract_start(&mut self.right, span);
subtract_start(&mut self.forward, span);
}

pub fn get_id_spans_left(&self) -> impl Iterator<Item = IdSpan> + '_ {
self.left.iter().map(|(peer, span)| IdSpan {
self.retreat.iter().map(|(peer, span)| IdSpan {
peer: *peer,
counter: *span,
})
}

pub fn get_id_spans_right(&self) -> impl Iterator<Item = IdSpan> + '_ {
self.right.iter().map(|(peer, span)| IdSpan {
self.forward.iter().map(|(peer, span)| IdSpan {
peer: *peer,
counter: *span,
})
Expand Down Expand Up @@ -457,7 +461,7 @@ impl VersionVector {
if let Some(&rhs_counter) = rhs.get(client_id) {
match counter.cmp(&rhs_counter) {
Ordering::Less => {
ans.right.insert(
ans.forward.insert(
*client_id,
CounterSpan {
start: counter,
Expand All @@ -466,7 +470,7 @@ impl VersionVector {
);
}
Ordering::Greater => {
ans.left.insert(
ans.retreat.insert(
*client_id,
CounterSpan {
start: rhs_counter,
Expand All @@ -477,7 +481,7 @@ impl VersionVector {
Ordering::Equal => {}
}
} else {
ans.left.insert(
ans.retreat.insert(
*client_id,
CounterSpan {
start: 0,
Expand All @@ -488,7 +492,7 @@ impl VersionVector {
}
for (client_id, &rhs_counter) in rhs.iter() {
if !self.contains_key(client_id) {
ans.right.insert(
ans.forward.insert(
*client_id,
CounterSpan {
start: 0,
Expand Down
79 changes: 48 additions & 31 deletions crates/loro-wasm/deno_tests/basic.test.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,60 @@
import init, { initSync, LoroDoc, LoroMap } from "../web/loro_wasm.js";
import init, { initSync, LoroDoc, LoroMap, LoroText } from "../web/index.js";
import { expect } from "npm:expect";

await init();

Deno.test("basic", () => {
const doc = new LoroDoc();
doc.getText("text").insert(0, "Hello, world!");
expect(doc.getText("text").toString()).toBe("Hello, world!");
const doc = new LoroDoc();
doc.getText("text").insert(0, "Hello, world!");
expect(doc.getText("text").toString()).toBe("Hello, world!");
});

Deno.test("fork when detached", () => {
const doc = new LoroDoc();
doc.setPeerId("0");
doc.getText("text").insert(0, "Hello, world!");
doc.checkout([{ peer: "0", counter: 5 }]);
const newDoc = doc.fork();
newDoc.setPeerId("1");
newDoc.getText("text").insert(6, " Alice!");
// ┌───────────────┐ ┌───────────────┐
// │ Hello, │◀─┬──│ world! │
// └───────────────┘ │ └───────────────┘
// │
// │ ┌───────────────┐
// └──│ Alice! │
// └───────────────┘
doc.import(newDoc.export({ mode: "update" }));
doc.checkoutToLatest();
console.log(doc.getText("text").toString()); // "Hello, world! Alice!"
const doc: LoroDoc = new LoroDoc();
doc.setPeerId("0");
doc.getText("text").insert(0, "Hello, world!");
doc.checkout([{ peer: "0", counter: 5 }]);
const newDoc = doc.fork();
newDoc.setPeerId("1");
newDoc.getText("text").insert(6, " Alice!");
// ┌───────────────┐ ┌───────────────┐
// │ Hello, │◀─┬──│ world! │
// └───────────────┘ │ └───────────────┘
// │
// │ ┌───────────────┐
// └──│ Alice! │
// └───────────────┘
doc.import(newDoc.export({ mode: "update" }));
doc.checkoutToLatest();
console.log(doc.getText("text").toString()); // "Hello, world! Alice!"
});

Deno.test("isDeleted", () => {
const doc = new LoroDoc();
const list = doc.getList("list");
expect(list.isDeleted()).toBe(false);
const tree = doc.getTree("root");
const node = tree.createNode(undefined, undefined);
const containerBefore = node.data.setContainer("container", new LoroMap());
containerBefore.set("A", "B");
tree.delete(node.id);
const containerAfter = doc.getContainerById(containerBefore.id) as LoroMap;
expect(containerAfter.isDeleted()).toBe(true);
const doc = new LoroDoc();
const list = doc.getList("list");
expect(list.isDeleted()).toBe(false);
const tree = doc.getTree("root");
const node = tree.createNode(undefined, undefined);
const containerBefore = node.data.setContainer("container", new LoroMap());
containerBefore.set("A", "B");
tree.delete(node.id);
const containerAfter = doc.getContainerById(containerBefore.id) as LoroMap;
expect(containerAfter.isDeleted()).toBe(true);
});

Deno.test("toJsonWithReplacer", () => {
const doc = new LoroDoc();
const text = doc.getText("text");
text.insert(0, "Hello");
text.mark({ start: 0, end: 2 }, "bold", true);

// Use delta to represent text
// @ts-ignore: deno is not very clever
const json = doc.toJsonWithReplacer((key, value) => {
if (value instanceof LoroText) {
return value.toDelta();
}

return value;
});
});
Loading