mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 12:52:10 -03:00
Debounce canvas output updates and optimize image handling
Added debouncing to the updateOutput function to prevent excessive updates during rapid changes. Large images are now handled using blob URLs for better performance, while small images use data URIs. Also added logic to skip output updates when preview is disabled and improved cleanup of temporary file trackers when nodes are removed.
This commit is contained in:
@@ -534,45 +534,80 @@ async function createCanvasWidget(node, widget, app) {
|
||||
};
|
||||
updateButtonStates();
|
||||
canvas.updateHistoryButtons();
|
||||
// Debounce timer for updateOutput to prevent excessive updates
|
||||
let updateOutputTimer = null;
|
||||
const updateOutput = async (node, canvas) => {
|
||||
// Check if preview is disabled - if so, skip updateOutput entirely
|
||||
const showPreviewWidget = node.widgets.find((w) => w.name === "show_preview");
|
||||
if (showPreviewWidget && !showPreviewWidget.value) {
|
||||
log.debug("Preview disabled, skipping updateOutput");
|
||||
return;
|
||||
}
|
||||
const triggerWidget = node.widgets.find((w) => w.name === "trigger");
|
||||
if (triggerWidget) {
|
||||
triggerWidget.value = (triggerWidget.value + 1) % 99999999;
|
||||
}
|
||||
try {
|
||||
const blob = await canvas.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
|
||||
if (blob) {
|
||||
// Auto-register in clipspace for Impact Pack compatibility and get server URL
|
||||
const serverImg = await registerImageInClipspace(node, blob);
|
||||
if (serverImg) {
|
||||
// Use server URL image as the main image for Impact Pack compatibility
|
||||
node.imgs = [serverImg];
|
||||
node.clipspaceImg = serverImg;
|
||||
log.debug(`Using server URL for node.imgs: ${serverImg.src}`);
|
||||
// Clear previous timer
|
||||
if (updateOutputTimer) {
|
||||
clearTimeout(updateOutputTimer);
|
||||
}
|
||||
// Debounce the update to prevent excessive processing during rapid changes
|
||||
updateOutputTimer = setTimeout(async () => {
|
||||
try {
|
||||
const blob = await canvas.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
|
||||
if (blob) {
|
||||
// For large images, use blob URL for better performance
|
||||
if (blob.size > 2 * 1024 * 1024) { // 2MB threshold
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
node.imgs = [img];
|
||||
log.debug(`Using blob URL for large image (${(blob.size / 1024 / 1024).toFixed(1)}MB): ${blobUrl.substring(0, 50)}...`);
|
||||
// Clean up old blob URLs to prevent memory leaks
|
||||
if (node.imgs.length > 1) {
|
||||
const oldImg = node.imgs[0];
|
||||
if (oldImg.src.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(oldImg.src);
|
||||
}
|
||||
}
|
||||
};
|
||||
img.src = blobUrl;
|
||||
}
|
||||
else {
|
||||
// For smaller images, use data URI as before
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const dataUrl = reader.result;
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
node.imgs = [img];
|
||||
log.debug(`Using data URI for small image (${(blob.size / 1024).toFixed(1)}KB): ${dataUrl.substring(0, 50)}...`);
|
||||
};
|
||||
img.src = dataUrl;
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Fallback to blob URL if server upload failed
|
||||
const new_preview = new Image();
|
||||
new_preview.src = URL.createObjectURL(blob);
|
||||
await new Promise(r => new_preview.onload = r);
|
||||
node.imgs = [new_preview];
|
||||
log.debug(`Fallback to blob URL for node.imgs: ${new_preview.src}`);
|
||||
node.imgs = [];
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.imgs = [];
|
||||
catch (error) {
|
||||
console.error("Error updating node preview:", error);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error updating node preview:", error);
|
||||
}
|
||||
}, 150); // 150ms debounce delay
|
||||
};
|
||||
// Store previous temp filenames for cleanup (make it globally accessible)
|
||||
if (!window.layerForgeTempFileTracker) {
|
||||
window.layerForgeTempFileTracker = new Map();
|
||||
}
|
||||
const tempFileTracker = window.layerForgeTempFileTracker;
|
||||
// Function to register image in clipspace for Impact Pack compatibility
|
||||
const registerImageInClipspace = async (node, blob) => {
|
||||
try {
|
||||
// Upload the image to ComfyUI's temp storage for clipspace access
|
||||
const formData = new FormData();
|
||||
const filename = `layerforge-auto-${node.id}-${Date.now()}.png`;
|
||||
const filename = `layerforge-sam-${node.id}-${Date.now()}.png`; // Use timestamp for SAM Detector
|
||||
formData.append("image", blob, filename);
|
||||
formData.append("overwrite", "true");
|
||||
formData.append("type", "temp");
|
||||
@@ -1249,6 +1284,13 @@ app.registerExtension({
|
||||
const onRemoved = nodeType.prototype.onRemoved;
|
||||
nodeType.prototype.onRemoved = function () {
|
||||
log.info(`Cleaning up canvas node ${this.id}`);
|
||||
// Clean up temp file tracker for this node (just remove from tracker)
|
||||
const nodeKey = `node-${this.id}`;
|
||||
const tempFileTracker = window.layerForgeTempFileTracker;
|
||||
if (tempFileTracker && tempFileTracker.has(nodeKey)) {
|
||||
tempFileTracker.delete(nodeKey);
|
||||
log.debug(`Removed temp file tracker for node ${this.id}`);
|
||||
}
|
||||
canvasNodeInstances.delete(this.id);
|
||||
log.info(`Deregistered CanvasNode instance for ID: ${this.id}`);
|
||||
if (window.canvasExecutionStates) {
|
||||
|
||||
@@ -568,45 +568,83 @@ async function createCanvasWidget(node: ComfyNode, widget: any, app: ComfyApp):
|
||||
updateButtonStates();
|
||||
canvas.updateHistoryButtons();
|
||||
|
||||
// Debounce timer for updateOutput to prevent excessive updates
|
||||
let updateOutputTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
const updateOutput = async (node: ComfyNode, canvas: Canvas) => {
|
||||
// Check if preview is disabled - if so, skip updateOutput entirely
|
||||
const showPreviewWidget = node.widgets.find((w) => w.name === "show_preview");
|
||||
if (showPreviewWidget && !showPreviewWidget.value) {
|
||||
log.debug("Preview disabled, skipping updateOutput");
|
||||
return;
|
||||
}
|
||||
|
||||
const triggerWidget = node.widgets.find((w) => w.name === "trigger");
|
||||
if (triggerWidget) {
|
||||
triggerWidget.value = (triggerWidget.value + 1) % 99999999;
|
||||
}
|
||||
|
||||
try {
|
||||
const blob = await canvas.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
|
||||
if (blob) {
|
||||
// Auto-register in clipspace for Impact Pack compatibility and get server URL
|
||||
const serverImg = await registerImageInClipspace(node, blob);
|
||||
|
||||
if (serverImg) {
|
||||
// Use server URL image as the main image for Impact Pack compatibility
|
||||
node.imgs = [serverImg];
|
||||
(node as any).clipspaceImg = serverImg;
|
||||
log.debug(`Using server URL for node.imgs: ${serverImg.src}`);
|
||||
} else {
|
||||
// Fallback to blob URL if server upload failed
|
||||
const new_preview = new Image();
|
||||
new_preview.src = URL.createObjectURL(blob);
|
||||
await new Promise(r => new_preview.onload = r);
|
||||
node.imgs = [new_preview];
|
||||
log.debug(`Fallback to blob URL for node.imgs: ${new_preview.src}`);
|
||||
}
|
||||
} else {
|
||||
node.imgs = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating node preview:", error);
|
||||
// Clear previous timer
|
||||
if (updateOutputTimer) {
|
||||
clearTimeout(updateOutputTimer);
|
||||
}
|
||||
|
||||
// Debounce the update to prevent excessive processing during rapid changes
|
||||
updateOutputTimer = setTimeout(async () => {
|
||||
try {
|
||||
const blob = await canvas.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
|
||||
if (blob) {
|
||||
// For large images, use blob URL for better performance
|
||||
if (blob.size > 2 * 1024 * 1024) { // 2MB threshold
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
node.imgs = [img];
|
||||
log.debug(`Using blob URL for large image (${(blob.size / 1024 / 1024).toFixed(1)}MB): ${blobUrl.substring(0, 50)}...`);
|
||||
// Clean up old blob URLs to prevent memory leaks
|
||||
if (node.imgs.length > 1) {
|
||||
const oldImg = node.imgs[0];
|
||||
if (oldImg.src.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(oldImg.src);
|
||||
}
|
||||
}
|
||||
};
|
||||
img.src = blobUrl;
|
||||
} else {
|
||||
// For smaller images, use data URI as before
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const dataUrl = reader.result as string;
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
node.imgs = [img];
|
||||
log.debug(`Using data URI for small image (${(blob.size / 1024).toFixed(1)}KB): ${dataUrl.substring(0, 50)}...`);
|
||||
};
|
||||
img.src = dataUrl;
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
} else {
|
||||
node.imgs = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating node preview:", error);
|
||||
}
|
||||
}, 250); // 150ms debounce delay
|
||||
};
|
||||
|
||||
// Store previous temp filenames for cleanup (make it globally accessible)
|
||||
if (!(window as any).layerForgeTempFileTracker) {
|
||||
(window as any).layerForgeTempFileTracker = new Map<string, string>();
|
||||
}
|
||||
const tempFileTracker = (window as any).layerForgeTempFileTracker;
|
||||
|
||||
// Function to register image in clipspace for Impact Pack compatibility
|
||||
const registerImageInClipspace = async (node: ComfyNode, blob: Blob): Promise<HTMLImageElement | null> => {
|
||||
try {
|
||||
// Upload the image to ComfyUI's temp storage for clipspace access
|
||||
const formData = new FormData();
|
||||
const filename = `layerforge-auto-${node.id}-${Date.now()}.png`;
|
||||
const filename = `layerforge-sam-${node.id}-${Date.now()}.png`; // Use timestamp for SAM Detector
|
||||
formData.append("image", blob, filename);
|
||||
formData.append("overwrite", "true");
|
||||
formData.append("type", "temp");
|
||||
@@ -1387,6 +1425,14 @@ app.registerExtension({
|
||||
nodeType.prototype.onRemoved = function (this: ComfyNode) {
|
||||
log.info(`Cleaning up canvas node ${this.id}`);
|
||||
|
||||
// Clean up temp file tracker for this node (just remove from tracker)
|
||||
const nodeKey = `node-${this.id}`;
|
||||
const tempFileTracker = (window as any).layerForgeTempFileTracker;
|
||||
if (tempFileTracker && tempFileTracker.has(nodeKey)) {
|
||||
tempFileTracker.delete(nodeKey);
|
||||
log.debug(`Removed temp file tracker for node ${this.id}`);
|
||||
}
|
||||
|
||||
canvasNodeInstances.delete(this.id);
|
||||
log.info(`Deregistered CanvasNode instance for ID: ${this.id}`);
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@ import { LogLevel } from "./logger";
|
||||
|
||||
// Log level for development.
|
||||
// Possible values: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE'
|
||||
export const LOG_LEVEL: keyof typeof LogLevel = 'DEBUG';
|
||||
export const LOG_LEVEL: keyof typeof LogLevel = 'NONE';
|
||||
|
||||
Reference in New Issue
Block a user