Skip to content

Commit

Permalink
feat(#21, #22): 간단한 crdt 기능 구현
Browse files Browse the repository at this point in the history
state를 보낸 id, timestamp를 비교해서 merge 처리

Co-authored-by: Yong <[email protected]>
  • Loading branch information
Conut-1 and tnpfldyd committed Nov 15, 2023
1 parent e25a62f commit 7cc054b
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 61 deletions.
66 changes: 66 additions & 0 deletions nestjs-BE/crdt/lww-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { LWWRegister, lwwRegisterState } from './lww-register';

export type lwwMapState<T> = {
[key: string]: lwwRegisterState<T>;
};

export class LWWMap<T> {
readonly id: string;
private data = new Map<string, LWWRegister<T | null>>();

constructor(id: string, state: lwwMapState<T> = {}) {
this.id = id;
this.initializeData(state);
}

private initializeData(state: lwwMapState<T>): void {
for (const [key, register] of Object.entries(state))
this.data.set(key, new LWWRegister(this.id, register));
}

getState(): lwwMapState<T> {
const state: lwwMapState<T> = {};
for (const [key, register] of this.data.entries())
if (register) state[key] = register.state;
return state;
}

get(key: string): T | null | undefined {
return this.data.get(key)?.getValue();
}

set(key: string, value: T): void {
const register = this.data.get(key);
if (register) register.setValue(value);
else
this.data.set(
key,
new LWWRegister(this.id, {
id: this.id,
timestamp: Date.now(),
value,
}),
);
}

delete(key: string): void {
this.data.get(key)?.setValue(null);
}

has(key: string): boolean {
return !!this.data.get(key)?.getValue();
}

clear(): void {
for (const [key, register] of this.data.entries())
if (register) this.delete(key);
}

merge(state: lwwMapState<T>): void {
for (const [key, remoteRegister] of Object.entries(state)) {
const local = this.data.get(key);
if (local) local.merge(remoteRegister);
else this.data.set(key, new LWWRegister(this.id, remoteRegister));
}
}
}
31 changes: 31 additions & 0 deletions nestjs-BE/crdt/lww-register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export interface lwwRegisterState<T> {
id: string;
timestamp: number;
value: T;
}

export class LWWRegister<T> {
readonly id: string;
state: lwwRegisterState<T>;

constructor(id: string, state: lwwRegisterState<T>) {
this.id = id;
this.state = state;
}

getValue(): T {
return this.state.value;
}

setValue(value: T): void {
this.state = { id: this.id, timestamp: Date.now(), value };
}

merge(state: lwwRegisterState<T>): void {
const { id: remoteId, timestamp: remoteTimestamp } = state;
const { id: localId, timestamp: localTimestamp } = this.state;
if (localTimestamp > remoteTimestamp) return;
if (localTimestamp === remoteTimestamp && localId > remoteId) return;
this.state = state;
}
}
47 changes: 1 addition & 46 deletions nestjs-BE/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions nestjs-BE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
"@nestjs/platform-socket.io": "^10.2.8",
"@nestjs/websockets": "^10.2.8",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"yjs": "^13.6.8"
"rxjs": "^7.8.1"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand Down
26 changes: 13 additions & 13 deletions nestjs-BE/src/mindmap/mindmap.service.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { Injectable } from '@nestjs/common';
import * as Y from 'yjs';
import { LWWMap, lwwMapState } from 'crdt/lww-map';

@Injectable()
export class MindmapService {
private ydocs = new Map<string, Y.Doc>();
private boards = new Map<string, LWWMap<string>>();

updateMindmap(boardId: string, message: any) {
const ydoc = this.getMindmap(boardId);
Y.applyUpdate(ydoc, message);
updateMindmap(boardId: string, message: any): void {
const board = this.getMindmap(boardId);
board.merge(message);
}

getEncodedState(boardId: string) {
const ydoc = this.getMindmap(boardId);
return Y.encodeStateAsUpdate(ydoc);
getEncodedState(boardId: string): lwwMapState<string> {
const board = this.getMindmap(boardId);
return board.getState();
}

private getMindmap(boardId: string) {
let ydoc = this.ydocs.get(boardId);
if (!ydoc) {
ydoc = new Y.Doc();
this.ydocs.set(boardId, ydoc);
let board = this.boards.get(boardId);
if (!board) {
board = new LWWMap<string>(boardId); // boardId 대신에 서버 아이디???
this.boards.set(boardId, board);
}
return ydoc;
return board;
}
}

0 comments on commit 7cc054b

Please sign in to comment.