Testing concurrent remote updates (wip)

This commit is contained in:
Marcel Mraz 2025-01-22 22:16:33 +01:00
parent 7e0f5b6369
commit cdd7f6158b
No known key found for this signature in database
GPG key ID: 4EBD6E62DC830CD2
3 changed files with 17 additions and 4 deletions

View file

@ -3878,6 +3878,7 @@ class App extends React.Component<AppProps, AppState> {
// flush all incoming updates immediately, so that they couldn't be batched with other updates, having different `storeAction` // flush all incoming updates immediately, so that they couldn't be batched with other updates, having different `storeAction`
flushSync(() => { flushSync(() => {
const { elements, appState, collaborators, storeAction } = sceneData; const { elements, appState, collaborators, storeAction } = sceneData;
const nextElements = elements const nextElements = elements
? syncInvalidIndices(elements) ? syncInvalidIndices(elements)
: undefined; : undefined;
@ -3892,8 +3893,8 @@ class App extends React.Component<AppProps, AppState> {
const nextCommittedElements = elements const nextCommittedElements = elements
? this.store.filterUncomittedElements( ? this.store.filterUncomittedElements(
this.scene.getElementsMapIncludingDeleted(), // Only used to detect uncomitted local elements this.scene.getElementsMapIncludingDeleted(), // only used to detect uncomitted local elements
arrayToMap(nextElements ?? []), // We expect all (already reconciled) elements arrayToMap(nextElements ?? []), // we expect all (already reconciled) elements
) )
: prevCommittedElements; : prevCommittedElements;

View file

@ -224,6 +224,8 @@ export class Store {
const increment = new EphemeralStoreIncrement(change); const increment = new EphemeralStoreIncrement(change);
// Notify listeners with the increment // 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); this.onStoreIncrementEmitter.trigger(increment);
return nextSnapshot; return nextSnapshot;

View file

@ -17,6 +17,7 @@ import type { ExcalidrawElement, SceneElementsMap } from "../element/types";
import type { CLIENT_MESSAGE_RAW, SERVER_DELTA, CHANGE } from "./protocol"; import type { CLIENT_MESSAGE_RAW, SERVER_DELTA, CHANGE } from "./protocol";
import { debounce } from "../utils"; import { debounce } from "../utils";
import { randomId } from "../random"; import { randomId } from "../random";
import { orderByFractionalIndex } from "../fractionalIndex";
class SocketMessage implements CLIENT_MESSAGE_RAW { class SocketMessage implements CLIENT_MESSAGE_RAW {
constructor( constructor(
@ -388,13 +389,18 @@ export class SyncClient {
!existingElement || // new element !existingElement || // new element
existingElement.version < relayedElement.version // updated 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); nextElements.set(id, relayedElement);
this.relayedElementsVersionsCache.set(id, relayedElement.version); this.relayedElementsVersionsCache.set(id, relayedElement.version);
} }
} }
const orderedElements = orderByFractionalIndex(
Array.from(nextElements.values()),
);
this.api.updateScene({ this.api.updateScene({
elements: Array.from(nextElements.values()), elements: orderedElements,
storeAction: StoreAction.UPDATE, storeAction: StoreAction.UPDATE,
}); });
} catch (e) { } catch (e) {
@ -468,9 +474,13 @@ export class SyncClient {
prevSnapshot = this.api.store.snapshot; prevSnapshot = this.api.store.snapshot;
} }
const orderedElements = orderByFractionalIndex(
Array.from(nextElements.values()),
);
// CFDO: might need to restore first due to potentially stale delta versions // CFDO: might need to restore first due to potentially stale delta versions
this.api.updateScene({ this.api.updateScene({
elements: Array.from(nextElements.values()), elements: orderedElements,
// even though the snapshot should be up-to-date already, // even though the snapshot should be up-to-date already,
// still some more updates might be triggered, // still some more updates might be triggered,
// i.e. as a result from syncing invalid indices // i.e. as a result from syncing invalid indices