Skip to content

Commit

Permalink
fix: fixed compareSeq & merge, README completed
Browse files Browse the repository at this point in the history
  • Loading branch information
joaopereira12 committed Aug 6, 2024
1 parent 5725447 commit de9352a
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 12 deletions.
55 changes: 54 additions & 1 deletion packages/crdt/src/builtins/LSeq/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,57 @@
# LSeq CRDT

# Overview
Implementation of the Linear Sequence (LSeq) Conflict-free Replicated Data Type (CRDT).

## Overview

LSeq is a data structure especially used in collaborative text editors by managing ordered sequences of elements across multiple replicas.
For this to be acomplished, each element has it's own unique identifier called "virtual pointer" (`vPointer`), composed by a byte sequence and the nodeId. The identifiers are used to track each element in the collection.

## Components

### vPointer Type

Represents the unique identifier associated with each `element` in the collection.
Since there's no guarantee that sequences produced on disconnected machines are 100% unique we associate the `nodeId` to create a unique identifier for the `element`.
It's composed by:

- **sequence**: byte sequence indentifier for the `element`
- **nodeId**: node identifier

### Vertex Type

It associates each `vPointer` with the correspondent `element`.
It's composed by:

- **vPointer** : `element's` identifier
- **element**: `element` to be stored

### LSeq Class

Class that manages the operations of the LSeq data structure. It's composed by the `nodeId` and the `vertices` array that stores all the `Vertex` types of the LSeq.
It has the following operations:

- **getVertices()**: returns the `vertices` array
- **getNodeId()**: returns the `nodeId`
- **insert(index, element)**: inserts the `element` in the `vertices` array at the specified `index`
- **delete(index)**: deletes the `element` from the `vertices` array at the specified `index`
- **query()**: return an array with all the elements stored in the `vertices` array
- **merge(otherLSeq)**: merges the `otherLSeq` `vertices` array with the current one.

### Auxiliary functions
This implementation also has the following auxiliary functions:

- **compareSeq(seq1, seq2)**: compares two byte sequences to determine `index` positions
- **generateSeq(lo, hi)**: generates the byte sequence to be between `lo` and `hi`

## How it Works

**1.** When an element is inserted, a new virtual pointer (`vPointer`) is generated. It depends on the sequences of the neighbour elements in the collection.

**2.** After the `vPointer` being created, a `vertex` ( {`vPointer`, `element`} ) is created. So this `element` is identified by this `vPointer`.

**3.** Then, the `vertex` is inserted at the given index in the `vertices` array.

**4.** To delete a `vertex` from the `vertices` array, just pass the index of the `vertex` to be delete.

**5.** When merging two LSeqs, the peer LSeq elements that the current LSeq doesn't contain are inserted into the 'vertices' array in the correct indexes.
21 changes: 13 additions & 8 deletions packages/crdt/src/builtins/LSeq/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type VPointer = {
sequence: number[],
id: string
nodeId: string
};

type Vertex<T> = {
Expand All @@ -25,13 +25,13 @@ export class LSeq<T> {
return this._nodeId;
}

insert(index: number, content: T): void {
insert(index: number, element: T): void {
const left = index === 0 ? [] : this._vertices[index - 1].vPointer.sequence;
const right = index === this._vertices.length ? [] : this._vertices[index].vPointer.sequence;
const pointer = { sequence: generateSeq(left, right), id: this._nodeId };
const idx = this._vertices.findIndex( vertex => compareSeq(vertex.vPointer.sequence, pointer.sequence) >= 0);
const pointer = { sequence: generateSeq(left, right), nodeId: this._nodeId };
const idx = this._vertices.findIndex( vertex => compareSeq(vertex.vPointer.nodeId, pointer.nodeId,vertex.vPointer.sequence, pointer.sequence) >= 0);
const newVertices = this._vertices;
newVertices.splice(idx >= 0 ? idx : this._vertices.length, 0, { vPointer: pointer, element: content });
newVertices.splice(idx >= 0 ? idx : this._vertices.length, 0, { vPointer: pointer, element: element });
this._vertices = newVertices;
}

Expand All @@ -51,21 +51,26 @@ export class LSeq<T> {
const newVertices = this._vertices;
otherLSeq.getVertices().forEach((value) => {
if(!newVertices.some( vertex => vertex.vPointer === value.vPointer)) {
newVertices.splice(otherLSeq._vertices.indexOf(value),0, value);
const idx = otherLSeq.getVertices().findIndex( vertex => compareSeq(vertex.vPointer.nodeId, value.vPointer.nodeId, vertex.vPointer.sequence, value.vPointer.sequence) == 0);
newVertices.splice(idx >= 0 ? idx: this._vertices.length,0, value);
}
});
this._vertices = newVertices;
}
}

function compareSeq(seq1: number[], seq2: number[]): number {
function compareSeq(id1: string, id2: string, seq1: number[], seq2: number[]): number {
const len = Math.min(seq1.length, seq2.length);
for (let i = 0; i < len; i++) {
if (seq1[i] !== seq2[i]) {
return seq1[i] - seq2[i];
}
}
return seq1.length - seq2.length;
let cmp = seq1.length - seq2.length;
if(cmp === 0 ){
cmp = id1.localeCompare(id2);
}
return cmp;
}

function generateSeq(lo: number[], hi: number[]): number[] {
Expand Down
11 changes: 8 additions & 3 deletions packages/crdt/tests/LSeq.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("LSeq Tests", () => {
lseq.insert(2,'mom');
lseq.insert(2,'zom');
lseq.insert(2,'dad');

expect(lseq.query().join('')).toBe("hi*dadzommom!");
});

Expand Down Expand Up @@ -58,8 +58,12 @@ describe("LSeq Tests", () => {

expect(lseq2.query().join('')).toBe("hi*!");

lseq1.insert(2, 'mom');
lseq2.insert(2 ,'dad');
lseq1.insert(2, 'm');
lseq1.insert(2, 'o');
lseq1.insert(2, 'm');
lseq2.insert(2 ,'d');
lseq2.insert(2 ,'a');
lseq2.insert(2 ,'d');

expect(lseq1.query().join('')).toBe("hi*mom!");
expect(lseq2.query().join('')).toBe("hi*dad!");
Expand All @@ -69,5 +73,6 @@ describe("LSeq Tests", () => {

expect(lseq1.query().join('')).toBe("hi*dadmom!");
expect(lseq2.query().join('')).toBe("hi*dadmom!");

});
});

0 comments on commit de9352a

Please sign in to comment.