diff --git a/packages/core-v2/src/scene/scene.ts b/packages/core-v2/src/scene/scene.ts index 761f872..f5eb120 100644 --- a/packages/core-v2/src/scene/scene.ts +++ b/packages/core-v2/src/scene/scene.ts @@ -285,9 +285,12 @@ class Scene implements OriginScene { */ /** The latest release (finish call) of each operation. */ private readonly releases = new WeakMap(); - private readonly outcomes = new WeakMap< + /** Final outcome of each operation, once a release has decided it. */ + private readonly outcomes = new WeakMap(); + /** Interrupted releases waiting for a later release to decide. */ + private readonly waiters = new WeakMap< MutableOriginOperation, - { promise: Promise; resolve(committed: boolean): void } + Array<(committed: boolean) => void> >(); private readonly effectSets = new WeakMap< MutableOriginOperation, @@ -322,7 +325,7 @@ class Scene implements OriginScene { originKey: string, action: OriginAction, ): Promise { - this.refreshViewport(); + if (this.container) this.refreshViewport(); const operation = this.graph.begin( originKey, action, @@ -383,36 +386,40 @@ class Scene implements OriginScene { current, ); - const outcome = this.outcomeOf(operation); + // Another release already decided this operation. + const decided = this.outcomes.get(operation); + if (decided !== undefined) return decided; // A later operation may already have finalized this edge. const finished = (operation.phase as OriginOperationPhase) === "finished"; - if (!this.graph.operation(operation.id) || finished) { - const committed = this.graph.operation(operation.id) - ? operation.intent === "commit" - : commit; - outcome.resolve(committed); - return committed; - } - // Caught by update() or superseded by a later finish(). - if (!current()) return outcome.promise; + if (!this.graph.operation(operation.id) || finished) + return this.decide( + operation, + finished ? operation.intent === "commit" : commit, + ); + // Caught by update() or superseded by a later finish(): wait for the + // release that decides. + if (!current()) + return new Promise((resolve) => + this.waiters.set(operation, [ + ...(this.waiters.get(operation) ?? []), + resolve, + ]), + ); - const committed = commit - ? this.graph.commit(operation.id) - : (this.graph.cancel(operation.id), false); - outcome.resolve(committed); - return committed; + return this.decide( + operation, + commit + ? this.graph.commit(operation.id) + : (this.graph.cancel(operation.id), false), + ); } - /** Resolves with an operation's final outcome, whichever release decides. */ - private outcomeOf(operation: MutableOriginOperation) { - let outcome = this.outcomes.get(operation); - if (!outcome) { - let resolve!: (committed: boolean) => void; - const promise = new Promise((done) => (resolve = done)); - outcome = { promise, resolve }; - this.outcomes.set(operation, outcome); - } - return outcome; + /** Record an operation's final outcome and settle interrupted releases. */ + private decide(operation: MutableOriginOperation, committed: boolean) { + this.outcomes.set(operation, committed); + for (const resolve of this.waiters.get(operation) ?? []) resolve(committed); + this.waiters.delete(operation); + return committed; } registerElement(nodeKey: string, element: HTMLElement | null): void {