move broadcastScene and broadcastMouseLocation to portal (#2262)

This commit is contained in:
Aakansha Doshi 2020-10-21 19:11:20 +05:30 committed by GitHub
parent cc5e27af42
commit 6db5647048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 88 deletions

View File

@ -3,7 +3,6 @@ import React from "react";
import rough from "roughjs/bin/rough";
import { RoughCanvas } from "roughjs/bin/canvas";
import { simplify, Point } from "points-on-curve";
import { SocketUpdateData } from "../types";
import {
newElement,
@ -279,9 +278,8 @@ export type ExcalidrawImperativeAPI =
class App extends React.Component<ExcalidrawProps, AppState> {
canvas: HTMLCanvasElement | null = null;
rc: RoughCanvas | null = null;
portal: Portal = new Portal(this);
lastBroadcastedOrReceivedSceneVersion: number = -1;
broadcastedElementVersions: Map<string, number> = new Map();
portal: Portal;
private lastBroadcastedOrReceivedSceneVersion: number = -1;
unmounted: boolean = false;
actionManager: ActionManager;
private excalidrawRef: any;
@ -312,6 +310,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
};
}
this.scene = new Scene();
this.portal = new Portal(this);
this.excalidrawRef = React.createRef();
this.actionManager = new ActionManager(
this.syncActionResult,
@ -396,6 +396,18 @@ class App extends React.Component<ExcalidrawProps, AppState> {
);
}
public setLastBroadcastedOrReceivedSceneVersion = (version: number) => {
this.lastBroadcastedOrReceivedSceneVersion = version;
};
public getLastBroadcastedOrReceivedSceneVersion = () => {
return this.lastBroadcastedOrReceivedSceneVersion;
};
public getSceneElementsIncludingDeleted = () => {
return this.scene.getElementsIncludingDeleted();
};
private syncActionResult = withBatchedUpdates(
(actionResult: ActionResult) => {
if (this.unmounted || actionResult === false) {
@ -823,7 +835,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
});
queueBroadcastAllElements = throttle(() => {
this.broadcastScene(SCENE.UPDATE, /* syncAll */ true);
this.portal.broadcastScene(SCENE.UPDATE, /* syncAll */ true);
}, SYNC_FULL_SCENE_INTERVAL_MS);
componentDidUpdate(prevProps: ExcalidrawProps, prevState: AppState) {
@ -949,7 +961,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
getSceneVersion(this.scene.getElementsIncludingDeleted()) >
this.lastBroadcastedOrReceivedSceneVersion
) {
this.broadcastScene(SCENE.UPDATE, /* syncAll */ false);
this.portal.broadcastScene(SCENE.UPDATE, /* syncAll */ false);
this.queueBroadcastAllElements();
}
@ -1363,7 +1375,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
// we just received!
// Note: this needs to be set before replaceAllElements as it
// syncronously calls render.
this.lastBroadcastedOrReceivedSceneVersion = getSceneVersion(newElements);
this.setLastBroadcastedOrReceivedSceneVersion(
getSceneVersion(newElements),
);
this.scene.replaceAllElements(newElements);
}
@ -1511,28 +1526,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
});
}
private broadcastMouseLocation = (payload: {
pointer: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["pointer"];
button: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["button"];
}) => {
if (this.portal.socket?.id) {
const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
type: "MOUSE_LOCATION",
payload: {
socketId: this.portal.socket.id,
pointer: payload.pointer,
button: payload.button || "up",
selectedElementIds: this.state.selectedElementIds,
username: this.state.username,
},
};
return this.portal._broadcastSocketData(
data as SocketUpdateData,
true, // volatile
);
}
};
saveCollabRoomToFirebase = async (
syncableElements: ExcalidrawElement[] = getSyncableElements(
this.scene.getElementsIncludingDeleted(),
@ -1545,62 +1538,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
};
// maybe should move to Portal
broadcastScene = async (
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(
this.scene.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] = {
type: sceneType,
payload: {
elements: syncableElements,
},
};
this.lastBroadcastedOrReceivedSceneVersion = Math.max(
this.lastBroadcastedOrReceivedSceneVersion,
getSceneVersion(this.scene.getElementsIncludingDeleted()),
);
for (const syncableElement of syncableElements) {
this.broadcastedElementVersions.set(
syncableElement.id,
syncableElement.version,
);
}
const broadcastPromise = this.portal._broadcastSocketData(
data as SocketUpdateData,
);
if (syncAll && this.state.isCollaborating) {
await Promise.all([
broadcastPromise,
this.saveCollabRoomToFirebase(syncableElements),
]);
} else {
await broadcastPromise;
}
};
private onSceneUpdated = () => {
this.setState({});
};
@ -4078,7 +4015,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
this.portal.socket &&
// do not broadcast when more than 1 pointer since that shows flickering on the other side
gesture.pointers.size < 2 &&
this.broadcastMouseLocation({
this.portal.broadcastMouseLocation({
pointer,
button,
});

View File

@ -1,8 +1,9 @@
import { encryptAESGEM } from "../data";
import { encryptAESGEM, SocketUpdateDataSource } from "../data";
import { SocketUpdateData } from "../types";
import { BROADCAST, SCENE } from "../constants";
import App from "./App";
import { getSceneVersion, getSyncableElements } from "../element";
class Portal {
app: App;
@ -10,6 +11,7 @@ class Portal {
socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
roomID: string | null = null;
roomKey: string | null = null;
broadcastedElementVersions: Map<string, number> = new Map();
constructor(app: App) {
this.app = app;
@ -27,7 +29,7 @@ class Portal {
}
});
this.socket.on("new-user", async (_socketId: string) => {
this.app.broadcastScene(SCENE.INIT, /* syncAll */ true);
this.broadcastScene(SCENE.INIT, /* syncAll */ true);
});
this.socket.on("room-user-change", (clients: string[]) => {
this.app.setCollaborators(clients);
@ -69,5 +71,86 @@ class Portal {
);
}
}
broadcastScene = async (
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(
this.app.getSceneElementsIncludingDeleted(),
);
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] = {
type: sceneType,
payload: {
elements: syncableElements,
},
};
const currentVersion = this.app.getLastBroadcastedOrReceivedSceneVersion();
const newVersion = Math.max(
currentVersion,
getSceneVersion(this.app.getSceneElementsIncludingDeleted()),
);
this.app.setLastBroadcastedOrReceivedSceneVersion(newVersion);
for (const syncableElement of syncableElements) {
this.broadcastedElementVersions.set(
syncableElement.id,
syncableElement.version,
);
}
const broadcastPromise = this._broadcastSocketData(
data as SocketUpdateData,
);
if (syncAll && this.app.state.isCollaborating) {
await Promise.all([
broadcastPromise,
this.app.saveCollabRoomToFirebase(syncableElements),
]);
} else {
await broadcastPromise;
}
};
broadcastMouseLocation = (payload: {
pointer: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["pointer"];
button: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["button"];
}) => {
if (this.socket?.id) {
const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
type: "MOUSE_LOCATION",
payload: {
socketId: this.socket.id,
pointer: payload.pointer,
button: payload.button || "up",
selectedElementIds: this.app.state.selectedElementIds,
username: this.app.state.username,
},
};
return this._broadcastSocketData(
data as SocketUpdateData,
true, // volatile
);
}
};
}
export default Portal;