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 cleanup = manager.initialize();
return cleanup;
manager.initialize();
return manager.destroy;
}, [asPreview, container, socket, stage, store]);
useLayoutEffect(() => {

View File

@ -1,29 +1,35 @@
import { getArbitraryBaseColor } from '@invoke-ai/ui-library';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import Konva from 'konva';
import type { Logger } from 'roarr';
export class CanvasBackgroundModule {
readonly type = 'background_grid';
export class CanvasBackgroundModule extends CanvasModuleBase {
readonly type = 'background';
static GRID_LINE_COLOR_COARSE = getArbitraryBaseColor(27);
static GRID_LINE_COLOR_FINE = getArbitraryBaseColor(18);
id: string;
path: string[];
manager: CanvasManager;
subscriptions = new Set<() => void>();
log: Logger;
konva: {
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) {
super();
this.id = getPrefixedId(this.type);
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.subscriptions.add(
@ -116,9 +122,8 @@ export class CanvasBackgroundModule {
}
destroy = () => {
for (const cleanup of this.subscriptions) {
cleanup();
}
this.log.trace('Destroying background module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.layer.destroy();
};
@ -145,4 +150,16 @@ export class CanvasBackgroundModule {
}
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 { roundToMultiple, roundToMultipleMin } from 'common/util/roundDownToMultiple';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
import { getPrefixedId } from 'features/controlLayers/konva/util';
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 NO_ANCHORS: string[] = [];
export class CanvasBboxModule {
readonly type = 'generation_bbox';
export class CanvasBboxModule extends CanvasModuleBase {
readonly type = 'bbox';
id: string;
path: string[];
parent: CanvasPreviewModule;
manager: CanvasManager;
log: Logger;
/**
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
*/
subscriptions: Set<() => void> = new Set();
parent: CanvasPreviewModule;
konva: {
group: Konva.Group;
rect: Konva.Rect;
@ -43,13 +41,14 @@ export class CanvasBboxModule {
};
constructor(parent: CanvasPreviewModule) {
super();
this.id = getPrefixedId(this.type);
this.parent = parent;
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.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
// transforming the bbox.
@ -238,7 +237,7 @@ export class CanvasBboxModule {
}
render = () => {
this.log.trace('Rendering generation bbox');
this.log.trace('Rendering bbox module');
const bbox = this.manager.stateApi.getBbox();
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 = () => {
this.log.trace('Destroying generation bbox');
for (const unsubscribe of this.subscriptions) {
unsubscribe();
}
this.log.trace('Destroying bbox module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy();
};
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 { deepClone } from 'common/util/deepClone';
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 { CanvasBrushLineState } from 'features/controlLayers/store/types';
import Konva from 'konva';
import type { Logger } from 'roarr';
export class CanvasBrushLineRenderer {
export class CanvasBrushLineRenderer extends CanvasModuleBase {
readonly type = 'brush_line_renderer';
id: string;
@ -15,6 +15,7 @@ export class CanvasBrushLineRenderer {
parent: CanvasObjectRenderer;
manager: CanvasManager;
log: Logger;
subscriptions = new Set<() => void>();
state: CanvasBrushLineState;
konva: {
@ -23,6 +24,7 @@ export class CanvasBrushLineRenderer {
};
constructor(state: CanvasBrushLineState, parent: CanvasObjectRenderer) {
super();
const { id, clip } = state;
this.id = id;
this.parent = parent;
@ -30,7 +32,7 @@ export class CanvasBrushLineRenderer {
this.path = this.parent.path.concat(this.id);
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 = {
group: new Konva.Group({
@ -69,26 +71,28 @@ export class CanvasBrushLineRenderer {
return false;
}
destroy() {
this.log.trace('Destroying brush line');
destroy = () => {
this.log.debug('Destroying brush line renderer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy();
}
};
setVisibility(isVisible: boolean): void {
this.log.trace({ isVisible }, 'Setting brush line visibility');
this.konva.group.visible(isVisible);
}
repr() {
repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
parent: this.parent.id,
state: deepClone(this.state),
};
}
};
getLoggingContext = (): SerializableObject => {
getLoggingContext = () => {
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 { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { GenerationMode } from 'features/controlLayers/store/types';
import { LRUCache } from 'lru-cache';
import type { Logger } from 'roarr';
export class CanvasCacheModule {
export class CanvasCacheModule extends CanvasModuleBase {
readonly type = 'cache';
id: string;
path: string[];
log: Logger;
manager: CanvasManager;
subscriptions = new Set<() => void>();
imageNameCache = new LRUCache<string, string>({ max: 100 });
canvasElementCache = new LRUCache<string, HTMLCanvasElement>({ max: 32 });
generationModeCache = new LRUCache<string, GenerationMode>({ max: 100 });
constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId('cache');
this.manager = manager;
this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating canvas cache');
this.log.debug('Creating cache module');
}
clearAll = () => {
@ -29,7 +34,21 @@ export class CanvasCacheModule {
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('.') };
};
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
import { getLastPointOfLine } from 'features/controlLayers/konva/util';
@ -22,12 +23,13 @@ import type { Logger } from 'roarr';
import stableHash from 'stable-hash';
import { assert } from 'tsafe';
export class CanvasLayerAdapter {
export class CanvasLayerAdapter extends CanvasModuleBase {
readonly type = 'layer_adapter';
id: string;
path: string[];
manager: CanvasManager;
subscriptions = new Set<() => void>();
log: Logger;
state: CanvasRasterLayerState | CanvasControlLayerState;
@ -41,11 +43,14 @@ export class CanvasLayerAdapter {
isFirstRender: boolean = true;
constructor(state: CanvasLayerAdapter['state'], manager: CanvasLayerAdapter['manager']) {
super();
this.id = state.id;
this.manager = manager;
this.path = this.manager.path.concat(this.id);
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.konva = {
@ -71,10 +76,10 @@ export class CanvasLayerAdapter {
};
destroy = (): void => {
this.log.debug('Destroying layer');
// We need to call the destroy method on all children so they can do their own cleanup.
this.transformer.destroy();
this.log.debug('Destroying layer adapter module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.renderer.destroy();
this.transformer.destroy();
this.konva.layer.destroy();
};
@ -144,6 +149,7 @@ export class CanvasLayerAdapter {
return {
id: this.id,
type: this.type,
path: this.path,
state: deepClone(this.state),
transformer: this.transformer.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 { CanvasCompositorModule } from 'features/controlLayers/konva/CanvasCompositorModule';
import { CanvasFilterModule } from 'features/controlLayers/konva/CanvasFilterModule';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasRenderingModule } from 'features/controlLayers/konva/CanvasRenderingModule';
import { CanvasStageModule } from 'features/controlLayers/konva/CanvasStageModule';
import { CanvasWorkerModule } from 'features/controlLayers/konva/CanvasWorkerModule.js';
@ -21,17 +22,20 @@ import { CanvasPreviewModule } from './CanvasPreviewModule';
import { CanvasStateApiModule } from './CanvasStateApiModule';
export const $canvasManager = atom<CanvasManager | null>(null);
const TYPE = 'manager';
export class CanvasManager {
readonly type = TYPE;
export class CanvasManager extends CanvasModuleBase {
readonly type = 'manager';
id: string;
path: string[];
manager: CanvasManager;
log: Logger;
store: AppStore;
socket: AppSocket;
subscriptions = new Set<() => void>();
adapters = {
rasterLayers: new SyncableMap<string, CanvasLayerAdapter>(),
controlLayers: new SyncableMap<string, CanvasLayerAdapter>(),
@ -60,8 +64,21 @@ export class CanvasManager {
_isDebugging: boolean = false;
constructor(stage: Konva.Stage, container: HTMLDivElement, store: AppStore, socket: AppSocket) {
super();
this.id = getPrefixedId(this.type);
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.socket = socket;
@ -80,16 +97,6 @@ export class CanvasManager {
this.stage.addLayer(this.background.konva.layer);
}
log = logger('canvas').child((message) => {
return {
...message,
context: {
...this.getLoggingContext(),
...message.context,
},
};
});
enableDebugging() {
this._isDebugging = true;
this.logDebugInfo();
@ -100,7 +107,7 @@ export class CanvasManager {
}
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
this.stateApi.$transformingEntity.set(null);
@ -109,26 +116,52 @@ export class CanvasManager {
this.stateApi.$currentFill.set(this.stateApi.getCurrentFill());
this.stateApi.$selectedEntity.set(this.stateApi.getSelectedEntity());
const cleanupStage = this.stage.initialize();
const cleanupStore = this.store.subscribe(this.renderer.render);
this.subscriptions.add(this.store.subscribe(this.renderer.render));
this.stage.initialize();
};
return () => {
this.log.debug('Cleaning up canvas manager');
for (const adapter of this.adapters.getAll()) {
adapter.destroy();
}
this.background.destroy();
this.preview.destroy();
cleanupStore();
cleanupStage();
};
destroy = () => {
this.log.debug('Destroying canvas manager module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
for (const adapter of this.adapters.getAll()) {
adapter.destroy();
}
this.stateApi.destroy();
this.preview.destroy();
this.background.destroy();
this.filter.destroy();
this.worker.destroy();
this.renderer.destroy();
this.compositor.destroy();
this.stage.destroy();
$canvasManager.set(null);
};
setCanvasManager = () => {
this.log.debug('Setting canvas manager');
this.log.debug('Setting canvas manager global');
$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 => {
return {
path: this.path.join('.'),
@ -150,11 +183,6 @@ export class CanvasManager {
logDebugInfo() {
// eslint-disable-next-line no-console
console.log('Canvas manager', this);
for (const adapter of this.adapters.getAll()) {
// eslint-disable-next-line no-console
console.log(adapter.id, adapter);
}
this.log.debug({ manager: this.repr() }, 'Canvas manager');
}
getPrefixedId = getPrefixedId;
}

View File

@ -1,6 +1,7 @@
import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
import { getLastPointOfLine } from 'features/controlLayers/konva/util';
@ -21,13 +22,14 @@ import { get, omit } from 'lodash-es';
import type { Logger } from 'roarr';
import stableHash from 'stable-hash';
export class CanvasMaskAdapter {
export class CanvasMaskAdapter extends CanvasModuleBase {
readonly type = 'mask_adapter';
id: string;
path: string[];
manager: CanvasManager;
log: Logger;
subscriptions = new Set<() => void>();
state: CanvasInpaintMaskState | CanvasRegionalGuidanceState;
@ -41,11 +43,14 @@ export class CanvasMaskAdapter {
};
constructor(state: CanvasMaskAdapter['state'], manager: CanvasMaskAdapter['manager']) {
super();
this.id = state.id;
this.manager = manager;
this.path = this.manager.path.concat(this.id);
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.konva = {
@ -71,8 +76,8 @@ export class CanvasMaskAdapter {
};
destroy = (): void => {
this.log.debug('Destroying mask');
// We need to call the destroy method on all children so they can do their own cleanup.
this.log.debug('Destroying mask adapter module');
this.transformer.destroy();
this.renderer.destroy();
this.konva.layer.destroy();
@ -157,6 +162,7 @@ export class CanvasMaskAdapter {
return {
id: this.id,
type: this.type,
path: this.path,
state: deepClone(this.state),
};
};
@ -182,7 +188,8 @@ export class CanvasMaskAdapter {
const canvas = this.renderer.getCanvas(rect, attrs);
return canvas;
};
getLoggingContext = (): SerializableObject => {
getLoggingContext = () => {
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 { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
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 { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters';
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.
*/
export class CanvasObjectRenderer {
readonly type = 'object_renderer';
export class CanvasObjectRenderer extends CanvasModuleBase {
readonly type = 'entity_object_renderer';
id: string;
path: string[];
@ -123,12 +123,13 @@ export class CanvasObjectRenderer {
$canvasCache = atom<{ canvas: HTMLCanvasElement; rect: Rect } | null>(null);
constructor(parent: CanvasLayerAdapter | CanvasMaskAdapter) {
super();
this.id = getPrefixedId(this.type);
this.parent = parent;
this.path = this.parent.path.concat(this.id);
this.manager = parent.manager;
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace('Creating object renderer');
this.log.debug('Creating entity object renderer module');
this.konva = {
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.
*/
destroy = () => {
this.log.trace('Destroying object renderer');
for (const cleanup of this.subscriptions) {
this.log.trace('Cleaning up listener');
cleanup();
}
this.log.debug('Destroying entity object renderer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
for (const renderer of this.renderers.values()) {
renderer.destroy();
}
@ -616,13 +614,14 @@ export class CanvasObjectRenderer {
return {
id: this.id,
type: this.type,
path: this.path,
parent: this.parent.id,
renderers: Array.from(this.renderers.values()).map((renderer) => renderer.repr()),
buffer: this.bufferRenderer?.repr(),
};
};
getLoggingContext = (): SerializableObject => {
getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
};
}

View File

@ -1,13 +1,22 @@
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasProgressImageModule } from 'features/controlLayers/konva/CanvasProgressImageModule';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import Konva from 'konva';
import type { Logger } from 'roarr';
import { CanvasBboxModule } from './CanvasBboxModule';
import { CanvasStagingAreaModule } from './CanvasStagingAreaModule';
import { CanvasToolModule } from './CanvasToolModule';
export class CanvasPreviewModule {
export class CanvasPreviewModule extends CanvasModuleBase {
readonly type = 'preview';
id: string;
path: string[];
manager: CanvasManager;
log: Logger;
subscriptions = new Set<() => void>();
konva: {
layer: Konva.Layer;
@ -19,7 +28,14 @@ export class CanvasPreviewModule {
progressImage: CanvasProgressImageModule;
constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId(this.type);
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 = {
layer: new Konva.Layer({ listening: false, imageSmoothingEnabled: false }),
};
@ -41,11 +57,25 @@ export class CanvasPreviewModule {
return this.konva.layer;
};
destroy() {
// this.stagingArea.destroy(); // TODO(psyche): implement destroy
repr = () => {
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.bbox.destroy(); // TODO(psyche): implement destroy
this.bbox.destroy();
this.tool.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 type { SerializableObject } from 'common/types';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
import { getPrefixedId, loadImage } from 'features/controlLayers/konva/util';
import Konva from 'konva';
import type { Logger } from 'roarr';
import type { S } from 'services/api/types';
export class CanvasProgressImageModule {
export class CanvasProgressImageModule extends CanvasModuleBase {
readonly type = 'progress_image';
id: string;
@ -35,13 +35,14 @@ export class CanvasProgressImageModule {
mutex: Mutex = new Mutex();
constructor(parent: CanvasPreviewModule) {
super();
this.id = getPrefixedId(this.type);
this.parent = parent;
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.trace('Creating progress image');
this.log.debug('Creating progress image module');
this.konva = {
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 = () => {
this.log.trace('Destroying progress image');
for (const unsubscribe of this.subscriptions) {
unsubscribe();
}
this.log.debug('Destroying progress image module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy();
};
getLoggingContext = (): SerializableObject => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
getLoggingContext = () => {
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 { deepClone } from 'common/util/deepClone';
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 { CanvasRectState } from 'features/controlLayers/store/types';
import Konva from 'konva';
import type { Logger } from 'roarr';
export class CanvasRectRenderer {
export class CanvasRectRenderer extends CanvasModuleBase {
readonly type = 'rect_renderer';
id: string;
@ -15,6 +15,7 @@ export class CanvasRectRenderer {
parent: CanvasObjectRenderer;
manager: CanvasManager;
log: Logger;
subscriptions = new Set<() => void>();
state: CanvasRectState;
konva: {
@ -24,13 +25,15 @@ export class CanvasRectRenderer {
isFirstRender: boolean = false;
constructor(state: CanvasRectState, parent: CanvasObjectRenderer) {
super();
const { id } = state;
this.id = id;
this.parent = parent;
this.manager = parent.manager;
this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace({ state }, 'Creating rect');
this.log.debug({ state }, 'Creating rect renderer module');
this.konva = {
group: new Konva.Group({ name: `${this.type}:group`, listening: false }),
@ -60,27 +63,29 @@ export class CanvasRectRenderer {
return false;
}
destroy() {
this.log.trace('Destroying rect');
this.konva.group.destroy();
}
setVisibility(isVisible: boolean): void {
this.log.trace({ isVisible }, 'Setting rect visibility');
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 {
id: this.id,
type: this.type,
path: this.path,
parent: this.parent.id,
isFirstRender: this.isFirstRender,
state: deepClone(this.state),
};
}
};
getLoggingContext = (): SerializableObject => {
getLoggingContext = () => {
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 type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { CanvasV2State } from 'features/controlLayers/store/types';
import type { Logger } from 'roarr';
export class CanvasRenderingModule {
export class CanvasRenderingModule extends CanvasModuleBase {
readonly type = 'canvas_renderer';
id: string;
path: string[];
log: Logger;
manager: CanvasManager;
subscriptions = new Set<() => void>();
state: CanvasV2State | null = null;
constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId('canvas_renderer');
this.manager = manager;
this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating canvas renderer');
this.log.debug('Creating canvas renderer module');
}
render = async () => {
@ -263,4 +268,17 @@ export class CanvasRenderingModule {
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 { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CANVAS_SCALE_BY } from 'features/controlLayers/konva/constants';
import { getPrefixedId, getRectUnion } from 'features/controlLayers/konva/util';
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 type { Logger } from 'roarr';
export class CanvasStageModule {
export class CanvasStageModule extends CanvasModuleBase {
readonly type = 'stage';
static MIN_CANVAS_SCALE = 0.1;
static MAX_CANVAS_SCALE = 20;
@ -19,12 +21,17 @@ export class CanvasStageModule {
container: HTMLDivElement;
log: Logger;
subscriptions = new Set<() => void>();
constructor(stage: Konva.Stage, container: HTMLDivElement, manager: CanvasManager) {
super();
this.id = getPrefixedId('stage');
this.manager = manager;
this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating stage module');
this.container = container;
this.konva = { stage };
}
@ -50,12 +57,10 @@ export class CanvasStageModule {
this.fitLayersToStage();
const cleanupListeners = this.setEventListeners();
return () => {
this.log.debug('Destroying stage');
this.subscriptions.add(cleanupListeners);
this.subscriptions.add(() => {
resizeObserver.disconnect();
this.konva.stage.destroy();
cleanupListeners();
};
});
};
fitStageToContainer = () => {
@ -276,7 +281,21 @@ export class CanvasStageModule {
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('.') };
};
}

View File

@ -1,13 +1,14 @@
import type { SerializableObject } from 'common/types';
import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { imageDTOToImageWithDims, type StagingAreaImage } from 'features/controlLayers/store/types';
import Konva from 'konva';
import type { Logger } from 'roarr';
export class CanvasStagingAreaModule {
export class CanvasStagingAreaModule extends CanvasModuleBase {
readonly type = 'staging_area';
id: string;
@ -27,12 +28,14 @@ export class CanvasStagingAreaModule {
subscriptions: Set<() => void> = new Set();
constructor(parent: CanvasPreviewModule) {
super();
this.id = getPrefixedId(this.type);
this.parent = parent;
this.manager = this.parent.manager;
this.path = this.manager.path.concat(this.id);
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.image = null;
@ -85,12 +88,11 @@ export class CanvasStagingAreaModule {
};
destroy = () => {
this.log.debug('Destroying staging area module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
if (this.image) {
this.image.destroy();
}
for (const unsubscribe of this.subscriptions) {
unsubscribe();
}
for (const node of this.getNodes()) {
node.destroy();
}
@ -100,6 +102,7 @@ export class CanvasStagingAreaModule {
return {
id: this.id,
type: this.type,
path: this.path,
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 { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import {
bboxChanged,
brushWidthChanged,
@ -40,6 +42,7 @@ import type {
import { RGBA_BLACK } from 'features/controlLayers/store/types';
import type { WritableAtom } from 'nanostores';
import { atom } from 'nanostores';
import type { Logger } from 'roarr';
import { queueApi } from 'services/api/endpoints/queue';
import type { BatchConfig } from 'services/api/types';
import { $lastCanvasProgressEvent } from 'services/events/setEventListeners';
@ -70,13 +73,27 @@ type EntityStateAndAdapter =
adapter: CanvasMaskAdapter;
};
export class CanvasStateApiModule {
store: AppStore;
export class CanvasStateApiModule extends CanvasModuleBase {
readonly type = 'state_api';
id: string;
path: string[];
manager: CanvasManager;
log: Logger;
subscriptions = new Set<() => void>();
store: AppStore;
constructor(store: AppStore, manager: CanvasManager) {
this.store = store;
super();
this.id = getPrefixedId(this.type);
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
@ -258,4 +275,21 @@ export class CanvasStateApiModule {
height: 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 type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
import {
BRUSH_BORDER_INNER_COLOR,
@ -31,8 +31,8 @@ import Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node';
import type { Logger } from 'roarr';
export class CanvasToolModule {
readonly type = 'tool_preview';
export class CanvasToolModule extends CanvasModuleBase {
readonly type = 'tool';
static readonly COLOR_PICKER_RADIUS = 25;
static readonly COLOR_PICKER_THICKNESS = 15;
static readonly COLOR_PICKER_CROSSHAIR_SPACE = 5;
@ -84,11 +84,15 @@ export class CanvasToolModule {
subscriptions: Set<() => void> = new Set();
constructor(parent: CanvasPreviewModule) {
super();
this.id = getPrefixedId(this.type);
this.parent = parent;
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.debug('Creating tool module');
this.konva = {
stage: this.manager.stage.konva.stage,
group: new Konva.Group({ name: `${this.type}:group`, listening: false }),
@ -240,13 +244,6 @@ export class CanvasToolModule {
this.subscriptions.add(cleanupListeners);
}
destroy = () => {
for (const cleanup of this.subscriptions) {
cleanup();
}
this.konva.group.destroy();
};
setToolVisibility = (tool: Tool) => {
this.konva.brush.group.visible(tool === 'brush');
this.konva.eraser.group.visible(tool === 'eraser');
@ -882,7 +879,21 @@ export class CanvasToolModule {
}
};
getLoggingContext = (): SerializableObject => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
repr = () => {
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 { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
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 type { Coordinate, Rect } from 'features/controlLayers/store/types';
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.
*/
export class CanvasTransformer {
export class CanvasTransformer extends CanvasModuleBase {
readonly type = 'entity_transformer';
static RECT_CALC_DEBOUNCE_MS = 300;
@ -99,11 +99,13 @@ export class CanvasTransformer {
};
constructor(parent: CanvasLayerAdapter | CanvasMaskAdapter) {
super();
this.id = getPrefixedId(this.type);
this.parent = parent;
this.manager = parent.manager;
this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating entity transformer module');
this.konva = {
outlineRect: new Konva.Rect({
@ -721,6 +723,7 @@ export class CanvasTransformer {
return {
id: this.id,
type: this.type,
path: this.path,
mode: this.interactionMode,
isTransformEnabled: this.isTransformEnabled,
isDragEnabled: this.isDragEnabled,
@ -731,17 +734,14 @@ export class CanvasTransformer {
* Destroys the transformer, cleaning up any subscriptions.
*/
destroy = () => {
this.log.trace('Destroying transformer');
for (const cleanup of this.subscriptions) {
this.log.trace('Cleaning up listener');
cleanup();
}
this.log.debug('Destroying entity transformer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.outlineRect.destroy();
this.konva.transformer.destroy();
this.konva.proxyRect.destroy();
};
getLoggingContext = (): SerializableObject => {
getLoggingContext = () => {
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 { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { Extents, ExtentsResult, GetBboxTask, WorkerLogMessage } from 'features/controlLayers/konva/worker';
import type { Logger } from 'roarr';
export class CanvasWorkerModule {
export class CanvasWorkerModule extends CanvasModuleBase {
readonly type = 'worker';
id: string;
path: string[];
log: Logger;
manager: CanvasManager;
subscriptions = new Set<() => void>();
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();
constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId('worker');
this.manager = manager;
this.path = this.manager.path.concat(this.id);
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>) => {
const { type, data } = event.data;
@ -55,7 +60,23 @@ export class CanvasWorkerModule {
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('.') };
};
}