Skip to content

Commit

Permalink
Improve tldraw example performance (#640)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
devleejb authored Sep 12, 2023
1 parent cb4551b commit 876d3dc
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 7,759 deletions.
2 changes: 2 additions & 0 deletions examples/react-tldraw/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
"dependencies": {
"@react-hook/throttle": "^2.2.0",
"@tldraw/tldraw": "1.26.3",
"lodash": "^4.17.21",
"randomcolor": "^0.6.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"unique-names-generator": "^4.7.1",
"yorkie-js-sdk": "^0.4.6"
},
"devDependencies": {
"@types/lodash": "^4.14.198",
"@types/randomcolor": "^0.5.5",
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
Expand Down
6 changes: 3 additions & 3 deletions examples/react-tldraw/src/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export type Options = {
};

export type YorkieDocType = {
shapes: JSONObject<Record<string, TDShape>>;
bindings: JSONObject<Record<string, TDBinding>>;
assets: JSONObject<Record<string, TDAsset>>;
shapes: JSONObject<Record<string, JSONObject<TDShape>>>;
bindings: JSONObject<Record<string, JSONObject<TDBinding>>>;
assets: JSONObject<Record<string, JSONObject<TDAsset>>>;
};

export type YorkiePresenceType = {
Expand Down
62 changes: 53 additions & 9 deletions examples/react-tldraw/src/hooks/useMultiplayerState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useThrottleCallback } from '@react-hook/throttle';
import * as yorkie from 'yorkie-js-sdk';
import randomColor from 'randomcolor';
import { uniqueNamesGenerator, names } from 'unique-names-generator';
import _ from 'lodash';

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

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

doc.update((root) => {
Object.entries(shapes).forEach(([id, shape]) => {
const getUpdatedPropertyList = <T extends object>(
source: T,
target: T,
) => {
return (Object.keys(source) as Array<keyof T>).filter(
(key) => !_.isEqual(source[key], target[key]),
);
};

Object.entries(shapes).forEach(([id, shape]) => {
doc.update((root) => {
if (!shape) {
delete root.shapes[id];
} else {
} else if (!root.shapes[id]) {
root.shapes[id] = shape;
} else {
const updatedPropertyList = getUpdatedPropertyList(
shape,
root.shapes[id]!.toJS!(),
);

updatedPropertyList.forEach((key) => {
const newValue = shape[key];
(root.shapes[id][key] as typeof newValue) = newValue;
});
}
});
});

Object.entries(bindings).forEach(([id, binding]) => {
Object.entries(bindings).forEach(([id, binding]) => {
doc.update((root) => {
if (!binding) {
delete root.bindings[id];
} else {
} else if (!root.bindings[id]) {
root.bindings[id] = binding;
} else {
const updatedPropertyList = getUpdatedPropertyList(
binding,
root.bindings[id]!.toJS!(),
);

updatedPropertyList.forEach((key) => {
const newValue = binding[key];
(root.bindings[id][key] as typeof newValue) = newValue;
});
}
});
});

// Should store app.document.assets which is global asset storage referenced by inner page assets
// Document key for assets should be asset.id (string), not index
Object.entries(app.assets).forEach(([, asset]) => {
// Should store app.document.assets which is global asset storage referenced by inner page assets
// Document key for assets should be asset.id (string), not index
Object.entries(app.assets).forEach(([, asset]) => {
doc.update((root) => {
if (!asset.id) {
delete root.assets[asset.id];
} else {
} else if (root.assets[asset.id]) {
root.assets[asset.id] = asset;
} else {
const updatedPropertyList = getUpdatedPropertyList(
asset,
root.assets[asset.id]!.toJS!(),
);

updatedPropertyList.forEach((key) => {
const newValue = asset[key];
(root.assets[asset.id][key] as typeof newValue) = newValue;
});
}
});
});
Expand Down
Loading

0 comments on commit 876d3dc

Please sign in to comment.