feat: add generation cancel job functionality and improve job handling
- Introduced `generationCancelJob` command ID in `commands/ids.ts`. - Added `GenerationCancelJobPayload` type in `commands/payloads.ts`. - Enhanced job status to include "cancelled" in `editor/state.ts`. - Updated `runGenerationJob` to accept an `AbortSignal` and handle cancellation. - Implemented cancellation logic in `runGenerate` and related functions. - Added tests for job cancellation in `operations/generation/workflow.test.ts`. - Improved layer rendering logic to prevent stack overflow in `editor/document-indexes.ts`. - Added raster size assertions in `platform/browser/rasterLimits.ts` for image processing limits. - Enhanced image file handling to check for size limits in `platform/browser/imageFiles.ts`. - Updated UI components to reflect job cancellation state in `view/GenerationJobStatus.tsx` and `view/bottom-controls/GenerateActionControls.tsx`.
This commit is contained in:
@@ -3,7 +3,7 @@ import type { ImageDocument } from "@core/document";
|
||||
import type { Rect } from "@core/geometry";
|
||||
import type { Layer } from "@core/layer";
|
||||
import { getLayerMask } from "@core/layer-mask-utils";
|
||||
import { createDocumentReadIndex, forEachLayerBackToFront, resolveIndexedLayerBounds, type DocumentReadIndex } from "@editor/document-indexes";
|
||||
import { createDocumentReadIndex, resolveIndexedLayerBounds, type DocumentReadIndex } from "@editor/document-indexes";
|
||||
import type { EditorState, GenerationCandidate, MaskViewMode, ViewportState } from "@editor/state";
|
||||
import { clearScreenRect } from "./clear-rect";
|
||||
import type { ImageTextureRenderer } from "./image-textures";
|
||||
@@ -24,7 +24,7 @@ export function renderLayers(context: WebGlRendererContext, document: ImageDocum
|
||||
if (!artboard.visible) continue;
|
||||
const clipRect = documentRectToScreenRect(context.canvas, artboard.bounds, editor.viewport);
|
||||
const maskLayerIds = documentIndex.maskLayerIdsByArtboardId.get(artboard.id) ?? emptyLayerIds;
|
||||
forEachLayerBackToFront(artboard.layers, (layer) => renderLayer(context, documentIndex, editor, layer, imageTextureRenderer, clipRect, maskLayerIds, 1));
|
||||
renderLayerTree(context, documentIndex, editor, artboard.layers, imageTextureRenderer, clipRect, maskLayerIds);
|
||||
if (generationCandidate?.placement.artboardId === artboard.id) renderGenerationCandidatePreview(context, editor, generationCandidate, imageTextureRenderer, clipRect);
|
||||
}
|
||||
}
|
||||
@@ -33,31 +33,46 @@ export function generationCandidatePreviewAssets(editor: EditorState): Asset[] {
|
||||
return editor.generation.candidates.map((candidate) => generationCandidateAsset(candidate));
|
||||
}
|
||||
|
||||
function renderLayer(
|
||||
function renderLayerTree(
|
||||
context: WebGlRendererContext,
|
||||
documentIndex: DocumentReadIndex,
|
||||
editor: EditorState,
|
||||
layer: Layer,
|
||||
layers: readonly Layer[],
|
||||
imageTextureRenderer: ImageTextureRenderer,
|
||||
clipRect: ScreenRect,
|
||||
maskLayerIds: ReadonlySet<string>,
|
||||
inheritedOpacity: number,
|
||||
) {
|
||||
const stack: Array<{ layer: Layer; clipRect: ScreenRect; inheritedOpacity: number }> = [];
|
||||
for (const layer of layers) stack.push({ layer, clipRect, inheritedOpacity: 1 });
|
||||
while (stack.length > 0) {
|
||||
const frame = stack.pop();
|
||||
if (!frame) continue;
|
||||
const { layer } = frame;
|
||||
if (!layer.visible || maskLayerIds.has(layer.id)) continue;
|
||||
const effectiveOpacity = frame.inheritedOpacity * layer.opacity;
|
||||
if (effectiveOpacity <= 0) continue;
|
||||
const effectiveClipRect = resolveLayerClipRect(context, documentIndex, editor.viewport, layer, frame.clipRect);
|
||||
if (!effectiveClipRect) continue;
|
||||
if (layer.type === "group") {
|
||||
for (const child of layer.children) stack.push({ layer: child, clipRect: effectiveClipRect, inheritedOpacity: effectiveOpacity });
|
||||
continue;
|
||||
}
|
||||
renderLeafLayer(context, documentIndex, editor, layer, imageTextureRenderer, effectiveClipRect, effectiveOpacity);
|
||||
}
|
||||
}
|
||||
|
||||
function renderLeafLayer(
|
||||
context: WebGlRendererContext,
|
||||
documentIndex: DocumentReadIndex,
|
||||
editor: EditorState,
|
||||
layer: Exclude<Layer, { type: "group" }>,
|
||||
imageTextureRenderer: ImageTextureRenderer,
|
||||
effectiveClipRect: ScreenRect,
|
||||
effectiveOpacity: number,
|
||||
) {
|
||||
const editingMaskLayer = editor.maskEdit?.maskLayerId === layer.id;
|
||||
const maskViewMode = editor.maskEdit?.viewMode ?? "composite";
|
||||
const isolatedMaskView = isIsolatedMaskView(maskViewMode);
|
||||
if (!layer.visible || maskLayerIds.has(layer.id)) return;
|
||||
const effectiveOpacity = inheritedOpacity * layer.opacity;
|
||||
if (effectiveOpacity <= 0) return;
|
||||
|
||||
const effectiveClipRect = resolveLayerClipRect(context, documentIndex, editor.viewport, layer, clipRect);
|
||||
if (!effectiveClipRect) return;
|
||||
|
||||
if (layer.type === "group") {
|
||||
forEachLayerBackToFront(layer.children, (child) => renderLayer(context, documentIndex, editor, child, imageTextureRenderer, effectiveClipRect, maskLayerIds, effectiveOpacity));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isolatedMaskView && layer.id !== editor.maskEdit?.targetLayerId) return;
|
||||
|
||||
const bounds = resolveIndexedLayerBounds(documentIndex, layer);
|
||||
|
||||
Reference in New Issue
Block a user