From cdd7f6158b26eae86e29b424b2c7859b3d1d3355 Mon Sep 17 00:00:00 2001 From: Marcel Mraz Date: Wed, 22 Jan 2025 22:16:33 +0100 Subject: [PATCH] Testing concurrent remote updates (wip) --- packages/excalidraw/components/App.tsx | 5 +++-- packages/excalidraw/store.ts | 2 ++ packages/excalidraw/sync/client.ts | 14 ++++++++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index da395822aa..97747f0f89 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -3878,6 +3878,7 @@ class App extends React.Component { // flush all incoming updates immediately, so that they couldn't be batched with other updates, having different `storeAction` flushSync(() => { const { elements, appState, collaborators, storeAction } = sceneData; + const nextElements = elements ? syncInvalidIndices(elements) : undefined; @@ -3892,8 +3893,8 @@ class App extends React.Component { const nextCommittedElements = elements ? this.store.filterUncomittedElements( - this.scene.getElementsMapIncludingDeleted(), // Only used to detect uncomitted local elements - arrayToMap(nextElements ?? []), // We expect all (already reconciled) elements + this.scene.getElementsMapIncludingDeleted(), // only used to detect uncomitted local elements + arrayToMap(nextElements ?? []), // we expect all (already reconciled) elements ) : prevCommittedElements; diff --git a/packages/excalidraw/store.ts b/packages/excalidraw/store.ts index f0bc82d54e..10a5821050 100644 --- a/packages/excalidraw/store.ts +++ b/packages/excalidraw/store.ts @@ -224,6 +224,8 @@ export class Store { const increment = new EphemeralStoreIncrement(change); // Notify listeners with the increment + // CFDO: consider having this async instead, possibly should also happen after the component updates; + // or get rid of filtering local in progress elements, switch to unidirectional store flow and keep it synchronous this.onStoreIncrementEmitter.trigger(increment); return nextSnapshot; diff --git a/packages/excalidraw/sync/client.ts b/packages/excalidraw/sync/client.ts index 2d393dd97f..a23d2768d5 100644 --- a/packages/excalidraw/sync/client.ts +++ b/packages/excalidraw/sync/client.ts @@ -17,6 +17,7 @@ import type { ExcalidrawElement, SceneElementsMap } from "../element/types"; import type { CLIENT_MESSAGE_RAW, SERVER_DELTA, CHANGE } from "./protocol"; import { debounce } from "../utils"; import { randomId } from "../random"; +import { orderByFractionalIndex } from "../fractionalIndex"; class SocketMessage implements CLIENT_MESSAGE_RAW { constructor( @@ -388,13 +389,18 @@ export class SyncClient { !existingElement || // new element existingElement.version < relayedElement.version // updated element ) { + // CFDO: in theory could make the yet unsynced element (due to a bug) to move to the top nextElements.set(id, relayedElement); this.relayedElementsVersionsCache.set(id, relayedElement.version); } } + const orderedElements = orderByFractionalIndex( + Array.from(nextElements.values()), + ); + this.api.updateScene({ - elements: Array.from(nextElements.values()), + elements: orderedElements, storeAction: StoreAction.UPDATE, }); } catch (e) { @@ -468,9 +474,13 @@ export class SyncClient { prevSnapshot = this.api.store.snapshot; } + const orderedElements = orderByFractionalIndex( + Array.from(nextElements.values()), + ); + // CFDO: might need to restore first due to potentially stale delta versions this.api.updateScene({ - elements: Array.from(nextElements.values()), + elements: orderedElements, // even though the snapshot should be up-to-date already, // still some more updates might be triggered, // i.e. as a result from syncing invalid indices