Skip to content

Commit

Permalink
Merge pull request #90 from boostcampwm2023/BE-feature/crdt-tree
Browse files Browse the repository at this point in the history
ํŠธ๋ฆฌ์— ๋Œ€ํ•œ CRDT ๊ตฌํ˜„
  • Loading branch information
Conut-1 authored Nov 27, 2023
2 parents f3f3584 + 3574292 commit 4e6e6f5
Show file tree
Hide file tree
Showing 10 changed files with 462 additions and 140 deletions.
33 changes: 33 additions & 0 deletions nestjs-BE/crdt/clock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export enum COMPARE {
GREATER,
LESS,
}

export class Clock {
id: string;
counter: number;

constructor(id: string, counter: number = 0) {
this.id = id;
this.counter = counter;
}

increment() {
this.counter++;
}

copy(): Clock {
return new Clock(this.id, this.counter);
}

merge(remoteClock: Clock): Clock {
return new Clock(this.id, Math.max(this.counter, remoteClock.counter));
}

compare(remoteClock: Clock): COMPARE {
if (this.counter > remoteClock.counter) return COMPARE.GREATER;
if (this.counter === remoteClock.counter && this.id > remoteClock.id)
return COMPARE.GREATER;
return COMPARE.LESS;
}
}
206 changes: 206 additions & 0 deletions nestjs-BE/crdt/crdt-tree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { Clock, COMPARE } from './clock';
import {
Operation,
OperationAdd,
OperationDelete,
OperationInput,
OperationLog,
OperationMove,
OperationUpdate,
SerializedOperation,
} from './operation';
import { Tree } from './tree';
import { Node } from './node';

export class CrdtTree<T> {
operationLog: OperationLog<T>[] = [];
clock: Clock;
tree = new Tree<T>();

constructor(id: string) {
this.clock = new Clock(id);
}

get(id: string): Node<T> | undefined {
return this.tree.get(id);
}

addLog(log: OperationLog<T>) {
this.operationLog.push(log);
}

generateOperationAdd(
targetId: string,
parentId: string,
content: T,
): OperationAdd<T> {
this.clock.increment();
const clock = this.clock.copy();
const input: OperationInput<T> = {
id: targetId,
parentId,
content,
clock,
};
return new OperationAdd<T>(input);
}

generateOperationDelete(targetId: string): OperationDelete<T> {
this.clock.increment();
const clock = this.clock.copy();
const input: OperationInput<T> = {
id: targetId,
clock,
};
return new OperationDelete<T>(input);
}

generateOperationMove(targetId: string, parentId: string): OperationMove<T> {
this.clock.increment();
const clock = this.clock.copy();
const input: OperationInput<T> = {
id: targetId,
parentId,
clock,
};
return new OperationMove<T>(input);
}

generateOperationUpdate(targetId: string, content: T): OperationUpdate<T> {
this.clock.increment();
const clock = this.clock.copy();
const input: OperationInput<T> = {
id: targetId,
content,
clock,
};
return new OperationUpdate<T>(input);
}

serializeOperationAdd(operation: OperationAdd<T>): SerializedOperation<T> {
const serializedOperation: SerializedOperation<T> = {
operationType: 'add',
id: operation.id,
clock: { id: operation.clock.id, counter: operation.clock.counter },
content: operation.content,
parentId: operation.parentId,
};
return serializedOperation;
}

serializeOperationDelete(
operation: OperationDelete<T>,
): SerializedOperation<T> {
const serializedOperation: SerializedOperation<T> = {
operationType: 'delete',
id: operation.id,
clock: { id: operation.clock.id, counter: operation.clock.counter },
};
return serializedOperation;
}

serializeOperationMove(operation: OperationMove<T>): SerializedOperation<T> {
const serializedOperation: SerializedOperation<T> = {
operationType: 'move',
id: operation.id,
clock: { id: operation.clock.id, counter: operation.clock.counter },
parentId: operation.parentId,
};
return serializedOperation;
}

serializeOperationUpdate(
operation: OperationUpdate<T>,
): SerializedOperation<T> {
const serializedOperation: SerializedOperation<T> = {
operationType: 'update',
id: operation.id,
clock: { id: operation.clock.id, counter: operation.clock.counter },
content: operation.content,
};
return serializedOperation;
}

deserializeOperationAdd(
serializedOperation: SerializedOperation<T>,
): OperationAdd<T> {
const input: OperationInput<T> = {
id: serializedOperation.id,
parentId: serializedOperation.parentId,
content: serializedOperation.content,
clock: new Clock(
serializedOperation.clock.id,
serializedOperation.clock.counter,
),
};
return new OperationAdd<T>(input);
}

deserializeOperationDelete(
serializedOperation: SerializedOperation<T>,
): OperationDelete<T> {
const input: OperationInput<T> = {
id: serializedOperation.id,
clock: new Clock(
serializedOperation.clock.id,
serializedOperation.clock.counter,
),
};
return new OperationDelete<T>(input);
}

deserializeOperationMove(
serializedOperation: SerializedOperation<T>,
): OperationMove<T> {
const input: OperationInput<T> = {
id: serializedOperation.id,
parentId: serializedOperation.parentId,
clock: new Clock(
serializedOperation.clock.id,
serializedOperation.clock.counter,
),
};
return new OperationMove<T>(input);
}

deserializeOperationUpdate(
serializedOperation: SerializedOperation<T>,
): OperationUpdate<T> {
const input: OperationInput<T> = {
id: serializedOperation.id,
content: serializedOperation.content,
clock: new Clock(
serializedOperation.clock.id,
serializedOperation.clock.counter,
),
};
return new OperationUpdate<T>(input);
}

applyOperation(operation: Operation<T>) {
this.clock = this.clock.merge(operation.clock);

if (this.operationLog.length === 0) {
const log = operation.doOperation(this.tree);
this.addLog(log);
return;
}

const lastOperation =
this.operationLog[this.operationLog.length - 1].operation;
if (operation.clock.compare(lastOperation.clock) === COMPARE.LESS) {
const prevLog = this.operationLog.pop();
prevLog.operation.undoOperation(this.tree, prevLog);
this.applyOperation(operation);
const redoLog = prevLog.operation.redoOperation(this.tree, prevLog);
this.addLog(redoLog);
} else {
const log = operation.doOperation(this.tree);
this.addLog(log);
}
}

applyOperations(operations: Operation<T>[]) {
for (const operation of operations) this.applyOperation(operation);
}
}
66 changes: 0 additions & 66 deletions nestjs-BE/crdt/lww-map.ts

This file was deleted.

31 changes: 0 additions & 31 deletions nestjs-BE/crdt/lww-register.ts

This file was deleted.

10 changes: 10 additions & 0 deletions nestjs-BE/crdt/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export class Node<T> {
parentId: string;
content: T;
children = new Set<string>();

constructor(parentId: string = '0', content: T | null = null) {
this.parentId = parentId;
this.content = content;
}
}
Loading

0 comments on commit 4e6e6f5

Please sign in to comment.