Files

152 lines
4.6 KiB
JavaScript

import { app } from "/scripts/app.js";
import { api } from "/scripts/api.js";
import {
setVueBadge,
clearVueBadges,
startBadgeObserver
} from "../modules/node_badges.js";
const SETTING_ID = "Mudknight Utils.Execution Time.enabled";
const SETTING_COLOR = "Mudknight Utils.Execution Time.color";
const BADGE_CLASS = "mk-exec-time";
const BADGE_EXTRA = ["ml-auto"];
// Semantic theme colors available as badge background presets.
// Values are CSS custom property names defined by the ComfyUI theme.
const COLOR_PRESETS = [
["None", null],
["Grey", "--muted-background"],
["Blue", "--primary-background"],
["Yellow", "--warning-background"],
["Green", "--success-background"],
["Red", "--color-error"],
];
app.registerExtension({
name: "Mudknight Utils.Execution Time",
setup() {
startBadgeObserver();
app.ui.settings.addSetting({
id: SETTING_ID,
name: "Show node execution time",
type: "boolean",
defaultValue: false,
});
app.ui.settings.addSetting({
id: SETTING_COLOR,
name: "Execution time badge color",
type: "combo",
defaultValue: "None",
options: COLOR_PRESETS.map(([label]) => label),
});
const timeMap = new Map();
// Map of nodeId -> elapsed seconds, kept for display.
const durationMap = new Map();
let lastId = null;
api.addEventListener("execution_start", () => {
// Clear all durations and badges on a new run.
durationMap.clear();
timeMap.clear();
lastId = null;
if (!app.graph) return;
for (const node of app.graph._nodes) {
node.executionDuration = undefined;
clearVueBadges(String(node.id));
}
});
api.addEventListener("executing", (e) => {
// The executing event detail is the node ID directly.
const id = e.detail ?? null;
// Record elapsed time for the previously executing node.
if (lastId !== null && timeMap.has(lastId)) {
const delta =
(performance.now() - timeMap.get(lastId)) / 1000;
durationMap.set(lastId, delta);
if (app.ui.settings.getSettingValue(SETTING_ID)) {
const text = `${delta.toFixed(3)}s`;
// LiteGraph node.
const node = app.graph?.getNodeById(lastId);
if (node) node.executionDuration = delta;
// Vue node DOM badge.
const label = app.ui.settings
.getSettingValue(SETTING_COLOR) ?? "None";
const color = COLOR_PRESETS.find(
([l]) => l === label)?.[1] ?? null;
setVueBadge(
BADGE_CLASS, BADGE_EXTRA, String(lastId),
text, color, "clock"
);
}
timeMap.delete(lastId);
}
lastId = id;
if (id !== null) timeMap.set(id, performance.now());
});
},
beforeRegisterNodeDef(nodeType) {
const orig = nodeType.prototype.onDrawForeground;
// LiteGraph canvas rendering path.
nodeType.prototype.onDrawForeground = function(ctx) {
if (
this.executionDuration
&& app.ui.settings.getSettingValue(SETTING_ID)
) {
drawTime(ctx, this.executionDuration);
}
if (orig) return orig.apply(this, arguments);
};
},
});
function drawTime(ctx, seconds) {
const text = `${seconds.toFixed(3)}s`;
ctx.save();
ctx.font = "12px sans-serif";
const padding = 4;
const w = ctx.measureText(text).width + padding * 2;
const h = LiteGraph.NODE_TITLE_HEIGHT - 10;
const x = 0;
const y = -LiteGraph.NODE_TITLE_HEIGHT - 22;
const r = 4;
ctx.fillStyle = LiteGraph.NODE_DEFAULT_BGCOLOR;
roundedRect(ctx, x, y, w, h, r);
ctx.fill();
ctx.fillStyle = LiteGraph.NODE_TITLE_COLOR;
ctx.fillText(text, x + padding, y + h - 6);
ctx.restore();
}
function roundedRect(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath();
}