Compare commits

...

10 Commits

Author SHA1 Message Date
bymyself
0821f4b443 test: increase HUD-on maxDiffPixels to account for FPS variance
The HUD displays dynamic FPS values that change between runs,
causing ~300 pixel variance in the 180×160 clip region (~1%).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 17:16:37 -07:00
bymyself
1af4b8efc6 test: update renderInfo test for new lineCount after T:/I: removal
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 16:56:43 -07:00
bymyself
6ba54935ce test: update HUD-on snapshot to match CI rendering
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 16:43:38 -07:00
bymyself
b0947ee834 docs: update LGraph class JSDoc after executor removal
Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/12233#pullrequestreview-4286031194

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 16:37:59 -07:00
bymyself
74c09f31ef test: update HUD-on snapshot from CI actual
The HUD now shows N/V/FPS without the removed T:/I: lines.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 16:18:20 -07:00
bymyself
173293b919 fix(litegraph): update renderInfo lineCount after T:/I: removal
Reverts incorrect local snapshots; fixes lineCount from 5 to 3 to
match the actual number of lines displayed (N, V, FPS).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 16:01:44 -07:00
bymyself
fc6a0c8491 test: update HUD snapshots after removing T:/I: lines
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 15:42:45 -07:00
bymyself
699824f1e4 fix: remove lingering LGraph.start() call
Missed in initial deletion - also clean README example.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 15:26:44 -07:00
Alexander Brown
1f8e2c71d3 Merge branch 'main' into litegraph/prune-executor-cluster 2026-05-13 18:40:35 -07:00
Connor Byrne
2dadcde05d refactor(litegraph): delete dead LGraph executor cluster
Delete LGraph.start(), .stop(), .runStep(), .sendEventToAllNodes(), the
runtime state fields they own (iteration, globaltime, runningtime,
fixedtime, fixedtime_lapse, elapsed_time, last_update_time, starttime,
catch_errors, execution_timer_id, errors_in_execution, execution_time,
status), and the LGraph.STATUS_RUNNING/STATUS_STOPPED constants.

The host methods were already `@deprecated 'Will be removed in 0.9'`.
AUDIT-LG.9 confirmed zero internal and zero external callers across
src/, browser_tests/, packages/. Stacks on top of #12228 which deleted
the 6 stepping hooks fired from these methods.

Transitive cleanup folded in:
- LGraph.getTime/getFixedTime/getElapsedTime accessors (read deleted fields, zero callers)
- LGraphNode.doExecute now drops 'this.exec_version = this.graph.iteration'
- LGraphCanvas.renderInfo drops the T:/I: debug overlay lines
- useCoreCommands.test.ts subgraph mock drops start/stop/runStep stubs
- Unused LGraphEventMode import in LGraph.ts

Preserves nodes_executing/nodes_actioning/nodes_executedAction - those
pair with the trigger cluster being removed separately.
2026-05-13 16:48:27 -07:00
9 changed files with 10 additions and 242 deletions

View File

@@ -59,9 +59,10 @@ test.describe('Canvas settings', { tag: '@canvas' }, () => {
await test.step('Capture HUD region with setting on', async () => {
await comfyPage.settings.setSetting('Comfy.Graph.CanvasInfo', true)
await comfyPage.canvasOps.moveMouseToEmptyArea()
// FPS value varies per run; allow ~1% pixel variance in the 180×160 clip
await expect(comfyPage.page).toHaveScreenshot(
'canvas-info-hud-on.png',
{ clip: hudClip, maxDiffPixels: 50 }
{ clip: hudClip, maxDiffPixels: 350 }
)
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -212,9 +212,6 @@ describe('useCoreCommands', () => {
clear: vi.fn(),
serialize: vi.fn(),
configure: vi.fn(),
start: vi.fn(),
stop: vi.fn(),
runStep: vi.fn(),
findNodeByTitle: vi.fn(),
findNodesByTitle: vi.fn(),
findNodesByType: vi.fn(),

View File

@@ -104,8 +104,6 @@ const secondNode = LiteGraph.createNode('basic/sum')
graph.add(secondNode)
firstNode.connect(0, secondNode, 1)
graph.start()
```
## Projects using it

View File

@@ -73,7 +73,7 @@ import {
multiClone,
splitPositionables
} from './subgraph/subgraphUtils'
import { Alignment, LGraphEventMode } from './types/globalEnums'
import { Alignment } from './types/globalEnums'
import type {
LGraphTriggerAction,
LGraphTriggerEvent,
@@ -173,8 +173,10 @@ export interface BaseLGraph {
}
/**
* LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.
* supported callbacks:
* LGraph contains a full graph. Instantiate it, add nodes/groups, and use it
* for editing, traversal, and serialisation.
*
* Supported callbacks:
* + onNodeAdded: when a new node is added to the graph
* + onNodeRemoved: when a node inside this graph is removed
*/
@@ -183,9 +185,6 @@ export class LGraph
{
static serialisedSchemaVersion = 1 as const
static STATUS_STOPPED = 1
static STATUS_RUNNING = 2
/** List of LGraph properties that are manually handled by {@link LGraph.configure}. */
static readonly ConfigureProperties = new Set([
'nodes',
@@ -224,7 +223,6 @@ export class LGraph
*/
links: Map<LinkId, LLink> & Record<LinkId, LLink>
list_of_graphcanvas: LGraphCanvas[] | null
status: number = LGraph.STATUS_STOPPED
private _state: LGraphState = {
lastGroupId: 0,
@@ -249,20 +247,6 @@ export class LGraph
_nodes_in_order: LGraphNode[] = []
_nodes_executable: LGraphNode[] | null = null
_groups: LGraphGroup[] = []
iteration: number = 0
globaltime: number = 0
/** @deprecated Unused */
runningtime: number = 0
fixedtime: number = 0
fixedtime_lapse: number = 0.01
elapsed_time: number = 0.01
last_update_time: number = 0
starttime: number = 0
catch_errors: boolean = true
execution_timer_id?: number | null
errors_in_execution?: boolean
/** @deprecated Unused */
execution_time!: number
_last_trigger_time?: number
filter?: string
/** Must contain serialisable values, e.g. primitive types */
@@ -368,9 +352,6 @@ export class LGraph
* Removes all nodes from this graph
*/
clear(): void {
this.stop()
this.status = LGraph.STATUS_STOPPED
const graphId = this.id
if (this.isRootGraph && graphId !== zeroUuid) {
usePromotionStore().clearGraph(graphId)
@@ -416,26 +397,12 @@ export class LGraph
// other scene stuff
this._groups = []
// iterations
this.iteration = 0
// custom data
this.config = {}
this.vars = {}
// to store custom data
this.extra = {}
// timing
this.globaltime = 0
this.runningtime = 0
this.fixedtime = 0
this.fixedtime_lapse = 0.01
this.elapsed_time = 0.01
this.last_update_time = 0
this.starttime = 0
this.catch_errors = true
this.nodes_executing = []
this.nodes_actioning = []
this.nodes_executedAction = []
@@ -492,131 +459,6 @@ export class LGraph
}
}
/**
* @deprecated Will be removed in 0.9
* Starts running this graph every interval milliseconds.
* @param interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate
*/
start(interval?: number): void {
if (this.status == LGraph.STATUS_RUNNING) return
this.status = LGraph.STATUS_RUNNING
this.sendEventToAllNodes('onStart')
// launch
this.starttime = LiteGraph.getTime()
this.last_update_time = this.starttime
interval ||= 0
// execute once per frame
if (
interval == 0 &&
typeof window != 'undefined' &&
window.requestAnimationFrame
) {
const on_frame = () => {
if (this.execution_timer_id != -1) return
window.requestAnimationFrame(on_frame)
this.runStep(1, !this.catch_errors)
}
this.execution_timer_id = -1
on_frame()
} else {
// execute every 'interval' ms
// @ts-expect-error - Timer ID type mismatch needs fixing
this.execution_timer_id = setInterval(() => {
// execute
this.runStep(1, !this.catch_errors)
}, interval)
}
}
/**
* @deprecated Will be removed in 0.9
* Stops the execution loop of the graph
*/
stop(): void {
if (this.status == LGraph.STATUS_STOPPED) return
this.status = LGraph.STATUS_STOPPED
if (this.execution_timer_id != null) {
if (this.execution_timer_id != -1) {
clearInterval(this.execution_timer_id)
}
this.execution_timer_id = null
}
this.sendEventToAllNodes('onStop')
}
/**
* Run N steps (cycles) of the graph
* @param num number of steps to run, default is 1
* @param do_not_catch_errors [optional] if you want to try/catch errors
* @param limit max number of nodes to execute (used to execute from start to a node)
*/
runStep(num: number, do_not_catch_errors: boolean, limit?: number): void {
num = num || 1
const start = LiteGraph.getTime()
this.globaltime = 0.001 * (start - this.starttime)
const nodes = this._nodes_executable || this._nodes
if (!nodes) return
limit = limit || nodes.length
if (do_not_catch_errors) {
// iterations
for (let i = 0; i < num; i++) {
for (let j = 0; j < limit; ++j) {
const node = nodes[j]
// FIXME: Looks like copy/paste broken logic - checks for "on", executes "do"
if (node.mode == LGraphEventMode.ALWAYS && node.onExecute) {
// wrap node.onExecute();
node.doExecute?.()
}
}
this.fixedtime += this.fixedtime_lapse
}
} else {
try {
// iterations
for (let i = 0; i < num; i++) {
for (let j = 0; j < limit; ++j) {
const node = nodes[j]
if (node.mode == LGraphEventMode.ALWAYS) {
node.onExecute?.()
}
}
this.fixedtime += this.fixedtime_lapse
}
this.errors_in_execution = false
} catch (error) {
this.errors_in_execution = true
if (LiteGraph.throw_errors) throw error
if (LiteGraph.debug) console.error('Error during execution:', error)
this.stop()
}
}
const now = LiteGraph.getTime()
let elapsed = now - start
if (elapsed == 0) elapsed = 1
this.execution_time = 0.001 * elapsed
this.globaltime += 0.001 * elapsed
this.iteration += 1
this.elapsed_time = (now - this.last_update_time) * 0.001
this.last_update_time = now
this.nodes_executing = []
this.nodes_actioning = []
this.nodes_executedAction = []
}
/**
* Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than
* nodes with only inputs.
@@ -803,33 +645,6 @@ export class LGraph
this.setDirtyCanvas(true, true)
}
/**
* Returns the amount of time the graph has been running in milliseconds
* @returns number of milliseconds the graph has been running
*/
getTime(): number {
return this.globaltime
}
/**
* Returns the amount of time accumulated using the fixedtime_lapse var.
* This is used in context where the time increments should be constant
* @returns number of milliseconds the graph has been running
*/
getFixedTime(): number {
return this.fixedtime
}
/**
* Returns the amount of time it took to compute the latest iteration.
* Take into account that this number could be not correct
* if the nodes are using graphical actions
* @returns number of milliseconds it took the last cycle
*/
getElapsedTime(): number {
return this.elapsed_time
}
/**
* Increments the internal version counter.
* Currently only read for debug display in {@link LGraphCanvas.renderInfo}.
@@ -839,39 +654,6 @@ export class LGraph
this._version++
}
/**
* @deprecated Will be removed in 0.9
* Sends an event to all the nodes, useful to trigger stuff
* @param eventname the name of the event (function to be called)
* @param params parameters in array format
*/
sendEventToAllNodes(
eventname: string,
params?: object | object[],
mode?: LGraphEventMode
): void {
mode = mode || LGraphEventMode.ALWAYS
const nodes = this._nodes_in_order || this._nodes
if (!nodes) return
for (const node of nodes) {
// @ts-expect-error deprecated
if (!node[eventname] || node.mode != mode) continue
if (params === undefined) {
// @ts-expect-error deprecated
node[eventname]()
} else if (params && params.constructor === Array) {
// @ts-expect-error deprecated
// eslint-disable-next-line prefer-spread
node[eventname].apply(node, params)
} else {
// @ts-expect-error deprecated
node[eventname](params)
}
}
}
/**
* Runs an action on every canvas registered to this graph.
* @param action Action to run for every canvas

View File

@@ -48,9 +48,9 @@ describe('LGraphCanvas.renderInfo', () => {
try {
lgCanvas.renderInfo(ctx, 10, 0)
// lineCount = 5 (graph present, no info_text), lineHeight = 13
// lineCount = 3 (graph present, no info_text), lineHeight = 13
// y = canvas.height / DPR - (lineCount + 1) * lineHeight
expect(ctx.translate).toHaveBeenCalledWith(10, 2160 / 2 - 6 * 13)
expect(ctx.translate).toHaveBeenCalledWith(10, 2160 / 2 - 4 * 13)
} finally {
Object.defineProperty(window, 'devicePixelRatio', {
value: originalDPR,

View File

@@ -5404,7 +5404,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
*/
renderInfo(ctx: CanvasRenderingContext2D, x: number, y: number): void {
const lineHeight = 13
const lineCount = (this.graph ? 5 : 1) + (this.info_text ? 1 : 0)
const lineCount = (this.graph ? 3 : 1) + (this.info_text ? 1 : 0)
x = x || 10
y =
y ||
@@ -5421,12 +5421,6 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
ctx.textAlign = 'left'
let line = 1
if (this.graph) {
ctx.fillText(
`T: ${this.graph.globaltime.toFixed(2)}s`,
5,
lineHeight * line++
)
ctx.fillText(`I: ${this.graph.iteration}`, 5, lineHeight * line++)
ctx.fillText(
`N: ${this.graph._nodes.length} [${this.visible_nodes.length}]`,
5,

View File

@@ -1422,8 +1422,6 @@ export class LGraphNode
// @ts-expect-error deprecated
this.graph.nodes_executing[this.id] = false
// save execution/action ref
this.exec_version = this.graph.iteration
if (options?.action_call) {
this.action_call = options.action_call
// @ts-expect-error deprecated

View File

@@ -947,8 +947,6 @@ export class ComfyApp {
}
)
this.rootGraph.start()
// Ensure the canvas fills the window
useResizeObserver(this.canvasElRef, ([canvasEl]) => {
if (canvasEl.target instanceof HTMLCanvasElement) {