From 8d14661ef0eb2c033ebf74bb6e3bdc4afbd99e19 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Tue, 21 Nov 2023 22:35:30 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat(#71):=20Timestamp=20=EB=85=BC?= =?UTF-8?q?=EB=A6=AC=EC=A0=81=20=EC=8B=9C=EA=B3=84=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/crdt/lww-map.ts | 2 +- nestjs-BE/crdt/lww-register.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nestjs-BE/crdt/lww-map.ts b/nestjs-BE/crdt/lww-map.ts index 52fb9bde..458c86be 100644 --- a/nestjs-BE/crdt/lww-map.ts +++ b/nestjs-BE/crdt/lww-map.ts @@ -37,7 +37,7 @@ export class LWWMap { key, new LWWRegister(this.id, { id: this.id, - timestamp: Date.now(), + timestamp: 1, value, }), ); diff --git a/nestjs-BE/crdt/lww-register.ts b/nestjs-BE/crdt/lww-register.ts index b7c020e2..ccb74cb9 100644 --- a/nestjs-BE/crdt/lww-register.ts +++ b/nestjs-BE/crdt/lww-register.ts @@ -18,7 +18,7 @@ export class LWWRegister { } setValue(value: T): void { - this.state = { id: this.id, timestamp: Date.now(), value }; + this.state = { id: this.id, timestamp: this.state.timestamp + 1, value }; } merge(state: lwwRegisterState): void { From 4ad2ac1b4f2537530adf797124bbbcf9880fdef4 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Tue, 21 Nov 2023 22:38:01 +0900 Subject: [PATCH 02/10] =?UTF-8?q?rename:=20lww=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=97=90=EC=84=9C=20=EB=B9=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/crdt/{lww-map.ts => map.ts} | 2 +- nestjs-BE/crdt/{lww-register.ts => register.ts} | 0 nestjs-BE/src/mindmap/mindmap.service.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename nestjs-BE/crdt/{lww-map.ts => map.ts} (96%) rename nestjs-BE/crdt/{lww-register.ts => register.ts} (100%) diff --git a/nestjs-BE/crdt/lww-map.ts b/nestjs-BE/crdt/map.ts similarity index 96% rename from nestjs-BE/crdt/lww-map.ts rename to nestjs-BE/crdt/map.ts index 458c86be..3a7cf31c 100644 --- a/nestjs-BE/crdt/lww-map.ts +++ b/nestjs-BE/crdt/map.ts @@ -1,4 +1,4 @@ -import { LWWRegister, lwwRegisterState } from './lww-register'; +import { LWWRegister, lwwRegisterState } from './register'; export type lwwMapState = { [key: string]: lwwRegisterState; diff --git a/nestjs-BE/crdt/lww-register.ts b/nestjs-BE/crdt/register.ts similarity index 100% rename from nestjs-BE/crdt/lww-register.ts rename to nestjs-BE/crdt/register.ts diff --git a/nestjs-BE/src/mindmap/mindmap.service.ts b/nestjs-BE/src/mindmap/mindmap.service.ts index 14b24730..1794b188 100644 --- a/nestjs-BE/src/mindmap/mindmap.service.ts +++ b/nestjs-BE/src/mindmap/mindmap.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { LWWMap, lwwMapState } from 'crdt/lww-map'; +import { LWWMap, lwwMapState } from 'crdt/map'; @Injectable() export class MindmapService { From e77f41744c3454668f211439f043e703fcaa8f7c Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 22 Nov 2023 23:44:05 +0900 Subject: [PATCH 03/10] =?UTF-8?q?delete:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/crdt/map.ts | 66 -------------------------------------- nestjs-BE/crdt/register.ts | 31 ------------------ 2 files changed, 97 deletions(-) delete mode 100644 nestjs-BE/crdt/map.ts delete mode 100644 nestjs-BE/crdt/register.ts diff --git a/nestjs-BE/crdt/map.ts b/nestjs-BE/crdt/map.ts deleted file mode 100644 index 3a7cf31c..00000000 --- a/nestjs-BE/crdt/map.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { LWWRegister, lwwRegisterState } from './register'; - -export type lwwMapState = { - [key: string]: lwwRegisterState; -}; - -export class LWWMap { - readonly id: string; - private data = new Map>(); - - constructor(id: string, state: lwwMapState = {}) { - this.id = id; - this.initializeData(state); - } - - private initializeData(state: lwwMapState): void { - for (const [key, register] of Object.entries(state)) - this.data.set(key, new LWWRegister(this.id, register)); - } - - getState(): lwwMapState { - const state: lwwMapState = {}; - 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: 1, - 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): 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)); - } - } -} diff --git a/nestjs-BE/crdt/register.ts b/nestjs-BE/crdt/register.ts deleted file mode 100644 index ccb74cb9..00000000 --- a/nestjs-BE/crdt/register.ts +++ /dev/null @@ -1,31 +0,0 @@ -export interface lwwRegisterState { - id: string; - timestamp: number; - value: T; -} - -export class LWWRegister { - readonly id: string; - state: lwwRegisterState; - - constructor(id: string, state: lwwRegisterState) { - this.id = id; - this.state = state; - } - - getValue(): T { - return this.state.value; - } - - setValue(value: T): void { - this.state = { id: this.id, timestamp: this.state.timestamp + 1, value }; - } - - merge(state: lwwRegisterState): 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; - } -} From 4cd6050cee8019e75a2bd2e08904ce91e564971a Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 22 Nov 2023 23:45:39 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat(#76):=20=ED=8A=B8=EB=A6=AC=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/crdt/node.ts | 9 +++++++ nestjs-BE/crdt/tree.ts | 58 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 nestjs-BE/crdt/node.ts create mode 100644 nestjs-BE/crdt/tree.ts diff --git a/nestjs-BE/crdt/node.ts b/nestjs-BE/crdt/node.ts new file mode 100644 index 00000000..bf9c94a9 --- /dev/null +++ b/nestjs-BE/crdt/node.ts @@ -0,0 +1,9 @@ +export class Node { + parentId: string; + content: T; + + constructor(parentId: string = '0', content: T | null = null) { + this.parentId = parentId; + this.content = content; + } +} diff --git a/nestjs-BE/crdt/tree.ts b/nestjs-BE/crdt/tree.ts new file mode 100644 index 00000000..3be39393 --- /dev/null +++ b/nestjs-BE/crdt/tree.ts @@ -0,0 +1,58 @@ +import { Node } from './node'; + +export class Tree { + nodes = new Map>(); + children = new Map>(); + + constructor() { + this.nodes.set('root', new Node()); + } + + get(id: string): Node | undefined { + return this.nodes.get(id); + } + + addNode(targetId: string, parentId: string, content: T) { + const newNode = new Node(parentId, content); + + const parentNode = this.nodes.get(parentId); + if (!parentNode) return; + + let childrenSet = this.children.get(parentId); + if (!childrenSet) { + childrenSet = new Set(); + this.children.set(parentId, childrenSet); + } + + childrenSet.add(targetId); + this.nodes.set(targetId, newNode); + } + + attachNode(targetId: string, parentId: string) { + const targetNode = this.nodes.get(targetId); + if (!targetNode) return; + + const parentNode = this.nodes.get(parentId); + if (!parentNode) return; + + let childrenSet = this.children.get(parentId); + if (!childrenSet) { + childrenSet = new Set(); + this.children.set(parentId, childrenSet); + } + + childrenSet.add(targetId); + targetNode.parentId = parentId; + } + + removeNode(targetId: string): Node { + const targetNode = this.nodes.get(targetId); + if (!targetNode) return; + + const parentChildren = this.children.get(targetNode.parentId); + if (!parentChildren) return; + parentChildren.delete(targetId); + + return this.nodes.get(targetId); + } +} From 4cfd580793c9b552e8e3067804703b23d74f981d Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 22 Nov 2023 23:46:32 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat(#76):=20=EB=85=BC=EB=A6=AC=EC=A0=81?= =?UTF-8?q?=20=EC=8B=9C=EA=B3=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/crdt/clock.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 nestjs-BE/crdt/clock.ts diff --git a/nestjs-BE/crdt/clock.ts b/nestjs-BE/crdt/clock.ts new file mode 100644 index 00000000..4a78508d --- /dev/null +++ b/nestjs-BE/crdt/clock.ts @@ -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; + } +} From c2737978b9f47b267a1f80c5889aa697d820199a Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 23 Nov 2023 18:39:14 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat(#76):=20CRDT=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/crdt/crdt-tree.ts | 168 ++++++++++++++++++++++++++++++++++++ nestjs-BE/crdt/operation.ts | 119 +++++++++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 nestjs-BE/crdt/crdt-tree.ts create mode 100644 nestjs-BE/crdt/operation.ts diff --git a/nestjs-BE/crdt/crdt-tree.ts b/nestjs-BE/crdt/crdt-tree.ts new file mode 100644 index 00000000..59758a08 --- /dev/null +++ b/nestjs-BE/crdt/crdt-tree.ts @@ -0,0 +1,168 @@ +import { Clock, COMPARE } from './clock'; +import { + Operation, + OperationAdd, + OperationDelete, + OperationInput, + OperationLog, + OperationMove, + SerializedOperation, +} from './operation'; +import { Tree } from './tree'; +import { Node } from './node'; + +export class CrdtTree { + operationLog: OperationLog[] = []; + clock: Clock; + tree = new Tree(); + + constructor(id: string) { + this.clock = new Clock(id); + } + + get(id: string): Node | undefined { + return this.tree.get(id); + } + + addLog(log: OperationLog) { + this.operationLog.push(log); + } + + generateOperationAdd( + targetId: string, + parentId: string, + content: T, + ): OperationAdd { + this.clock.increment(); + const clock = this.clock.copy(); + const input: OperationInput = { + id: targetId, + parentId, + content, + clock, + }; + return new OperationAdd(input); + } + + generateOperationDelete(targetId: string): OperationDelete { + this.clock.increment(); + const clock = this.clock.copy(); + const input: OperationInput = { + id: targetId, + clock, + }; + return new OperationDelete(input); + } + + generateOperationMove(targetId: string, parentId: string): OperationMove { + this.clock.increment(); + const clock = this.clock.copy(); + const input: OperationInput = { + id: targetId, + parentId, + clock, + }; + return new OperationMove(input); + } + + serializeOperationAdd(operation: OperationAdd): SerializedOperation { + const serializedOperation: SerializedOperation = { + 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, + ): SerializedOperation { + const serializedOperation: SerializedOperation = { + operationType: 'delete', + id: operation.id, + clock: { id: operation.clock.id, counter: operation.clock.counter }, + }; + return serializedOperation; + } + + serializeOperationMove(operation: OperationMove): SerializedOperation { + const serializedOperation: SerializedOperation = { + operationType: 'move', + id: operation.id, + clock: { id: operation.clock.id, counter: operation.clock.counter }, + parentId: operation.parentId, + }; + return serializedOperation; + } + + deserializeOperationAdd( + serializedOperation: SerializedOperation, + ): OperationAdd { + const input: OperationInput = { + id: serializedOperation.id, + parentId: serializedOperation.parentId, + content: serializedOperation.content, + clock: new Clock( + serializedOperation.clock.id, + serializedOperation.clock.counter, + ), + }; + return new OperationAdd(input); + } + + deserializeOperationDelete( + serializedOperation: SerializedOperation, + ): OperationDelete { + const input: OperationInput = { + id: serializedOperation.id, + clock: new Clock( + serializedOperation.clock.id, + serializedOperation.clock.counter, + ), + }; + return new OperationDelete(input); + } + + deserializeOperationMove( + serializedOperation: SerializedOperation, + ): OperationMove { + const input: OperationInput = { + id: serializedOperation.id, + parentId: serializedOperation.parentId, + clock: new Clock( + serializedOperation.clock.id, + serializedOperation.clock.counter, + ), + }; + return new OperationMove(input); + } + + applyOperation(operation: Operation) { + 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[]) { + for (const operation of operations) this.applyOperation(operation); + } +} diff --git a/nestjs-BE/crdt/operation.ts b/nestjs-BE/crdt/operation.ts new file mode 100644 index 00000000..7bf2255f --- /dev/null +++ b/nestjs-BE/crdt/operation.ts @@ -0,0 +1,119 @@ +import { Clock } from './clock'; +import { Tree } from './tree'; + +export interface OperationLog { + operation: Operation; + oldParentId?: string; + oldContent?: T; +} + +export interface OperationInput { + id: string; + clock: Clock; + content?: T; + parentId?: string; +} + +interface ClockInterface { + id: string; + counter: number; +} + +export interface SerializedOperation { + operationType: string; + id: string; + clock: ClockInterface; + content?: T; + parentId?: string; +} + +export abstract class Operation { + operationType: string; + id: string; + clock: Clock; + + constructor(operationType: string, id: string, clock: Clock) { + this.operationType = operationType; + this.id = id; + this.clock = clock; + } + + abstract doOperation(tree: Tree): OperationLog; + abstract undoOperation(tree: Tree, log: OperationLog): void; + abstract redoOperation(tree: Tree, log: OperationLog): OperationLog; +} + +export class OperationAdd extends Operation { + content: T; + parentId: string; + + constructor(input: OperationInput) { + super('add', input.id, input.clock); + this.content = input.content; + this.parentId = input.parentId; + } + + doOperation(tree: Tree): OperationLog { + tree.addNode(this.id, this.parentId, this.content); + return { operation: this }; + } + + undoOperation(tree: Tree, log: OperationLog): void { + tree.removeNode(log.operation.id); + } + + redoOperation(tree: Tree, log?: OperationLog): OperationLog { + tree.attachNode(log.operation.id, this.parentId); + return { operation: this }; + } +} + +export class OperationDelete extends Operation { + constructor(input: OperationInput) { + super('delete', input.id, input.clock); + } + + doOperation(tree: Tree): OperationLog { + const node = tree.get(this.id); + const oldParentId = node.parentId; + tree.removeNode(this.id); + return { operation: this, oldParentId: oldParentId }; + } + + undoOperation(tree: Tree, log: OperationLog): void { + tree.attachNode(log.operation.id, log.oldParentId); + } + + redoOperation(tree: Tree, log?: OperationLog): OperationLog { + const redoLog = log.operation.doOperation(tree); + return redoLog; + } +} + +export class OperationMove extends Operation { + parentId: string; + + constructor(input: OperationInput) { + super('move', input.id, input.clock); + this.parentId = input.parentId; + } + + doOperation(tree: Tree): OperationLog { + const node = tree.get(this.id); + const oldParentId = node.parentId; + + tree.removeNode(this.id); + tree.attachNode(this.id, this.parentId); + return { operation: this, oldParentId }; + } + + undoOperation(tree: Tree, log: OperationLog): void { + tree.removeNode(log.operation.id); + tree.attachNode(log.operation.id, log.oldParentId); + } + + redoOperation(tree: Tree, log?: OperationLog): OperationLog { + const redoLog = log.operation.doOperation(tree); + return redoLog; + } +} From c1962c213dce491d4b254173430b413949989bde Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 23 Nov 2023 18:40:22 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat(#76):=20=ED=8A=B8=EB=A6=AC=20?= =?UTF-8?q?=EB=85=B8=EB=93=9C=20update=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/crdt/crdt-tree.ts | 38 +++++++++++++++++++++++++++++++++++++ nestjs-BE/crdt/operation.ts | 25 ++++++++++++++++++++++++ nestjs-BE/crdt/tree.ts | 7 +++++++ 3 files changed, 70 insertions(+) diff --git a/nestjs-BE/crdt/crdt-tree.ts b/nestjs-BE/crdt/crdt-tree.ts index 59758a08..e4ce4192 100644 --- a/nestjs-BE/crdt/crdt-tree.ts +++ b/nestjs-BE/crdt/crdt-tree.ts @@ -6,6 +6,7 @@ import { OperationInput, OperationLog, OperationMove, + OperationUpdate, SerializedOperation, } from './operation'; import { Tree } from './tree'; @@ -65,6 +66,17 @@ export class CrdtTree { return new OperationMove(input); } + generateOperationUpdate(targetId: string, content: T): OperationUpdate { + this.clock.increment(); + const clock = this.clock.copy(); + const input: OperationInput = { + id: targetId, + content, + clock, + }; + return new OperationUpdate(input); + } + serializeOperationAdd(operation: OperationAdd): SerializedOperation { const serializedOperation: SerializedOperation = { operationType: 'add', @@ -97,6 +109,18 @@ export class CrdtTree { return serializedOperation; } + serializeOperationUpdate( + operation: OperationUpdate, + ): SerializedOperation { + const serializedOperation: SerializedOperation = { + operationType: 'update', + id: operation.id, + clock: { id: operation.clock.id, counter: operation.clock.counter }, + content: operation.content, + }; + return serializedOperation; + } + deserializeOperationAdd( serializedOperation: SerializedOperation, ): OperationAdd { @@ -139,6 +163,20 @@ export class CrdtTree { return new OperationMove(input); } + deserializeOperationUpdate( + serializedOperation: SerializedOperation, + ): OperationUpdate { + const input: OperationInput = { + id: serializedOperation.id, + content: serializedOperation.content, + clock: new Clock( + serializedOperation.clock.id, + serializedOperation.clock.counter, + ), + }; + return new OperationUpdate(input); + } + applyOperation(operation: Operation) { this.clock = this.clock.merge(operation.clock); diff --git a/nestjs-BE/crdt/operation.ts b/nestjs-BE/crdt/operation.ts index 7bf2255f..644206f2 100644 --- a/nestjs-BE/crdt/operation.ts +++ b/nestjs-BE/crdt/operation.ts @@ -117,3 +117,28 @@ export class OperationMove extends Operation { return redoLog; } } + +export class OperationUpdate extends Operation { + content: T; + + constructor(input: OperationInput) { + super('update', input.id, input.clock); + this.content = input.content; + } + + doOperation(tree: Tree): OperationLog { + const node = tree.get(this.id); + const oldContent = node.content; + tree.updateNode(this.id, this.content); + return { operation: this, oldContent }; + } + + undoOperation(tree: Tree, log: OperationLog): void { + tree.updateNode(log.operation.id, log.oldContent); + } + + redoOperation(tree: Tree, log?: OperationLog): OperationLog { + const redoLog = log.operation.doOperation(tree); + return redoLog; + } +} diff --git a/nestjs-BE/crdt/tree.ts b/nestjs-BE/crdt/tree.ts index 3be39393..3e1cf36e 100644 --- a/nestjs-BE/crdt/tree.ts +++ b/nestjs-BE/crdt/tree.ts @@ -55,4 +55,11 @@ export class Tree { return this.nodes.get(targetId); } + + updateNode(targetId: string, content: T) { + const targetNode = this.nodes.get(targetId); + if (!targetNode) return; + + targetNode.content = content; + } } From 3b6846a714d9bfa579a9e462c38b009c877d6355 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 26 Nov 2023 22:28:56 +0900 Subject: [PATCH 08/10] =?UTF-8?q?delete:=20crdt=20map=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/src/board/board.gateway.ts | 21 ++++++------------- nestjs-BE/src/mindmap/mindmap.service.ts | 26 ------------------------ 2 files changed, 6 insertions(+), 41 deletions(-) delete mode 100644 nestjs-BE/src/mindmap/mindmap.service.ts diff --git a/nestjs-BE/src/board/board.gateway.ts b/nestjs-BE/src/board/board.gateway.ts index cd6229ea..2c555bcc 100644 --- a/nestjs-BE/src/board/board.gateway.ts +++ b/nestjs-BE/src/board/board.gateway.ts @@ -4,33 +4,24 @@ import { WebSocketServer, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; -import { MindmapService } from '../mindmap/mindmap.service'; -interface BoardDataPayload { +interface MindmapDataPayload { message: any; boardId: string; } @WebSocketGateway({ namespace: 'board' }) export class BoardGateway { - constructor(private readonly mindmapService: MindmapService) {} - @WebSocketServer() server: Server; - @SubscribeMessage('joinRoom') - handleJoinRoom(client: Socket, payload: { boardId: string }): void { + @SubscribeMessage('joinBoard') + handleJoinBoard(client: Socket, payload: { boardId: string }): void { client.join(payload.boardId); } - @SubscribeMessage('pushData') - handlePushNode(client: Socket, payload: BoardDataPayload): void { - this.mindmapService.updateMindmap(payload.boardId, payload.message); - client.broadcast - .to(payload.boardId) - .emit( - 'messageFromServer', - this.mindmapService.getEncodedState(payload.boardId), - ); + @SubscribeMessage('updateMindmap') + handleUpdateMindmap(client: Socket, payload: MindmapDataPayload) { + client.broadcast.to(payload.boardId).emit('stateFromServer', payload); } } diff --git a/nestjs-BE/src/mindmap/mindmap.service.ts b/nestjs-BE/src/mindmap/mindmap.service.ts deleted file mode 100644 index 1794b188..00000000 --- a/nestjs-BE/src/mindmap/mindmap.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { LWWMap, lwwMapState } from 'crdt/map'; - -@Injectable() -export class MindmapService { - private boards = new Map>(); - - updateMindmap(boardId: string, message: any): void { - const board = this.getMindmap(boardId); - board.merge(message); - } - - getEncodedState(boardId: string): lwwMapState { - const board = this.getMindmap(boardId); - return board.getState(); - } - - private getMindmap(boardId: string) { - let board = this.boards.get(boardId); - if (!board) { - board = new LWWMap(boardId); // boardId 대신에 서버 아이디??? - this.boards.set(boardId, board); - } - return board; - } -} From a98a0a37e95f9f9df0f1ba4142fd444e525e181e Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 27 Nov 2023 10:10:31 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20=EB=85=B8=EB=93=9C=EC=97=90?= =?UTF-8?q?=EC=84=9C=20children=20=EA=B0=80=EC=A7=80=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/crdt/node.ts | 1 + nestjs-BE/crdt/tree.ts | 33 +++++++++++++++------------------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/nestjs-BE/crdt/node.ts b/nestjs-BE/crdt/node.ts index bf9c94a9..ca09556c 100644 --- a/nestjs-BE/crdt/node.ts +++ b/nestjs-BE/crdt/node.ts @@ -1,6 +1,7 @@ export class Node { parentId: string; content: T; + children = new Set(); constructor(parentId: string = '0', content: T | null = null) { this.parentId = parentId; diff --git a/nestjs-BE/crdt/tree.ts b/nestjs-BE/crdt/tree.ts index 3e1cf36e..fc72bc87 100644 --- a/nestjs-BE/crdt/tree.ts +++ b/nestjs-BE/crdt/tree.ts @@ -2,7 +2,6 @@ import { Node } from './node'; export class Tree { nodes = new Map>(); - children = new Map>(); constructor() { this.nodes.set('root', new Node()); @@ -12,19 +11,22 @@ export class Tree { return this.nodes.get(id); } + getNodeChildren(id: string): Array | null { + const node = this.get(id); + if (!node) return null; + + const childrenArray = Array.from(node.children); + childrenArray.sort(); + return childrenArray; + } + addNode(targetId: string, parentId: string, content: T) { const newNode = new Node(parentId, content); const parentNode = this.nodes.get(parentId); if (!parentNode) return; - let childrenSet = this.children.get(parentId); - if (!childrenSet) { - childrenSet = new Set(); - this.children.set(parentId, childrenSet); - } - - childrenSet.add(targetId); + parentNode.children.add(targetId); this.nodes.set(targetId, newNode); } @@ -35,13 +37,7 @@ export class Tree { const parentNode = this.nodes.get(parentId); if (!parentNode) return; - let childrenSet = this.children.get(parentId); - if (!childrenSet) { - childrenSet = new Set(); - this.children.set(parentId, childrenSet); - } - - childrenSet.add(targetId); + parentNode.children.add(targetId); targetNode.parentId = parentId; } @@ -49,9 +45,10 @@ export class Tree { const targetNode = this.nodes.get(targetId); if (!targetNode) return; - const parentChildren = this.children.get(targetNode.parentId); - if (!parentChildren) return; - parentChildren.delete(targetId); + const parentNode = this.nodes.get(targetNode.parentId); + if (!parentNode) return; + + parentNode.children.delete(targetId); return this.nodes.get(targetId); } From 3574292ce152a15f557815bfac10d8587ae9ad07 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 27 Nov 2023 10:14:42 +0900 Subject: [PATCH 10/10] =?UTF-8?q?delete:=20=EB=8F=99=EC=9E=91=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/src/app.module.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nestjs-BE/src/app.module.ts b/nestjs-BE/src/app.module.ts index fe6f5f38..b919faf3 100644 --- a/nestjs-BE/src/app.module.ts +++ b/nestjs-BE/src/app.module.ts @@ -2,11 +2,10 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { BoardGateway } from './board/board.gateway'; -import { MindmapService } from './mindmap/mindmap.service'; @Module({ imports: [], controllers: [AppController], - providers: [AppService, BoardGateway, MindmapService], + providers: [AppService, BoardGateway], }) export class AppModule {}