From 14f249a2f0c2e50c5958e32f0cd941922d35cf8e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:25:00 +1000 Subject: [PATCH] feat(ui): fix a few things that didn't unsubscribe correctly, add helper to manage subscriptions --- .../controlLayers/konva/CanvasManager.ts | 37 +++++++++---------- .../controlLayers/konva/CanvasStateApi.ts | 5 ++- .../features/controlLayers/konva/events.ts | 1 - .../src/features/controlLayers/konva/util.ts | 21 +++++++++++ 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts index 04fa9c8be4..69e69b161a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts @@ -254,12 +254,6 @@ export class CanvasManager { } } - syncStageScale() { - for (const layer of this.layers.values()) { - layer.syncStageScale(); - } - } - arrangeEntities() { const { getLayersState, getControlAdaptersState, getRegionsState } = this.stateApi; const layers = getLayersState().entities; @@ -462,7 +456,7 @@ export class CanvasManager { this.log.debug('Initializing renderer'); this.stage.container(this.container); - const cleanupListeners = setStageEventHandlers(this); + const unsubscribeListeners = setStageEventHandlers(this); // We can use a resize observer to ensure the stage always fits the container. We also need to re-render the bg and // document bounds overlay when the stage is resized. @@ -473,19 +467,23 @@ export class CanvasManager { const unsubscribeRenderer = this._store.subscribe(this.render); // When we this flag, we need to render the staging area - $shouldShowStagedImage.subscribe(async (shouldShowStagedImage, prevShouldShowStagedImage) => { - if (shouldShowStagedImage !== prevShouldShowStagedImage) { - this.log.debug('Rendering staging area'); - await this.preview.stagingArea.render(); + const unsubscribeShouldShowStagedImage = $shouldShowStagedImage.subscribe( + async (shouldShowStagedImage, prevShouldShowStagedImage) => { + if (shouldShowStagedImage !== prevShouldShowStagedImage) { + this.log.debug('Rendering staging area'); + await this.preview.stagingArea.render(); + } } - }); + ); - $lastProgressEvent.subscribe(async (lastProgressEvent, prevLastProgressEvent) => { - if (lastProgressEvent !== prevLastProgressEvent) { - this.log.debug('Rendering progress image'); - await this.preview.progressPreview.render(lastProgressEvent); + const unsubscribeLastProgressEvent = $lastProgressEvent.subscribe( + async (lastProgressEvent, prevLastProgressEvent) => { + if (lastProgressEvent !== prevLastProgressEvent) { + this.log.debug('Rendering progress image'); + await this.preview.progressPreview.render(lastProgressEvent); + } } - }); + ); this.log.debug('First render of konva stage'); this.preview.tool.render(); @@ -494,8 +492,9 @@ export class CanvasManager { return () => { this.log.debug('Cleaning up konva renderer'); unsubscribeRenderer(); - cleanupListeners(); - $shouldShowStagedImage.off(); + unsubscribeListeners(); + unsubscribeShouldShowStagedImage(); + unsubscribeLastProgressEvent(); resizeObserver.disconnect(); }; }; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts index e463a0dbd8..f1cb1733c6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts @@ -2,6 +2,7 @@ import { $alt, $ctrl, $meta, $shift } from '@invoke-ai/ui-library'; import type { Store } from '@reduxjs/toolkit'; import { logger } from 'app/logging/logger'; import type { RootState } from 'app/store/store'; +import { buildSubscribe } from 'features/controlLayers/konva/util'; import { $isDrawing, $isMouseDown, @@ -293,11 +294,11 @@ export class CanvasStateApi { onMetaChanged = $meta.subscribe; getShiftKey = $shift.get; - onShiftChanged = $shift.subscribe; + onShiftChanged = buildSubscribe($shift.subscribe, 'onShiftChanged'); getShouldShowStagedImage = $shouldShowStagedImage.get; onGetShouldShowStagedImageChanged = $shouldShowStagedImage.subscribe; setStageAttrs = $stageAttrs.set; - onStageAttrsChanged = $stageAttrs.subscribe; + onStageAttrsChanged = buildSubscribe($stageAttrs.subscribe, 'onStageAttrsChanged'); } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts index f590ea4939..ebce9006e1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts @@ -494,7 +494,6 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => { scale: newScale, }); manager.background.render(); - manager.syncStageScale(); } } manager.preview.tool.render(); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/util.ts b/invokeai/frontend/web/src/features/controlLayers/konva/util.ts index 8273c0455e..71f66151c4 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/util.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/util.ts @@ -7,6 +7,7 @@ import Konva from 'konva'; import type { KonvaEventObject } from 'konva/lib/Node'; import type { Vector2d } from 'konva/lib/types'; import { customAlphabet } from 'nanoid'; +import type { WritableAtom } from 'nanostores'; import type { ImageDTO } from 'services/api/types'; import { assert } from 'tsafe'; @@ -591,3 +592,23 @@ export function getObjectId(type: RenderableObject['type'], isBuffer?: boolean): return getPrefixedId(type); } } + +export type Subscription = { + name: string; + unsubscribe: () => void; +}; + +/** + * Builds a subscribe function for a nanostores atom. + * @param subscribe The subscribe function of the atom + * @param name The name of the atom + * @returns A subscribe function that returns an object with the name and unsubscribe function + */ +export const buildSubscribe = (subscribe: WritableAtom['subscribe'], name: string) => { + return (cb: Parameters['subscribe']>[0]): Subscription => { + return { + name, + unsubscribe: subscribe(cb), + }; + }; +};