Skip to content

Commit 876d3dc

Browse files
authored
Improve tldraw example performance (#640)
In the tldraw example project, there's long latency when moving large shapes (e.g., DrawShape). The reason is that the unchanged data (e.g., drawing points in DrawShape) is sent when moving shapes. So, in this PR, only point data (a point of shape on the whiteboard) is sent to the Yorkie server.
1 parent cb4551b commit 876d3dc

File tree

4 files changed

+127
-7759
lines changed

4 files changed

+127
-7759
lines changed

examples/react-tldraw/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
"dependencies": {
1212
"@react-hook/throttle": "^2.2.0",
1313
"@tldraw/tldraw": "1.26.3",
14+
"lodash": "^4.17.21",
1415
"randomcolor": "^0.6.2",
1516
"react": "^18.2.0",
1617
"react-dom": "^18.2.0",
1718
"unique-names-generator": "^4.7.1",
1819
"yorkie-js-sdk": "^0.4.6"
1920
},
2021
"devDependencies": {
22+
"@types/lodash": "^4.14.198",
2123
"@types/randomcolor": "^0.5.5",
2224
"@types/react": "^18.0.24",
2325
"@types/react-dom": "^18.0.8",

examples/react-tldraw/src/hooks/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ export type Options = {
88
};
99

1010
export type YorkieDocType = {
11-
shapes: JSONObject<Record<string, TDShape>>;
12-
bindings: JSONObject<Record<string, TDBinding>>;
13-
assets: JSONObject<Record<string, TDAsset>>;
11+
shapes: JSONObject<Record<string, JSONObject<TDShape>>>;
12+
bindings: JSONObject<Record<string, JSONObject<TDBinding>>>;
13+
assets: JSONObject<Record<string, JSONObject<TDAsset>>>;
1414
};
1515

1616
export type YorkiePresenceType = {

examples/react-tldraw/src/hooks/useMultiplayerState.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useThrottleCallback } from '@react-hook/throttle';
1212
import * as yorkie from 'yorkie-js-sdk';
1313
import randomColor from 'randomcolor';
1414
import { uniqueNamesGenerator, names } from 'unique-names-generator';
15+
import _ from 'lodash';
1516

1617
import type { Options, YorkieDocType, YorkiePresenceType } from './types';
1718

@@ -64,30 +65,73 @@ export function useMultiplayerState(roomId: string) {
6465
) => {
6566
if (!app || client === undefined || doc === undefined) return;
6667

67-
doc.update((root) => {
68-
Object.entries(shapes).forEach(([id, shape]) => {
68+
const getUpdatedPropertyList = <T extends object>(
69+
source: T,
70+
target: T,
71+
) => {
72+
return (Object.keys(source) as Array<keyof T>).filter(
73+
(key) => !_.isEqual(source[key], target[key]),
74+
);
75+
};
76+
77+
Object.entries(shapes).forEach(([id, shape]) => {
78+
doc.update((root) => {
6979
if (!shape) {
7080
delete root.shapes[id];
71-
} else {
81+
} else if (!root.shapes[id]) {
7282
root.shapes[id] = shape;
83+
} else {
84+
const updatedPropertyList = getUpdatedPropertyList(
85+
shape,
86+
root.shapes[id]!.toJS!(),
87+
);
88+
89+
updatedPropertyList.forEach((key) => {
90+
const newValue = shape[key];
91+
(root.shapes[id][key] as typeof newValue) = newValue;
92+
});
7393
}
7494
});
95+
});
7596

76-
Object.entries(bindings).forEach(([id, binding]) => {
97+
Object.entries(bindings).forEach(([id, binding]) => {
98+
doc.update((root) => {
7799
if (!binding) {
78100
delete root.bindings[id];
79-
} else {
101+
} else if (!root.bindings[id]) {
80102
root.bindings[id] = binding;
103+
} else {
104+
const updatedPropertyList = getUpdatedPropertyList(
105+
binding,
106+
root.bindings[id]!.toJS!(),
107+
);
108+
109+
updatedPropertyList.forEach((key) => {
110+
const newValue = binding[key];
111+
(root.bindings[id][key] as typeof newValue) = newValue;
112+
});
81113
}
82114
});
115+
});
83116

84-
// Should store app.document.assets which is global asset storage referenced by inner page assets
85-
// Document key for assets should be asset.id (string), not index
86-
Object.entries(app.assets).forEach(([, asset]) => {
117+
// Should store app.document.assets which is global asset storage referenced by inner page assets
118+
// Document key for assets should be asset.id (string), not index
119+
Object.entries(app.assets).forEach(([, asset]) => {
120+
doc.update((root) => {
87121
if (!asset.id) {
88122
delete root.assets[asset.id];
89-
} else {
123+
} else if (root.assets[asset.id]) {
90124
root.assets[asset.id] = asset;
125+
} else {
126+
const updatedPropertyList = getUpdatedPropertyList(
127+
asset,
128+
root.assets[asset.id]!.toJS!(),
129+
);
130+
131+
updatedPropertyList.forEach((key) => {
132+
const newValue = asset[key];
133+
(root.assets[asset.id][key] as typeof newValue) = newValue;
134+
});
91135
}
92136
});
93137
});

0 commit comments

Comments
 (0)