|
@@ -731,58 +731,63 @@ export class App extends React.Component<any, AppState> {
|
|
|
);
|
|
|
|
|
|
// Reconcile
|
|
|
- globalSceneState.replaceAllElements(
|
|
|
- restoredState.elements
|
|
|
- .reduce((elements, element) => {
|
|
|
- // if the remote element references one that's currently
|
|
|
- // edited on local, skip it (it'll be added in the next
|
|
|
- // step)
|
|
|
- if (
|
|
|
- element.id === this.state.editingElement?.id ||
|
|
|
- element.id === this.state.resizingElement?.id ||
|
|
|
- element.id === this.state.draggingElement?.id
|
|
|
- ) {
|
|
|
- return elements;
|
|
|
- }
|
|
|
+ const newElements = restoredState.elements
|
|
|
+ .reduce((elements, element) => {
|
|
|
+ // if the remote element references one that's currently
|
|
|
+ // edited on local, skip it (it'll be added in the next
|
|
|
+ // step)
|
|
|
+ if (
|
|
|
+ element.id === this.state.editingElement?.id ||
|
|
|
+ element.id === this.state.resizingElement?.id ||
|
|
|
+ element.id === this.state.draggingElement?.id
|
|
|
+ ) {
|
|
|
+ return elements;
|
|
|
+ }
|
|
|
|
|
|
+ if (
|
|
|
+ localElementMap.hasOwnProperty(element.id) &&
|
|
|
+ localElementMap[element.id].version > element.version
|
|
|
+ ) {
|
|
|
+ elements.push(localElementMap[element.id]);
|
|
|
+ delete localElementMap[element.id];
|
|
|
+ } else if (
|
|
|
+ localElementMap.hasOwnProperty(element.id) &&
|
|
|
+ localElementMap[element.id].version === element.version &&
|
|
|
+ localElementMap[element.id].versionNonce !==
|
|
|
+ element.versionNonce
|
|
|
+ ) {
|
|
|
+ // resolve conflicting edits deterministically by taking the one with the lowest versionNonce
|
|
|
if (
|
|
|
- localElementMap.hasOwnProperty(element.id) &&
|
|
|
- localElementMap[element.id].version > element.version
|
|
|
+ localElementMap[element.id].versionNonce <
|
|
|
+ element.versionNonce
|
|
|
) {
|
|
|
elements.push(localElementMap[element.id]);
|
|
|
- delete localElementMap[element.id];
|
|
|
- } else if (
|
|
|
- localElementMap.hasOwnProperty(element.id) &&
|
|
|
- localElementMap[element.id].version === element.version &&
|
|
|
- localElementMap[element.id].versionNonce !==
|
|
|
- element.versionNonce
|
|
|
- ) {
|
|
|
- // resolve conflicting edits deterministically by taking the one with the lowest versionNonce
|
|
|
- if (
|
|
|
- localElementMap[element.id].versionNonce <
|
|
|
- element.versionNonce
|
|
|
- ) {
|
|
|
- elements.push(localElementMap[element.id]);
|
|
|
- } else {
|
|
|
- // it should be highly unlikely that the two versionNonces are the same. if we are
|
|
|
- // really worried about this, we can replace the versionNonce with the socket id.
|
|
|
- elements.push(element);
|
|
|
- }
|
|
|
- delete localElementMap[element.id];
|
|
|
} else {
|
|
|
+ // it should be highly unlikely that the two versionNonces are the same. if we are
|
|
|
+ // really worried about this, we can replace the versionNonce with the socket id.
|
|
|
elements.push(element);
|
|
|
- delete localElementMap[element.id];
|
|
|
}
|
|
|
+ delete localElementMap[element.id];
|
|
|
+ } else {
|
|
|
+ elements.push(element);
|
|
|
+ delete localElementMap[element.id];
|
|
|
+ }
|
|
|
|
|
|
- return elements;
|
|
|
- }, [] as Mutable<typeof restoredState.elements>)
|
|
|
- // add local elements that weren't deleted or on remote
|
|
|
- .concat(...Object.values(localElementMap)),
|
|
|
+ return elements;
|
|
|
+ }, [] as Mutable<typeof restoredState.elements>)
|
|
|
+ // add local elements that weren't deleted or on remote
|
|
|
+ .concat(...Object.values(localElementMap));
|
|
|
+
|
|
|
+ // Avoid broadcasting to the rest of the collaborators the scene
|
|
|
+ // we just received!
|
|
|
+ // Note: this needs to be set before replaceAllElements as it
|
|
|
+ // syncronously calls render.
|
|
|
+ this.lastBroadcastedOrReceivedSceneVersion = getDrawingVersion(
|
|
|
+ newElements,
|
|
|
);
|
|
|
+
|
|
|
+ globalSceneState.replaceAllElements(newElements);
|
|
|
}
|
|
|
- this.lastBroadcastedOrReceivedSceneVersion = getDrawingVersion(
|
|
|
- globalSceneState.getAllElements(),
|
|
|
- );
|
|
|
|
|
|
// We haven't yet implemented multiplayer undo functionality, so we clear the undo stack
|
|
|
// when we receive any messages from another peer. This UX can be pretty rough -- if you
|