feat(ui): add CanvasModuleBase class to standardize canvas APIs

I did this ages ago but undid it for some reason, not sure why. Caught a few issues related to subscriptions.
This commit is contained in:
psychedelicious 2024-08-26 21:12:31 +10:00
parent 17e76981bb
commit 301da97670
24 changed files with 469 additions and 186 deletions

View File

@ -38,8 +38,8 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
} }
const manager = new CanvasManager(stage, container, store, socket); const manager = new CanvasManager(stage, container, store, socket);
const cleanup = manager.initialize(); manager.initialize();
return cleanup; return manager.destroy;
}, [asPreview, container, socket, stage, store]); }, [asPreview, container, socket, stage, store]);
useLayoutEffect(() => { useLayoutEffect(() => {

View File

@ -1,29 +1,35 @@
import { getArbitraryBaseColor } from '@invoke-ai/ui-library'; import { getArbitraryBaseColor } from '@invoke-ai/ui-library';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr';
export class CanvasBackgroundModule { export class CanvasBackgroundModule extends CanvasModuleBase {
readonly type = 'background_grid'; readonly type = 'background';
static GRID_LINE_COLOR_COARSE = getArbitraryBaseColor(27); static GRID_LINE_COLOR_COARSE = getArbitraryBaseColor(27);
static GRID_LINE_COLOR_FINE = getArbitraryBaseColor(18); static GRID_LINE_COLOR_FINE = getArbitraryBaseColor(18);
id: string; id: string;
path: string[];
manager: CanvasManager; manager: CanvasManager;
subscriptions = new Set<() => void>();
log: Logger;
konva: { konva: {
layer: Konva.Layer; layer: Konva.Layer;
}; };
/**
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
*/
subscriptions: Set<() => void> = new Set();
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating background module');
this.konva = { layer: new Konva.Layer({ name: `${this.type}:layer`, listening: false }) }; this.konva = { layer: new Konva.Layer({ name: `${this.type}:layer`, listening: false }) };
this.subscriptions.add( this.subscriptions.add(
@ -116,9 +122,8 @@ export class CanvasBackgroundModule {
} }
destroy = () => { destroy = () => {
for (const cleanup of this.subscriptions) { this.log.trace('Destroying background module');
cleanup(); this.subscriptions.forEach((unsubscribe) => unsubscribe());
}
this.konva.layer.destroy(); this.konva.layer.destroy();
}; };
@ -145,4 +150,16 @@ export class CanvasBackgroundModule {
} }
return 256; return 256;
}; };
repr = () => {
return {
id: this.id,
path: this.path,
type: this.type,
};
};
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
};
} }

View File

@ -1,6 +1,7 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import { roundToMultiple, roundToMultipleMin } from 'common/util/roundDownToMultiple'; import { roundToMultiple, roundToMultipleMin } from 'common/util/roundDownToMultiple';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule'; import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { Rect } from 'features/controlLayers/store/types'; import type { Rect } from 'features/controlLayers/store/types';
@ -22,20 +23,17 @@ const ALL_ANCHORS: string[] = [
const CORNER_ANCHORS: string[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']; const CORNER_ANCHORS: string[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
const NO_ANCHORS: string[] = []; const NO_ANCHORS: string[] = [];
export class CanvasBboxModule { export class CanvasBboxModule extends CanvasModuleBase {
readonly type = 'generation_bbox'; readonly type = 'bbox';
id: string; id: string;
path: string[]; path: string[];
parent: CanvasPreviewModule;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
/**
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
*/
subscriptions: Set<() => void> = new Set(); subscriptions: Set<() => void> = new Set();
parent: CanvasPreviewModule;
konva: { konva: {
group: Konva.Group; group: Konva.Group;
rect: Konva.Rect; rect: Konva.Rect;
@ -43,13 +41,14 @@ export class CanvasBboxModule {
}; };
constructor(parent: CanvasPreviewModule) { constructor(parent: CanvasPreviewModule) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.parent = parent; this.parent = parent;
this.manager = this.parent.manager; this.manager = this.parent.manager;
this.path = this.manager.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace('Creating bbox'); this.log.debug('Creating bbox module');
// Create a stash to hold onto the last aspect ratio of the bbox - this allows for locking the aspect ratio when // Create a stash to hold onto the last aspect ratio of the bbox - this allows for locking the aspect ratio when
// transforming the bbox. // transforming the bbox.
@ -238,7 +237,7 @@ export class CanvasBboxModule {
} }
render = () => { render = () => {
this.log.trace('Rendering generation bbox'); this.log.trace('Rendering bbox module');
const bbox = this.manager.stateApi.getBbox(); const bbox = this.manager.stateApi.getBbox();
const tool = this.manager.stateApi.$tool.get(); const tool = this.manager.stateApi.$tool.get();
@ -261,15 +260,21 @@ export class CanvasBboxModule {
}); });
}; };
repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
};
};
destroy = () => { destroy = () => {
this.log.trace('Destroying generation bbox'); this.log.trace('Destroying bbox module');
for (const unsubscribe of this.subscriptions) { this.subscriptions.forEach((unsubscribe) => unsubscribe());
unsubscribe();
}
this.konva.group.destroy(); this.konva.group.destroy();
}; };
getLoggingContext = (): SerializableObject => { getLoggingContext = (): SerializableObject => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,13 +1,13 @@
import type { SerializableObject } from 'common/types';
import { rgbaColorToString } from 'common/util/colorCodeTransformers'; import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import type { CanvasBrushLineState } from 'features/controlLayers/store/types'; import type { CanvasBrushLineState } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasBrushLineRenderer { export class CanvasBrushLineRenderer extends CanvasModuleBase {
readonly type = 'brush_line_renderer'; readonly type = 'brush_line_renderer';
id: string; id: string;
@ -15,6 +15,7 @@ export class CanvasBrushLineRenderer {
parent: CanvasObjectRenderer; parent: CanvasObjectRenderer;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
state: CanvasBrushLineState; state: CanvasBrushLineState;
konva: { konva: {
@ -23,6 +24,7 @@ export class CanvasBrushLineRenderer {
}; };
constructor(state: CanvasBrushLineState, parent: CanvasObjectRenderer) { constructor(state: CanvasBrushLineState, parent: CanvasObjectRenderer) {
super();
const { id, clip } = state; const { id, clip } = state;
this.id = id; this.id = id;
this.parent = parent; this.parent = parent;
@ -30,7 +32,7 @@ export class CanvasBrushLineRenderer {
this.path = this.parent.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace({ state }, 'Creating brush line'); this.log.debug({ state }, 'Creating brush line renderer module');
this.konva = { this.konva = {
group: new Konva.Group({ group: new Konva.Group({
@ -69,26 +71,28 @@ export class CanvasBrushLineRenderer {
return false; return false;
} }
destroy() { destroy = () => {
this.log.trace('Destroying brush line'); this.log.debug('Destroying brush line renderer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy(); this.konva.group.destroy();
} };
setVisibility(isVisible: boolean): void { setVisibility(isVisible: boolean): void {
this.log.trace({ isVisible }, 'Setting brush line visibility'); this.log.trace({ isVisible }, 'Setting brush line visibility');
this.konva.group.visible(isVisible); this.konva.group.visible(isVisible);
} }
repr() { repr = () => {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
parent: this.parent.id, parent: this.parent.id,
state: deepClone(this.state), state: deepClone(this.state),
}; };
} };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,26 +1,31 @@
import type { SerializableObject } from 'common/types';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { GenerationMode } from 'features/controlLayers/store/types'; import type { GenerationMode } from 'features/controlLayers/store/types';
import { LRUCache } from 'lru-cache'; import { LRUCache } from 'lru-cache';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasCacheModule { export class CanvasCacheModule extends CanvasModuleBase {
readonly type = 'cache';
id: string; id: string;
path: string[]; path: string[];
log: Logger; log: Logger;
manager: CanvasManager; manager: CanvasManager;
subscriptions = new Set<() => void>();
imageNameCache = new LRUCache<string, string>({ max: 100 }); imageNameCache = new LRUCache<string, string>({ max: 100 });
canvasElementCache = new LRUCache<string, HTMLCanvasElement>({ max: 32 }); canvasElementCache = new LRUCache<string, HTMLCanvasElement>({ max: 32 });
generationModeCache = new LRUCache<string, GenerationMode>({ max: 100 }); generationModeCache = new LRUCache<string, GenerationMode>({ max: 100 });
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId('cache'); this.id = getPrefixedId('cache');
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating canvas cache');
this.log.debug('Creating cache module');
} }
clearAll = () => { clearAll = () => {
@ -29,7 +34,21 @@ export class CanvasCacheModule {
this.generationModeCache.clear(); this.generationModeCache.clear();
}; };
getLoggingContext = (): SerializableObject => { repr = () => {
return {
id: this.id,
path: this.path,
type: this.type,
};
};
destroy = () => {
this.log.debug('Destroying cache module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.clearAll();
};
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,5 +1,6 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { import {
canvasToBlob, canvasToBlob,
canvasToImageData, canvasToImageData,
@ -14,18 +15,22 @@ import type { ImageDTO } from 'services/api/types';
import stableHash from 'stable-hash'; import stableHash from 'stable-hash';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export class CanvasCompositorModule { export class CanvasCompositorModule extends CanvasModuleBase {
readonly type = 'compositor';
id: string; id: string;
path: string[]; path: string[];
log: Logger; log: Logger;
manager: CanvasManager; manager: CanvasManager;
subscriptions = new Set<() => void>();
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId('canvas_compositor'); this.id = getPrefixedId('canvas_compositor');
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating canvas compositor'); this.log.debug('Creating compositor module');
} }
getCompositeRasterLayerEntityIds = (): string[] => { getCompositeRasterLayerEntityIds = (): string[] => {
@ -237,7 +242,20 @@ export class CanvasCompositorModule {
return generationMode; return generationMode;
} }
getLoggingContext = (): SerializableObject => { repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
};
};
destroy = () => {
this.log.trace('Destroying compositor module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
};
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,12 +1,12 @@
import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import type { CanvasEraserLineState } from 'features/controlLayers/store/types'; import type { CanvasEraserLineState } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasEraserLineRenderer { export class CanvasEraserLineRenderer extends CanvasModuleBase {
readonly type = 'eraser_line_renderer'; readonly type = 'eraser_line_renderer';
id: string; id: string;
@ -14,6 +14,7 @@ export class CanvasEraserLineRenderer {
parent: CanvasObjectRenderer; parent: CanvasObjectRenderer;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
state: CanvasEraserLineState; state: CanvasEraserLineState;
konva: { konva: {
@ -22,6 +23,7 @@ export class CanvasEraserLineRenderer {
}; };
constructor(state: CanvasEraserLineState, parent: CanvasObjectRenderer) { constructor(state: CanvasEraserLineState, parent: CanvasObjectRenderer) {
super();
const { id, clip } = state; const { id, clip } = state;
this.id = id; this.id = id;
this.parent = parent; this.parent = parent;
@ -29,7 +31,7 @@ export class CanvasEraserLineRenderer {
this.path = this.parent.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace({ state }, 'Creating eraser line'); this.log.debug({ state }, 'Creating eraser line renderer module');
this.konva = { this.konva = {
group: new Konva.Group({ group: new Konva.Group({
@ -68,26 +70,28 @@ export class CanvasEraserLineRenderer {
return false; return false;
} }
destroy() { destroy = () => {
this.log.trace('Destroying eraser line'); this.log.debug('Destroying eraser line renderer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy(); this.konva.group.destroy();
} };
setVisibility(isVisible: boolean): void { setVisibility(isVisible: boolean): void {
this.log.trace({ isVisible }, 'Setting brush line visibility'); this.log.trace({ isVisible }, 'Setting brush line visibility');
this.konva.group.visible(isVisible); this.konva.group.visible(isVisible);
} }
repr() { repr = () => {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
parent: this.parent.id, parent: this.parent.id,
state: deepClone(this.state), state: deepClone(this.state),
}; };
} };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,6 +1,7 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter'; import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { CanvasEntityIdentifier, CanvasImageState, FilterConfig } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier, CanvasImageState, FilterConfig } from 'features/controlLayers/store/types';
import { IMAGE_FILTERS, imageDTOToImageObject } from 'features/controlLayers/store/types'; import { IMAGE_FILTERS, imageDTOToImageObject } from 'features/controlLayers/store/types';
@ -10,15 +11,14 @@ import { getImageDTO } from 'services/api/endpoints/images';
import type { BatchConfig, ImageDTO, S } from 'services/api/types'; import type { BatchConfig, ImageDTO, S } from 'services/api/types';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
const TYPE = 'entity_filter_preview'; export class CanvasFilterModule extends CanvasModuleBase {
readonly type = 'canvas_filter';
export class CanvasFilterModule {
readonly type = TYPE;
id: string; id: string;
path: string[]; path: string[];
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
imageState: CanvasImageState | null = null; imageState: CanvasImageState | null = null;
@ -28,11 +28,13 @@ export class CanvasFilterModule {
$config = atom<FilterConfig>(IMAGE_FILTERS.canny_image_processor.buildDefaults()); $config = atom<FilterConfig>(IMAGE_FILTERS.canny_image_processor.buildDefaults());
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace('Creating filter');
this.log.debug('Creating filter module');
} }
initialize = (entityIdentifier: CanvasEntityIdentifier) => { initialize = (entityIdentifier: CanvasEntityIdentifier) => {
@ -167,17 +169,19 @@ export class CanvasFilterModule {
}; };
destroy = () => { destroy = () => {
this.log.trace('Destroying filter'); this.log.trace('Destroying filter module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
}; };
repr = () => { repr = () => {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
}; };
}; };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,8 +1,8 @@
import { Mutex } from 'async-mutex'; import { Mutex } from 'async-mutex';
import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasFilterModule } from 'features/controlLayers/konva/CanvasFilterModule'; import type { CanvasFilterModule } from 'features/controlLayers/konva/CanvasFilterModule';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import type { CanvasStagingAreaModule } from 'features/controlLayers/konva/CanvasStagingAreaModule'; import type { CanvasStagingAreaModule } from 'features/controlLayers/konva/CanvasStagingAreaModule';
import { loadImage } from 'features/controlLayers/konva/util'; import { loadImage } from 'features/controlLayers/konva/util';
@ -12,7 +12,7 @@ import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import { getImageDTO } from 'services/api/endpoints/images'; import { getImageDTO } from 'services/api/endpoints/images';
export class CanvasImageRenderer { export class CanvasImageRenderer extends CanvasModuleBase {
readonly type = 'image_renderer'; readonly type = 'image_renderer';
id: string; id: string;
@ -20,6 +20,7 @@ export class CanvasImageRenderer {
parent: CanvasObjectRenderer | CanvasStagingAreaModule | CanvasFilterModule; parent: CanvasObjectRenderer | CanvasStagingAreaModule | CanvasFilterModule;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
state: CanvasImageState; state: CanvasImageState;
konva: { konva: {
@ -33,6 +34,7 @@ export class CanvasImageRenderer {
mutex = new Mutex(); mutex = new Mutex();
constructor(state: CanvasImageState, parent: CanvasObjectRenderer | CanvasStagingAreaModule | CanvasFilterModule) { constructor(state: CanvasImageState, parent: CanvasObjectRenderer | CanvasStagingAreaModule | CanvasFilterModule) {
super();
const { id, image } = state; const { id, image } = state;
const { width, height } = image; const { width, height } = image;
this.id = id; this.id = id;
@ -41,7 +43,7 @@ export class CanvasImageRenderer {
this.path = this.parent.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace({ state }, 'Creating image'); this.log.debug({ state }, 'Creating image renderer module');
this.konva = { this.konva = {
group: new Konva.Group({ name: `${this.type}:group`, listening: false }), group: new Konva.Group({ name: `${this.type}:group`, listening: false }),
@ -166,7 +168,8 @@ export class CanvasImageRenderer {
}; };
destroy = () => { destroy = () => {
this.log.trace('Destroying image'); this.log.debug('Destroying image renderer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy(); this.konva.group.destroy();
}; };
@ -179,6 +182,7 @@ export class CanvasImageRenderer {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
parent: this.parent.id, parent: this.parent.id,
isLoading: this.isLoading, isLoading: this.isLoading,
isError: this.isError, isError: this.isError,
@ -186,7 +190,7 @@ export class CanvasImageRenderer {
}; };
}; };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,6 +1,7 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer'; import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
import { getLastPointOfLine } from 'features/controlLayers/konva/util'; import { getLastPointOfLine } from 'features/controlLayers/konva/util';
@ -22,12 +23,13 @@ import type { Logger } from 'roarr';
import stableHash from 'stable-hash'; import stableHash from 'stable-hash';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export class CanvasLayerAdapter { export class CanvasLayerAdapter extends CanvasModuleBase {
readonly type = 'layer_adapter'; readonly type = 'layer_adapter';
id: string; id: string;
path: string[]; path: string[];
manager: CanvasManager; manager: CanvasManager;
subscriptions = new Set<() => void>();
log: Logger; log: Logger;
state: CanvasRasterLayerState | CanvasControlLayerState; state: CanvasRasterLayerState | CanvasControlLayerState;
@ -41,11 +43,14 @@ export class CanvasLayerAdapter {
isFirstRender: boolean = true; isFirstRender: boolean = true;
constructor(state: CanvasLayerAdapter['state'], manager: CanvasLayerAdapter['manager']) { constructor(state: CanvasLayerAdapter['state'], manager: CanvasLayerAdapter['manager']) {
super();
this.id = state.id; this.id = state.id;
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug({ state }, 'Creating layer');
this.log.debug({ state }, 'Creating layer adapter module');
this.state = state; this.state = state;
this.konva = { this.konva = {
@ -71,10 +76,10 @@ export class CanvasLayerAdapter {
}; };
destroy = (): void => { destroy = (): void => {
this.log.debug('Destroying layer'); this.log.debug('Destroying layer adapter module');
// We need to call the destroy method on all children so they can do their own cleanup. this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.transformer.destroy();
this.renderer.destroy(); this.renderer.destroy();
this.transformer.destroy();
this.konva.layer.destroy(); this.konva.layer.destroy();
}; };
@ -144,6 +149,7 @@ export class CanvasLayerAdapter {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
state: deepClone(this.state), state: deepClone(this.state),
transformer: this.transformer.repr(), transformer: this.transformer.repr(),
renderer: this.renderer.repr(), renderer: this.renderer.repr(),

View File

@ -6,6 +6,7 @@ import { SyncableMap } from 'common/util/SyncableMap/SyncableMap';
import { CanvasCacheModule } from 'features/controlLayers/konva/CanvasCacheModule'; import { CanvasCacheModule } from 'features/controlLayers/konva/CanvasCacheModule';
import { CanvasCompositorModule } from 'features/controlLayers/konva/CanvasCompositorModule'; import { CanvasCompositorModule } from 'features/controlLayers/konva/CanvasCompositorModule';
import { CanvasFilterModule } from 'features/controlLayers/konva/CanvasFilterModule'; import { CanvasFilterModule } from 'features/controlLayers/konva/CanvasFilterModule';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasRenderingModule } from 'features/controlLayers/konva/CanvasRenderingModule'; import { CanvasRenderingModule } from 'features/controlLayers/konva/CanvasRenderingModule';
import { CanvasStageModule } from 'features/controlLayers/konva/CanvasStageModule'; import { CanvasStageModule } from 'features/controlLayers/konva/CanvasStageModule';
import { CanvasWorkerModule } from 'features/controlLayers/konva/CanvasWorkerModule.js'; import { CanvasWorkerModule } from 'features/controlLayers/konva/CanvasWorkerModule.js';
@ -21,17 +22,20 @@ import { CanvasPreviewModule } from './CanvasPreviewModule';
import { CanvasStateApiModule } from './CanvasStateApiModule'; import { CanvasStateApiModule } from './CanvasStateApiModule';
export const $canvasManager = atom<CanvasManager | null>(null); export const $canvasManager = atom<CanvasManager | null>(null);
const TYPE = 'manager';
export class CanvasManager { export class CanvasManager extends CanvasModuleBase {
readonly type = TYPE; readonly type = 'manager';
id: string; id: string;
path: string[]; path: string[];
manager: CanvasManager;
log: Logger;
store: AppStore; store: AppStore;
socket: AppSocket; socket: AppSocket;
subscriptions = new Set<() => void>();
adapters = { adapters = {
rasterLayers: new SyncableMap<string, CanvasLayerAdapter>(), rasterLayers: new SyncableMap<string, CanvasLayerAdapter>(),
controlLayers: new SyncableMap<string, CanvasLayerAdapter>(), controlLayers: new SyncableMap<string, CanvasLayerAdapter>(),
@ -60,8 +64,21 @@ export class CanvasManager {
_isDebugging: boolean = false; _isDebugging: boolean = false;
constructor(stage: Konva.Stage, container: HTMLDivElement, store: AppStore, socket: AppSocket) { constructor(stage: Konva.Stage, container: HTMLDivElement, store: AppStore, socket: AppSocket) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.path = [this.id]; this.path = [this.id];
this.manager = this;
this.log = logger('canvas').child((message) => {
return {
...message,
context: {
...this.getLoggingContext(),
...message.context,
},
};
});
this.log.debug('Creating canvas manager module');
this.store = store; this.store = store;
this.socket = socket; this.socket = socket;
@ -80,16 +97,6 @@ export class CanvasManager {
this.stage.addLayer(this.background.konva.layer); this.stage.addLayer(this.background.konva.layer);
} }
log = logger('canvas').child((message) => {
return {
...message,
context: {
...this.getLoggingContext(),
...message.context,
},
};
});
enableDebugging() { enableDebugging() {
this._isDebugging = true; this._isDebugging = true;
this.logDebugInfo(); this.logDebugInfo();
@ -100,7 +107,7 @@ export class CanvasManager {
} }
initialize = () => { initialize = () => {
this.log.debug('Initializing canvas manager'); this.log.debug('Initializing canvas manager module');
// These atoms require the canvas manager to be set up before we can provide their initial values // These atoms require the canvas manager to be set up before we can provide their initial values
this.stateApi.$transformingEntity.set(null); this.stateApi.$transformingEntity.set(null);
@ -109,26 +116,52 @@ export class CanvasManager {
this.stateApi.$currentFill.set(this.stateApi.getCurrentFill()); this.stateApi.$currentFill.set(this.stateApi.getCurrentFill());
this.stateApi.$selectedEntity.set(this.stateApi.getSelectedEntity()); this.stateApi.$selectedEntity.set(this.stateApi.getSelectedEntity());
const cleanupStage = this.stage.initialize(); this.subscriptions.add(this.store.subscribe(this.renderer.render));
const cleanupStore = this.store.subscribe(this.renderer.render); this.stage.initialize();
};
return () => { destroy = () => {
this.log.debug('Cleaning up canvas manager'); this.log.debug('Destroying canvas manager module');
for (const adapter of this.adapters.getAll()) { this.subscriptions.forEach((unsubscribe) => unsubscribe());
adapter.destroy(); for (const adapter of this.adapters.getAll()) {
} adapter.destroy();
this.background.destroy(); }
this.preview.destroy(); this.stateApi.destroy();
cleanupStore(); this.preview.destroy();
cleanupStage(); this.background.destroy();
}; this.filter.destroy();
this.worker.destroy();
this.renderer.destroy();
this.compositor.destroy();
this.stage.destroy();
$canvasManager.set(null);
}; };
setCanvasManager = () => { setCanvasManager = () => {
this.log.debug('Setting canvas manager'); this.log.debug('Setting canvas manager global');
$canvasManager.set(this); $canvasManager.set(this);
}; };
repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
rasterLayers: Array.from(this.adapters.rasterLayers.values()).map((adapter) => adapter.repr()),
controlLayers: Array.from(this.adapters.controlLayers.values()).map((adapter) => adapter.repr()),
inpaintMasks: Array.from(this.adapters.inpaintMasks.values()).map((adapter) => adapter.repr()),
regionMasks: Array.from(this.adapters.regionMasks.values()).map((adapter) => adapter.repr()),
stateApi: this.stateApi.repr(),
preview: this.preview.repr(),
background: this.background.repr(),
filter: this.filter.repr(),
worker: this.worker.repr(),
renderer: this.renderer.repr(),
compositor: this.compositor.repr(),
stage: this.stage.repr(),
};
};
getLoggingContext = (): SerializableObject => { getLoggingContext = (): SerializableObject => {
return { return {
path: this.path.join('.'), path: this.path.join('.'),
@ -150,11 +183,6 @@ export class CanvasManager {
logDebugInfo() { logDebugInfo() {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('Canvas manager', this); console.log('Canvas manager', this);
for (const adapter of this.adapters.getAll()) { this.log.debug({ manager: this.repr() }, 'Canvas manager');
// eslint-disable-next-line no-console
console.log(adapter.id, adapter);
}
} }
getPrefixedId = getPrefixedId;
} }

View File

@ -1,6 +1,7 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer'; import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
import { getLastPointOfLine } from 'features/controlLayers/konva/util'; import { getLastPointOfLine } from 'features/controlLayers/konva/util';
@ -21,13 +22,14 @@ import { get, omit } from 'lodash-es';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import stableHash from 'stable-hash'; import stableHash from 'stable-hash';
export class CanvasMaskAdapter { export class CanvasMaskAdapter extends CanvasModuleBase {
readonly type = 'mask_adapter'; readonly type = 'mask_adapter';
id: string; id: string;
path: string[]; path: string[];
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
state: CanvasInpaintMaskState | CanvasRegionalGuidanceState; state: CanvasInpaintMaskState | CanvasRegionalGuidanceState;
@ -41,11 +43,14 @@ export class CanvasMaskAdapter {
}; };
constructor(state: CanvasMaskAdapter['state'], manager: CanvasMaskAdapter['manager']) { constructor(state: CanvasMaskAdapter['state'], manager: CanvasMaskAdapter['manager']) {
super();
this.id = state.id; this.id = state.id;
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug({ state }, 'Creating mask');
this.log.debug({ state }, 'Creating mask adapter module');
this.state = state; this.state = state;
this.konva = { this.konva = {
@ -71,8 +76,8 @@ export class CanvasMaskAdapter {
}; };
destroy = (): void => { destroy = (): void => {
this.log.debug('Destroying mask'); this.log.debug('Destroying mask adapter module');
// We need to call the destroy method on all children so they can do their own cleanup.
this.transformer.destroy(); this.transformer.destroy();
this.renderer.destroy(); this.renderer.destroy();
this.konva.layer.destroy(); this.konva.layer.destroy();
@ -157,6 +162,7 @@ export class CanvasMaskAdapter {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
state: deepClone(this.state), state: deepClone(this.state),
}; };
}; };
@ -182,7 +188,8 @@ export class CanvasMaskAdapter {
const canvas = this.renderer.getCanvas(rect, attrs); const canvas = this.renderer.getCanvas(rect, attrs);
return canvas; return canvas;
}; };
getLoggingContext = (): SerializableObject => {
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -0,0 +1,20 @@
import type { SerializableObject } from 'common/types';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { Logger } from 'roarr';
export abstract class CanvasModuleBase {
abstract id: string;
abstract type: string;
abstract path: string[];
abstract manager: CanvasManager;
abstract log: Logger;
abstract subscriptions: Set<() => void>;
abstract getLoggingContext: () => SerializableObject;
abstract destroy: () => void;
abstract repr: () => SerializableObject & {
id: string;
path: string[];
type: string;
};
}

View File

@ -1,4 +1,3 @@
import type { SerializableObject } from 'common/types';
import { rgbColorToString } from 'common/util/colorCodeTransformers'; import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine'; import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine'; import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
@ -6,6 +5,7 @@ import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter'; import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter'; import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect'; import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters'; import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters';
import { getPatternSVG } from 'features/controlLayers/konva/patterns/getPatternSVG'; import { getPatternSVG } from 'features/controlLayers/konva/patterns/getPatternSVG';
@ -55,8 +55,8 @@ type AnyObjectState = CanvasBrushLineState | CanvasEraserLineState | CanvasImage
/** /**
* Handles rendering of objects for a canvas entity. * Handles rendering of objects for a canvas entity.
*/ */
export class CanvasObjectRenderer { export class CanvasObjectRenderer extends CanvasModuleBase {
readonly type = 'object_renderer'; readonly type = 'entity_object_renderer';
id: string; id: string;
path: string[]; path: string[];
@ -123,12 +123,13 @@ export class CanvasObjectRenderer {
$canvasCache = atom<{ canvas: HTMLCanvasElement; rect: Rect } | null>(null); $canvasCache = atom<{ canvas: HTMLCanvasElement; rect: Rect } | null>(null);
constructor(parent: CanvasLayerAdapter | CanvasMaskAdapter) { constructor(parent: CanvasLayerAdapter | CanvasMaskAdapter) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.parent = parent; this.parent = parent;
this.path = this.parent.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.manager = parent.manager; this.manager = parent.manager;
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace('Creating object renderer'); this.log.debug('Creating entity object renderer module');
this.konva = { this.konva = {
objectGroup: new Konva.Group({ name: `${this.type}:object_group`, listening: false }), objectGroup: new Konva.Group({ name: `${this.type}:object_group`, listening: false }),
@ -597,11 +598,8 @@ export class CanvasObjectRenderer {
* Destroys this renderer and all of its object renderers. * Destroys this renderer and all of its object renderers.
*/ */
destroy = () => { destroy = () => {
this.log.trace('Destroying object renderer'); this.log.debug('Destroying entity object renderer module');
for (const cleanup of this.subscriptions) { this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.log.trace('Cleaning up listener');
cleanup();
}
for (const renderer of this.renderers.values()) { for (const renderer of this.renderers.values()) {
renderer.destroy(); renderer.destroy();
} }
@ -616,13 +614,14 @@ export class CanvasObjectRenderer {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
parent: this.parent.id, parent: this.parent.id,
renderers: Array.from(this.renderers.values()).map((renderer) => renderer.repr()), renderers: Array.from(this.renderers.values()).map((renderer) => renderer.repr()),
buffer: this.bufferRenderer?.repr(), buffer: this.bufferRenderer?.repr(),
}; };
}; };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,13 +1,22 @@
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasProgressImageModule } from 'features/controlLayers/konva/CanvasProgressImageModule'; import { CanvasProgressImageModule } from 'features/controlLayers/konva/CanvasProgressImageModule';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr';
import { CanvasBboxModule } from './CanvasBboxModule'; import { CanvasBboxModule } from './CanvasBboxModule';
import { CanvasStagingAreaModule } from './CanvasStagingAreaModule'; import { CanvasStagingAreaModule } from './CanvasStagingAreaModule';
import { CanvasToolModule } from './CanvasToolModule'; import { CanvasToolModule } from './CanvasToolModule';
export class CanvasPreviewModule { export class CanvasPreviewModule extends CanvasModuleBase {
readonly type = 'preview';
id: string;
path: string[];
manager: CanvasManager; manager: CanvasManager;
log: Logger;
subscriptions = new Set<() => void>();
konva: { konva: {
layer: Konva.Layer; layer: Konva.Layer;
@ -19,7 +28,14 @@ export class CanvasPreviewModule {
progressImage: CanvasProgressImageModule; progressImage: CanvasProgressImageModule;
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId(this.type);
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating preview module');
this.konva = { this.konva = {
layer: new Konva.Layer({ listening: false, imageSmoothingEnabled: false }), layer: new Konva.Layer({ listening: false, imageSmoothingEnabled: false }),
}; };
@ -41,11 +57,25 @@ export class CanvasPreviewModule {
return this.konva.layer; return this.konva.layer;
}; };
destroy() { repr = () => {
// this.stagingArea.destroy(); // TODO(psyche): implement destroy return {
id: this.id,
type: this.type,
path: this.path,
};
};
destroy = () => {
this.log.debug('Destroying preview module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.stagingArea.destroy();
this.progressImage.destroy(); this.progressImage.destroy();
// this.bbox.destroy(); // TODO(psyche): implement destroy this.bbox.destroy();
this.tool.destroy(); this.tool.destroy();
this.konva.layer.destroy(); this.konva.layer.destroy();
} };
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
};
} }

View File

@ -1,13 +1,13 @@
import { Mutex } from 'async-mutex'; import { Mutex } from 'async-mutex';
import type { SerializableObject } from 'common/types';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule'; import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
import { getPrefixedId, loadImage } from 'features/controlLayers/konva/util'; import { getPrefixedId, loadImage } from 'features/controlLayers/konva/util';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import type { S } from 'services/api/types'; import type { S } from 'services/api/types';
export class CanvasProgressImageModule { export class CanvasProgressImageModule extends CanvasModuleBase {
readonly type = 'progress_image'; readonly type = 'progress_image';
id: string; id: string;
@ -35,13 +35,14 @@ export class CanvasProgressImageModule {
mutex: Mutex = new Mutex(); mutex: Mutex = new Mutex();
constructor(parent: CanvasPreviewModule) { constructor(parent: CanvasPreviewModule) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.parent = parent; this.parent = parent;
this.manager = parent.manager; this.manager = parent.manager;
this.path = this.manager.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace('Creating progress image'); this.log.debug('Creating progress image module');
this.konva = { this.konva = {
group: new Konva.Group({ name: `${this.type}:group`, listening: false }), group: new Konva.Group({ name: `${this.type}:group`, listening: false }),
@ -105,15 +106,21 @@ export class CanvasProgressImageModule {
} }
}; };
repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
};
};
destroy = () => { destroy = () => {
this.log.trace('Destroying progress image'); this.log.debug('Destroying progress image module');
for (const unsubscribe of this.subscriptions) { this.subscriptions.forEach((unsubscribe) => unsubscribe());
unsubscribe();
}
this.konva.group.destroy(); this.konva.group.destroy();
}; };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,13 +1,13 @@
import type { SerializableObject } from 'common/types';
import { rgbaColorToString } from 'common/util/colorCodeTransformers'; import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import type { CanvasRectState } from 'features/controlLayers/store/types'; import type { CanvasRectState } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasRectRenderer { export class CanvasRectRenderer extends CanvasModuleBase {
readonly type = 'rect_renderer'; readonly type = 'rect_renderer';
id: string; id: string;
@ -15,6 +15,7 @@ export class CanvasRectRenderer {
parent: CanvasObjectRenderer; parent: CanvasObjectRenderer;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
state: CanvasRectState; state: CanvasRectState;
konva: { konva: {
@ -24,13 +25,15 @@ export class CanvasRectRenderer {
isFirstRender: boolean = false; isFirstRender: boolean = false;
constructor(state: CanvasRectState, parent: CanvasObjectRenderer) { constructor(state: CanvasRectState, parent: CanvasObjectRenderer) {
super();
const { id } = state; const { id } = state;
this.id = id; this.id = id;
this.parent = parent; this.parent = parent;
this.manager = parent.manager; this.manager = parent.manager;
this.path = this.parent.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace({ state }, 'Creating rect');
this.log.debug({ state }, 'Creating rect renderer module');
this.konva = { this.konva = {
group: new Konva.Group({ name: `${this.type}:group`, listening: false }), group: new Konva.Group({ name: `${this.type}:group`, listening: false }),
@ -60,27 +63,29 @@ export class CanvasRectRenderer {
return false; return false;
} }
destroy() {
this.log.trace('Destroying rect');
this.konva.group.destroy();
}
setVisibility(isVisible: boolean): void { setVisibility(isVisible: boolean): void {
this.log.trace({ isVisible }, 'Setting rect visibility'); this.log.trace({ isVisible }, 'Setting rect visibility');
this.konva.group.visible(isVisible); this.konva.group.visible(isVisible);
} }
repr() { destroy = () => {
this.log.debug('Destroying rect renderer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy();
};
repr = () => {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
parent: this.parent.id, parent: this.parent.id,
isFirstRender: this.isFirstRender, isFirstRender: this.isFirstRender,
state: deepClone(this.state), state: deepClone(this.state),
}; };
} };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -2,24 +2,29 @@ import type { SerializableObject } from 'common/types';
import { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter'; import { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter'; import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { CanvasV2State } from 'features/controlLayers/store/types'; import type { CanvasV2State } from 'features/controlLayers/store/types';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasRenderingModule { export class CanvasRenderingModule extends CanvasModuleBase {
readonly type = 'canvas_renderer';
id: string; id: string;
path: string[]; path: string[];
log: Logger; log: Logger;
manager: CanvasManager; manager: CanvasManager;
subscriptions = new Set<() => void>();
state: CanvasV2State | null = null; state: CanvasV2State | null = null;
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId('canvas_renderer'); this.id = getPrefixedId('canvas_renderer');
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating canvas renderer'); this.log.debug('Creating canvas renderer module');
} }
render = async () => { render = async () => {
@ -263,4 +268,17 @@ export class CanvasRenderingModule {
this.manager.preview.getLayer().zIndex(++zIndex); this.manager.preview.getLayer().zIndex(++zIndex);
} }
}; };
repr = () => {
return {
id: this.id,
path: this.path,
type: this.type,
};
};
destroy = () => {
this.log.debug('Destroying canvas renderer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
};
} }

View File

@ -1,5 +1,5 @@
import type { SerializableObject } from 'common/types';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CANVAS_SCALE_BY } from 'features/controlLayers/konva/constants'; import { CANVAS_SCALE_BY } from 'features/controlLayers/konva/constants';
import { getPrefixedId, getRectUnion } from 'features/controlLayers/konva/util'; import { getPrefixedId, getRectUnion } from 'features/controlLayers/konva/util';
import type { Coordinate, Dimensions, Rect } from 'features/controlLayers/store/types'; import type { Coordinate, Dimensions, Rect } from 'features/controlLayers/store/types';
@ -8,7 +8,9 @@ import type { KonvaEventObject } from 'konva/lib/Node';
import { clamp } from 'lodash-es'; import { clamp } from 'lodash-es';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasStageModule { export class CanvasStageModule extends CanvasModuleBase {
readonly type = 'stage';
static MIN_CANVAS_SCALE = 0.1; static MIN_CANVAS_SCALE = 0.1;
static MAX_CANVAS_SCALE = 20; static MAX_CANVAS_SCALE = 20;
@ -19,12 +21,17 @@ export class CanvasStageModule {
container: HTMLDivElement; container: HTMLDivElement;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
constructor(stage: Konva.Stage, container: HTMLDivElement, manager: CanvasManager) { constructor(stage: Konva.Stage, container: HTMLDivElement, manager: CanvasManager) {
super();
this.id = getPrefixedId('stage'); this.id = getPrefixedId('stage');
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating stage module'); this.log.debug('Creating stage module');
this.container = container; this.container = container;
this.konva = { stage }; this.konva = { stage };
} }
@ -50,12 +57,10 @@ export class CanvasStageModule {
this.fitLayersToStage(); this.fitLayersToStage();
const cleanupListeners = this.setEventListeners(); const cleanupListeners = this.setEventListeners();
return () => { this.subscriptions.add(cleanupListeners);
this.log.debug('Destroying stage'); this.subscriptions.add(() => {
resizeObserver.disconnect(); resizeObserver.disconnect();
this.konva.stage.destroy(); });
cleanupListeners();
};
}; };
fitStageToContainer = () => { fitStageToContainer = () => {
@ -276,7 +281,21 @@ export class CanvasStageModule {
this.konva.stage.add(layer); this.konva.stage.add(layer);
}; };
getLoggingContext = (): SerializableObject => { repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
};
};
destroy = () => {
this.log.debug('Destroying stage module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.stage.destroy();
};
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,13 +1,14 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage'; import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule'; import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import { imageDTOToImageWithDims, type StagingAreaImage } from 'features/controlLayers/store/types'; import { imageDTOToImageWithDims, type StagingAreaImage } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasStagingAreaModule { export class CanvasStagingAreaModule extends CanvasModuleBase {
readonly type = 'staging_area'; readonly type = 'staging_area';
id: string; id: string;
@ -27,12 +28,14 @@ export class CanvasStagingAreaModule {
subscriptions: Set<() => void> = new Set(); subscriptions: Set<() => void> = new Set();
constructor(parent: CanvasPreviewModule) { constructor(parent: CanvasPreviewModule) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.parent = parent; this.parent = parent;
this.manager = this.parent.manager; this.manager = this.parent.manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating staging area');
this.log.debug('Creating staging area module');
this.konva = { group: new Konva.Group({ name: `${this.type}:group`, listening: false }) }; this.konva = { group: new Konva.Group({ name: `${this.type}:group`, listening: false }) };
this.image = null; this.image = null;
@ -85,12 +88,11 @@ export class CanvasStagingAreaModule {
}; };
destroy = () => { destroy = () => {
this.log.debug('Destroying staging area module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
if (this.image) { if (this.image) {
this.image.destroy(); this.image.destroy();
} }
for (const unsubscribe of this.subscriptions) {
unsubscribe();
}
for (const node of this.getNodes()) { for (const node of this.getNodes()) {
node.destroy(); node.destroy();
} }
@ -100,6 +102,7 @@ export class CanvasStagingAreaModule {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
selectedImage: this.selectedImage, selectedImage: this.selectedImage,
}; };
}; };

View File

@ -3,6 +3,8 @@ import type { AppStore } from 'app/store/store';
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter'; import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter'; import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { import {
bboxChanged, bboxChanged,
brushWidthChanged, brushWidthChanged,
@ -40,6 +42,7 @@ import type {
import { RGBA_BLACK } from 'features/controlLayers/store/types'; import { RGBA_BLACK } from 'features/controlLayers/store/types';
import type { WritableAtom } from 'nanostores'; import type { WritableAtom } from 'nanostores';
import { atom } from 'nanostores'; import { atom } from 'nanostores';
import type { Logger } from 'roarr';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
import type { BatchConfig } from 'services/api/types'; import type { BatchConfig } from 'services/api/types';
import { $lastCanvasProgressEvent } from 'services/events/setEventListeners'; import { $lastCanvasProgressEvent } from 'services/events/setEventListeners';
@ -70,13 +73,27 @@ type EntityStateAndAdapter =
adapter: CanvasMaskAdapter; adapter: CanvasMaskAdapter;
}; };
export class CanvasStateApiModule { export class CanvasStateApiModule extends CanvasModuleBase {
store: AppStore; readonly type = 'state_api';
id: string;
path: string[];
manager: CanvasManager; manager: CanvasManager;
log: Logger;
subscriptions = new Set<() => void>();
store: AppStore;
constructor(store: AppStore, manager: CanvasManager) { constructor(store: AppStore, manager: CanvasManager) {
this.store = store; super();
this.id = getPrefixedId(this.type);
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating state api module');
this.store = store;
} }
// Reminder - use arrow functions to avoid binding issues // Reminder - use arrow functions to avoid binding issues
@ -258,4 +275,21 @@ export class CanvasStateApiModule {
height: 0, height: 0,
scale: 0, scale: 0,
}); });
destroy = () => {
this.log.debug('Destroying state api module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
};
repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
};
};
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
};
} }

View File

@ -1,6 +1,6 @@
import type { SerializableObject } from 'common/types';
import { rgbaColorToString, rgbColorToString } from 'common/util/colorCodeTransformers'; import { rgbaColorToString, rgbColorToString } from 'common/util/colorCodeTransformers';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule'; import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
import { import {
BRUSH_BORDER_INNER_COLOR, BRUSH_BORDER_INNER_COLOR,
@ -31,8 +31,8 @@ import Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node'; import type { KonvaEventObject } from 'konva/lib/Node';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasToolModule { export class CanvasToolModule extends CanvasModuleBase {
readonly type = 'tool_preview'; readonly type = 'tool';
static readonly COLOR_PICKER_RADIUS = 25; static readonly COLOR_PICKER_RADIUS = 25;
static readonly COLOR_PICKER_THICKNESS = 15; static readonly COLOR_PICKER_THICKNESS = 15;
static readonly COLOR_PICKER_CROSSHAIR_SPACE = 5; static readonly COLOR_PICKER_CROSSHAIR_SPACE = 5;
@ -84,11 +84,15 @@ export class CanvasToolModule {
subscriptions: Set<() => void> = new Set(); subscriptions: Set<() => void> = new Set();
constructor(parent: CanvasPreviewModule) { constructor(parent: CanvasPreviewModule) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.parent = parent; this.parent = parent;
this.manager = this.parent.manager; this.manager = this.parent.manager;
this.path = this.manager.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating tool module');
this.konva = { this.konva = {
stage: this.manager.stage.konva.stage, stage: this.manager.stage.konva.stage,
group: new Konva.Group({ name: `${this.type}:group`, listening: false }), group: new Konva.Group({ name: `${this.type}:group`, listening: false }),
@ -240,13 +244,6 @@ export class CanvasToolModule {
this.subscriptions.add(cleanupListeners); this.subscriptions.add(cleanupListeners);
} }
destroy = () => {
for (const cleanup of this.subscriptions) {
cleanup();
}
this.konva.group.destroy();
};
setToolVisibility = (tool: Tool) => { setToolVisibility = (tool: Tool) => {
this.konva.brush.group.visible(tool === 'brush'); this.konva.brush.group.visible(tool === 'brush');
this.konva.eraser.group.visible(tool === 'eraser'); this.konva.eraser.group.visible(tool === 'eraser');
@ -882,7 +879,21 @@ export class CanvasToolModule {
} }
}; };
getLoggingContext = (): SerializableObject => { repr = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return {
id: this.id,
type: this.type,
path: this.path,
};
};
destroy = () => {
this.log.debug('Destroying tool module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy();
};
getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,7 +1,7 @@
import type { SerializableObject } from 'common/types';
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter'; import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter'; import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { canvasToImageData, getEmptyRect, getPrefixedId } from 'features/controlLayers/konva/util'; import { canvasToImageData, getEmptyRect, getPrefixedId } from 'features/controlLayers/konva/util';
import type { Coordinate, Rect } from 'features/controlLayers/store/types'; import type { Coordinate, Rect } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
@ -18,7 +18,7 @@ import type { Logger } from 'roarr';
* *
* It renders an outline when dragging and resizing the entity, with transform anchors for resizing and rotation. * It renders an outline when dragging and resizing the entity, with transform anchors for resizing and rotation.
*/ */
export class CanvasTransformer { export class CanvasTransformer extends CanvasModuleBase {
readonly type = 'entity_transformer'; readonly type = 'entity_transformer';
static RECT_CALC_DEBOUNCE_MS = 300; static RECT_CALC_DEBOUNCE_MS = 300;
@ -99,11 +99,13 @@ export class CanvasTransformer {
}; };
constructor(parent: CanvasLayerAdapter | CanvasMaskAdapter) { constructor(parent: CanvasLayerAdapter | CanvasMaskAdapter) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.parent = parent; this.parent = parent;
this.manager = parent.manager; this.manager = parent.manager;
this.path = this.parent.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating entity transformer module');
this.konva = { this.konva = {
outlineRect: new Konva.Rect({ outlineRect: new Konva.Rect({
@ -721,6 +723,7 @@ export class CanvasTransformer {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
mode: this.interactionMode, mode: this.interactionMode,
isTransformEnabled: this.isTransformEnabled, isTransformEnabled: this.isTransformEnabled,
isDragEnabled: this.isDragEnabled, isDragEnabled: this.isDragEnabled,
@ -731,17 +734,14 @@ export class CanvasTransformer {
* Destroys the transformer, cleaning up any subscriptions. * Destroys the transformer, cleaning up any subscriptions.
*/ */
destroy = () => { destroy = () => {
this.log.trace('Destroying transformer'); this.log.debug('Destroying entity transformer module');
for (const cleanup of this.subscriptions) { this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.log.trace('Cleaning up listener');
cleanup();
}
this.konva.outlineRect.destroy(); this.konva.outlineRect.destroy();
this.konva.transformer.destroy(); this.konva.transformer.destroy();
this.konva.proxyRect.destroy(); this.konva.proxyRect.destroy();
}; };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@ -1,24 +1,29 @@
import type { SerializableObject } from 'common/types';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { Extents, ExtentsResult, GetBboxTask, WorkerLogMessage } from 'features/controlLayers/konva/worker'; import type { Extents, ExtentsResult, GetBboxTask, WorkerLogMessage } from 'features/controlLayers/konva/worker';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasWorkerModule { export class CanvasWorkerModule extends CanvasModuleBase {
readonly type = 'worker';
id: string; id: string;
path: string[]; path: string[];
log: Logger; log: Logger;
manager: CanvasManager; manager: CanvasManager;
subscriptions = new Set<() => void>();
worker: Worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module', name: 'worker' }); worker: Worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module', name: 'worker' });
tasks: Map<string, { task: GetBboxTask; onComplete: (extents: Extents | null) => void }> = new Map(); tasks: Map<string, { task: GetBboxTask; onComplete: (extents: Extents | null) => void }> = new Map();
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId('worker'); this.id = getPrefixedId('worker');
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating canvas worker');
this.log.debug('Creating worker module');
this.worker.onmessage = (event: MessageEvent<ExtentsResult | WorkerLogMessage>) => { this.worker.onmessage = (event: MessageEvent<ExtentsResult | WorkerLogMessage>) => {
const { type, data } = event.data; const { type, data } = event.data;
@ -55,7 +60,23 @@ export class CanvasWorkerModule {
this.worker.postMessage(task, [data.buffer]); this.worker.postMessage(task, [data.buffer]);
} }
getLoggingContext = (): SerializableObject => { repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
tasks: Array.from(this.tasks.keys()),
};
};
destroy = () => {
this.log.trace('Destroying worker module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.worker.terminate();
this.tasks.clear();
};
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
}; };
} }