Syncing optimizations (#1554)

* Syncing optimizations

* Add comment
This commit is contained in:
Pete Hunt 2020-05-07 14:13:18 -07:00 committed by GitHub
parent e27f3f9ad2
commit 4696c9ee0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 15 deletions

20
package-lock.json generated
View File

@ -2017,6 +2017,21 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
"integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==" "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA=="
}, },
"@types/lodash": {
"version": "4.14.150",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.150.tgz",
"integrity": "sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w==",
"dev": true
},
"@types/lodash.throttle": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.6.tgz",
"integrity": "sha512-/UIH96i/sIRYGC60NoY72jGkCJtFN5KVPhEMMMTjol65effe1gPn0tycJqV5tlSwMTzX8FqzB5yAj0rfGHTPNg==",
"dev": true,
"requires": {
"@types/lodash": "*"
}
},
"@types/minimatch": { "@types/minimatch": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@ -9635,6 +9650,11 @@
"lodash._reinterpolate": "^3.0.0" "lodash._reinterpolate": "^3.0.0"
} }
}, },
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"lodash.uniq": { "lodash.uniq": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",

View File

@ -21,9 +21,18 @@
"dependencies": { "dependencies": {
"@sentry/browser": "5.15.5", "@sentry/browser": "5.15.5",
"@sentry/integrations": "5.15.5", "@sentry/integrations": "5.15.5",
"@testing-library/jest-dom": "5.5.0",
"@testing-library/react": "10.0.4",
"@types/jest": "25.2.1",
"@types/nanoid": "2.1.0",
"@types/react": "16.9.34",
"@types/react-dom": "16.9.7",
"@types/socket.io-client": "1.4.32",
"browser-nativefs": "0.7.1", "browser-nativefs": "0.7.1",
"i18next-browser-languagedetector": "4.1.1", "i18next-browser-languagedetector": "4.1.1",
"lodash.throttle": "4.1.1",
"nanoid": "2.1.11", "nanoid": "2.1.11",
"node-sass": "4.14.0",
"open-color": "1.7.0", "open-color": "1.7.0",
"points-on-curve": "0.2.0", "points-on-curve": "0.2.0",
"pwacompat": "2.0.11", "pwacompat": "2.0.11",
@ -32,17 +41,10 @@
"react-scripts": "3.4.1", "react-scripts": "3.4.1",
"roughjs": "4.2.3", "roughjs": "4.2.3",
"socket.io-client": "2.3.0", "socket.io-client": "2.3.0",
"node-sass": "4.14.0", "typescript": "3.8.3"
"typescript": "3.8.3",
"@types/jest": "25.2.1",
"@types/nanoid": "2.1.0",
"@types/react": "16.9.34",
"@types/react-dom": "16.9.7",
"@types/socket.io-client": "1.4.32",
"@testing-library/jest-dom": "5.5.0",
"@testing-library/react": "10.0.4"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash.throttle": "4.1.6",
"asar": "3.0.3", "asar": "3.0.3",
"eslint": "6.8.0", "eslint": "6.8.0",
"eslint-config-prettier": "6.11.0", "eslint-config-prettier": "6.11.0",

View File

@ -133,6 +133,8 @@ import {
saveUsernameToLocalStorage, saveUsernameToLocalStorage,
} from "../data/localStorage"; } from "../data/localStorage";
import throttle from "lodash.throttle";
/** /**
* @param func handler taking at most single parameter (event). * @param func handler taking at most single parameter (event).
*/ */
@ -163,11 +165,14 @@ const gesture: Gesture = {
initialScale: null, initialScale: null,
}; };
const SYNC_FULL_SCENE_INTERVAL_MS = 20000;
class App extends React.Component<any, AppState> { class App extends React.Component<any, AppState> {
canvas: HTMLCanvasElement | null = null; canvas: HTMLCanvasElement | null = null;
rc: RoughCanvas | null = null; rc: RoughCanvas | null = null;
portal: Portal = new Portal(this); portal: Portal = new Portal(this);
lastBroadcastedOrReceivedSceneVersion: number = -1; lastBroadcastedOrReceivedSceneVersion: number = -1;
broadcastedElementVersions: Map<string, number> = new Map();
removeSceneCallback: SceneStateCallbackRemover | null = null; removeSceneCallback: SceneStateCallbackRemover | null = null;
actionManager: ActionManager; actionManager: ActionManager;
@ -474,6 +479,10 @@ class App extends React.Component<any, AppState> {
} }
}); });
queueBroadcastAllElements = throttle(() => {
this.broadcastScene(SCENE.UPDATE, /* syncAll */ true);
}, SYNC_FULL_SCENE_INTERVAL_MS);
componentDidUpdate() { componentDidUpdate() {
if (this.state.isCollaborating && !this.portal.socket) { if (this.state.isCollaborating && !this.portal.socket) {
this.initializeSocketClient({ showLoadingState: true }); this.initializeSocketClient({ showLoadingState: true });
@ -555,7 +564,8 @@ class App extends React.Component<any, AppState> {
getDrawingVersion(globalSceneState.getElementsIncludingDeleted()) > getDrawingVersion(globalSceneState.getElementsIncludingDeleted()) >
this.lastBroadcastedOrReceivedSceneVersion this.lastBroadcastedOrReceivedSceneVersion
) { ) {
this.broadcastScene(SCENE.UPDATE); this.broadcastScene(SCENE.UPDATE, /* syncAll */ false);
this.queueBroadcastAllElements();
} }
history.record(this.state, globalSceneState.getElementsIncludingDeleted()); history.record(this.state, globalSceneState.getElementsIncludingDeleted());
@ -1020,19 +1030,43 @@ class App extends React.Component<any, AppState> {
}; };
// maybe should move to Portal // maybe should move to Portal
broadcastScene = (sceneType: SCENE.INIT | SCENE.UPDATE) => { broadcastScene = (sceneType: SCENE.INIT | SCENE.UPDATE, syncAll: boolean) => {
if (sceneType === SCENE.INIT && !syncAll) {
throw new Error("syncAll must be true when sending SCENE.INIT");
}
let syncableElements = getSyncableElements(
globalSceneState.getElementsIncludingDeleted(),
);
if (!syncAll) {
// sync out only the elements we think we need to to save bandwidth.
// periodically we'll resync the whole thing to make sure no one diverges
// due to a dropped message (server goes down etc).
syncableElements = syncableElements.filter(
(syncableElement) =>
!this.broadcastedElementVersions.has(syncableElement.id) ||
syncableElement.version >
this.broadcastedElementVersions.get(syncableElement.id)!,
);
}
const data: SocketUpdateDataSource[typeof sceneType] = { const data: SocketUpdateDataSource[typeof sceneType] = {
type: sceneType, type: sceneType,
payload: { payload: {
elements: getSyncableElements( elements: syncableElements,
globalSceneState.getElementsIncludingDeleted(),
),
}, },
}; };
this.lastBroadcastedOrReceivedSceneVersion = Math.max( this.lastBroadcastedOrReceivedSceneVersion = Math.max(
this.lastBroadcastedOrReceivedSceneVersion, this.lastBroadcastedOrReceivedSceneVersion,
getDrawingVersion(globalSceneState.getElementsIncludingDeleted()), getDrawingVersion(globalSceneState.getElementsIncludingDeleted()),
); );
for (const syncableElement of syncableElements) {
this.broadcastedElementVersions.set(
syncableElement.id,
syncableElement.version,
);
}
return this.portal._broadcastSocketData(data as SocketUpdateData); return this.portal._broadcastSocketData(data as SocketUpdateData);
}; };

View File

@ -29,7 +29,7 @@ class Portal {
} }
}); });
this.socket.on("new-user", async (_socketID: string) => { this.socket.on("new-user", async (_socketID: string) => {
this.app.broadcastScene(SCENE.INIT); this.app.broadcastScene(SCENE.INIT, /* syncAll */ true);
}); });
this.socket.on("room-user-change", (clients: string[]) => { this.socket.on("room-user-change", (clients: string[]) => {
this.app.setCollaborators(clients); this.app.setCollaborators(clients);