Compare commits

..

1 Commits

Author SHA1 Message Date
Connor Byrne
fd5a94e072 refactor(litegraph): deprecate LiteGraph.ON_EVENT (release N)
ON_EVENT is a no-op mode — use NEVER to mute a node. The numeric slot
(1) is preserved for v2 ABI; the symbol will be removed in release N+1.

- Rename LGraphEventMode.ON_EVENT → _UNUSED_1 (value=1) in globalEnums.ts.
- Replace window.LiteGraph.ON_EVENT static assignment with a deprecation
  getter that console.warn's then returns 1.
- Delete the no-op 'case ON_EVENT: break' arm in LGraphNode.changeMode().
  Widen the switch to a numeric default-accept so setMode(1) keeps working.
- Update comfyui-frontend-types .d.ts: replace typeof LiteGraph.ON_EVENT
  with literal 1 in the mode?: union. Bump types patch version.

Christian sign-off received on warning copy. Closes #12225 (release N
half — release N+1 PR will drop _UNUSED_1 and the deprecation getter).
2026-05-13 16:22:20 -07:00
7 changed files with 368 additions and 10 deletions

View File

@@ -263,10 +263,14 @@ export class LGraph
errors_in_execution?: boolean
/** @deprecated Unused */
execution_time!: number
_last_trigger_time?: number
filter?: string
/** Must contain serialisable values, e.g. primitive types */
config: LGraphConfig = {}
vars: Dictionary<unknown> = {}
nodes_executing: boolean[] = []
nodes_actioning: (string | boolean)[] = []
nodes_executedAction: string[] = []
extra: LGraphExtra = {}
/** @deprecated Deserialising a workflow sets this unused property. */
@@ -438,6 +442,10 @@ export class LGraph
this.catch_errors = true
this.nodes_executing = []
this.nodes_actioning = []
this.nodes_executedAction = []
// notify canvas to redraw
this.change()
@@ -578,8 +586,10 @@ export class LGraph
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?.()
// FIXME: Looks like copy/paste broken logic - checks for "on", executes "do"
if (node.mode == LGraphEventMode.ALWAYS && node.onExecute) {
// wrap node.onExecute();
node.doExecute?.()
}
}
@@ -623,6 +633,9 @@ export class LGraph
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 = []
}
/**
@@ -1359,6 +1372,24 @@ export class LGraph
// Don't handle unknown events - just ignore them
}
/** @todo Clean up - never implemented. */
triggerInput(name: string, value: unknown): void {
const nodes = this.findNodesByTitle(name)
for (const node of nodes) {
// @ts-expect-error - onTrigger method may not exist on all node types
node.onTrigger(value)
}
}
/** @todo Clean up - never implemented. */
setCallback(name: string, func?: () => void): void {
const nodes = this.findNodesByTitle(name)
for (const node of nodes) {
// @ts-expect-error - setTrigger method may not exist on all node types
node.setTrigger(func)
}
}
// used for undo, called before any change is made to the graph
beforeChange(info?: LGraphNode): void {
this.onBeforeChange?.(this, info)
@@ -1371,6 +1402,17 @@ export class LGraph
this.canvasAction((c) => c.onAfterChange?.(this))
}
/**
* clears the triggered slot animation in all links (stop visual animation)
*/
clearTriggeredSlots(): void {
for (const link_info of this._links.values()) {
if (!link_info) continue
if (link_info._last_time) link_info._last_time = 0
}
}
/* Called when something visually changed (not the graph!) */
change(): void {
this.canvasAction((c) => c.setDirty(true, true))

View File

@@ -1283,6 +1283,16 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
let entries: (IContextMenuValue<INodeSlotContextItem> | null)[] = []
if (
LiteGraph.do_add_triggers_slots &&
node.findOutputSlot('onExecuted') == -1
) {
entries.push({
content: 'On Executed',
value: ['onExecuted', LiteGraph.EVENT, { nameLocked: true }],
className: 'event'
})
}
// add callback for modifying the menu elements onMenuNodeOutputs
const retEntries = node.onMenuNodeOutputs?.(entries)
if (retEntries) entries = retEntries
@@ -5012,7 +5022,9 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
if (
this.dirty_bgcanvas ||
force_bgcanvas ||
this.always_render_background
this.always_render_background ||
(this.graph?._last_trigger_time &&
now - this.graph._last_trigger_time < 1000)
) {
this.drawBackCanvas()
}
@@ -5894,6 +5906,12 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
}
node.drawProgressBar(ctx)
// these counter helps in conditioning drawing based on if the node has been executed or an action occurred
if (node.execute_triggered != null && node.execute_triggered > 0)
node.execute_triggered--
if (node.action_triggered != null && node.action_triggered > 0)
node.action_triggered--
}
/**
@@ -5967,6 +5985,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
const visibleReroutes: Reroute[] = []
const now = LiteGraph.getTime()
const { visible_area } = this
margin_area[0] = visible_area[0] - 20
margin_area[1] = visible_area[1] - 20
@@ -6031,6 +6050,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
startPos,
endPos,
visibleReroutes,
now,
output.dir,
input.dir
)
@@ -6059,6 +6079,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
output.pos,
endPos,
visibleReroutes,
now,
input.dir,
input.dir
)
@@ -6085,6 +6106,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
startPos,
input.pos,
visibleReroutes,
now,
output.dir,
input.dir
)
@@ -6092,7 +6114,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
}
if (graph.floatingLinks.size > 0) {
this._renderFloatingLinks(ctx, graph, visibleReroutes)
this._renderFloatingLinks(ctx, graph, visibleReroutes, now)
}
const rerouteSet = this._visibleReroutes
@@ -6138,7 +6160,8 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
private _renderFloatingLinks(
ctx: CanvasRenderingContext2D,
graph: LGraph,
visibleReroutes: Reroute[]
visibleReroutes: Reroute[],
now: number
) {
// Render floating links with 3/4 current alpha
const { globalAlpha } = ctx
@@ -6169,6 +6192,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
startPos,
endPos,
visibleReroutes,
now,
LinkDirection.CENTER,
endDirection,
true
@@ -6190,6 +6214,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
startPos,
endPos,
visibleReroutes,
now,
startDirection,
LinkDirection.CENTER,
true
@@ -6205,6 +6230,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
startPos: Point,
endPos: Point,
visibleReroutes: Reroute[],
now: number,
startDirection?: LinkDirection,
endDirection?: LinkDirection,
disabled: boolean = false
@@ -6325,6 +6351,25 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
)
}
renderedPaths.add(link)
// event triggered rendered on top
if (link?._last_time && now - link._last_time < 1000) {
const f = 2.0 - (now - link._last_time) * 0.002
const tmp = ctx.globalAlpha
ctx.globalAlpha = tmp * f
this.renderLink(
ctx,
startPos,
endPos,
link,
true,
f,
'white',
start_dir,
end_dir
)
ctx.globalAlpha = tmp
}
}
/**

View File

@@ -214,6 +214,7 @@ supported callbacks:
+ onDropFile : file dropped over the node
+ onConnectInput : if returns false the incoming connection will be canceled
+ onConnectionsChange : a connection changed (new one or removed) (NodeSlotType.INPUT or NodeSlotType.OUTPUT, slot, true if connected, link_info, input_info )
+ onAction: action slot triggered
+ getExtraMenuOptions: to add option to context menu
*/
@@ -352,6 +353,11 @@ export class LGraphNode
get renderingBoxColor(): string {
if (this.boxcolor) return this.boxcolor
if (LiteGraph.node_box_coloured_when_on) {
if (this.action_triggered) return '#FFF'
if (this.execute_triggered) return '#AAA'
}
if (LiteGraph.node_box_coloured_by_mode) {
const modeColour =
LiteGraph.NODE_MODES_COLORS[this.mode ?? LGraphEventMode.ALWAYS]
@@ -395,6 +401,10 @@ export class LGraphNode
*/
progress?: number
exec_version?: number
action_call?: string
execute_triggered?: number
action_triggered?: number
/**
* @deprecated This property is unsupported and will be removed in a future release.
* Use `widgets_start_y` or a custom `arrange()` override instead.
@@ -621,6 +631,12 @@ export class LGraphNode
param?: unknown,
options?: { action_call?: string }
): void
onAction?(
this: LGraphNode,
action: string,
param: unknown,
options: { action_call?: string }
): void
onDrawBackground?(this: LGraphNode, ctx: CanvasRenderingContext2D): void
onNodeCreated?(this: LGraphNode): void
/**
@@ -1333,9 +1349,40 @@ export class LGraphNode
return r
}
addOnTriggerInput(): number {
const trigS = this.findInputSlot('onTrigger')
if (trigS == -1) {
this.addInput('onTrigger', LiteGraph.EVENT, {
nameLocked: true
})
return this.findInputSlot('onTrigger')
}
return trigS
}
addOnExecutedOutput(): number {
const trigS = this.findOutputSlot('onExecuted')
if (trigS == -1) {
this.addOutput('onExecuted', LiteGraph.ACTION, {
nameLocked: true
})
return this.findOutputSlot('onExecuted')
}
return trigS
}
onAfterExecuteNode(param: unknown, options?: { action_call?: string }) {
const trigS = this.findOutputSlot('onExecuted')
if (trigS != -1) {
this.triggerSlot(trigS, param, null, options)
}
}
changeMode(modeTo: number): boolean {
switch (modeTo) {
case LGraphEventMode.ON_EVENT:
case LGraphEventMode.ON_TRIGGER:
this.addOnTriggerInput()
this.addOnExecutedOutput()
break
case LGraphEventMode.NEVER:
@@ -1349,13 +1396,197 @@ export class LGraphNode
break
default:
return false
// Numeric default-accept: any caller-supplied numeric mode (including
// the deprecated slot 1 / ON_EVENT) falls through and is assigned.
break
}
this.mode = modeTo
return true
}
/**
* Triggers the node code execution, place a boolean/counter to mark the node as being executed
*/
doExecute(param?: unknown, options?: { action_call?: string }): void {
options = options || {}
if (this.onExecute) {
// enable this to give the event an ID
options.action_call ||= `${this.id}_exec_${Math.floor(Math.random() * 9999)}`
if (!this.graph) throw new NullGraphError()
// @ts-expect-error Technically it works when id is a string. Array gets props.
this.graph.nodes_executing[this.id] = true
this.onExecute(param, options)
// @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
this.graph.nodes_executedAction[this.id] = options.action_call
}
}
// the nFrames it will be used (-- each step), means "how old" is the event
this.execute_triggered = 2
this.onAfterExecuteNode?.(param, options)
}
/**
* Triggers an action, wrapped by logics to control execution flow
* @param action name
*/
actionDo(
action: string,
param: unknown,
options: { action_call?: string }
): void {
options = options || {}
if (this.onAction) {
// enable this to give the event an ID
options.action_call ||= `${this.id}_${action || 'action'}_${Math.floor(Math.random() * 9999)}`
if (!this.graph) throw new NullGraphError()
// @ts-expect-error deprecated
this.graph.nodes_actioning[this.id] = action || 'actioning'
this.onAction(action, param, options)
// @ts-expect-error deprecated
this.graph.nodes_actioning[this.id] = false
// save execution/action ref
if (options?.action_call) {
this.action_call = options.action_call
// @ts-expect-error deprecated
this.graph.nodes_executedAction[this.id] = options.action_call
}
}
// the nFrames it will be used (-- each step), means "how old" is the event
this.action_triggered = 2
this.onAfterExecuteNode?.(param, options)
}
/**
* Triggers an event in this node, this will trigger any output with the same name
* @param action name ( "on_play", ... ) if action is equivalent to false then the event is send to all
*/
trigger(
action: string,
param: unknown,
options: { action_call?: string }
): void {
const { outputs } = this
if (!outputs || !outputs.length) {
return
}
if (this.graph) this.graph._last_trigger_time = LiteGraph.getTime()
for (const [i, output] of outputs.entries()) {
if (
!output ||
output.type !== LiteGraph.EVENT ||
(action && output.name != action)
) {
continue
}
this.triggerSlot(i, param, null, options)
}
}
/**
* Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes
* @param slot the index of the output slot
* @param link_id [optional] in case you want to trigger and specific output link in a slot
*/
triggerSlot(
slot: number,
param: unknown,
link_id: number | null,
options?: { action_call?: string }
): void {
options = options || {}
if (!this.outputs) return
if (slot == null) {
console.error('slot must be a number')
return
}
if (typeof slot !== 'number')
console.warn(
"slot must be a number, use node.trigger('name') if you want to use a string"
)
const output = this.outputs[slot]
if (!output) return
const links = output.links
if (!links || !links.length) return
if (!this.graph) throw new NullGraphError()
this.graph._last_trigger_time = LiteGraph.getTime()
// for every link attached here
for (const id of links) {
// to skip links
if (link_id != null && link_id != id) continue
const link_info = this.graph._links.get(id)
// not connected
if (!link_info) continue
link_info._last_time = LiteGraph.getTime()
const node = this.graph.getNodeById(link_info.target_id)
// node not found?
if (!node) continue
if (node.mode === LGraphEventMode.ON_TRIGGER) {
// generate unique trigger ID if not present
if (!options.action_call)
options.action_call = `${this.id}_trigg_${Math.floor(Math.random() * 9999)}`
// -- wrapping node.onExecute(param); --
node.doExecute?.(param, options)
} else if (node.onAction) {
// generate unique action ID if not present
if (!options.action_call)
options.action_call = `${this.id}_act_${Math.floor(Math.random() * 9999)}`
// pass the action name
const target_connection = node.inputs[link_info.target_slot]
node.actionDo(target_connection.name, param, options)
}
}
}
/**
* clears the trigger slot animation
* @param slot the index of the output slot
* @param link_id [optional] in case you want to trigger and specific output link in a slot
*/
clearTriggeredSlot(slot: number, link_id: number): void {
if (!this.outputs) return
const output = this.outputs[slot]
if (!output) return
const links = output.links
if (!links || !links.length) return
if (!this.graph) throw new NullGraphError()
// for every link attached here
for (const id of links) {
// to skip links
if (link_id != null && link_id != id) continue
const link_info = this.graph._links.get(id)
// not connected
if (!link_info) continue
link_info._last_time = 0
}
}
/**
* changes node size and triggers callback
*/
@@ -2381,6 +2612,12 @@ export class LGraphNode
const slot = node.findSlotByType(findInputs, slotType, false, true)
if (slot >= 0 && slot !== null) return slot
// TODO: Remove or reimpl. events. WILL CREATE THE onTrigger IN SLOT
if (opts.createEventInCase && slotType == LiteGraph.EVENT) {
if (findInputs) return -1
if (LiteGraph.do_add_triggers_slots) return node.addOnExecutedOutput()
}
// connect to the first general output slot if not found a specific type and
if (opts.typedToWildcard) {
const generalSlot = node.findSlotByType(findInputs, 0, false, true, true)
@@ -2574,6 +2811,14 @@ export class LGraphNode
console.error(`Connect: Error, no slot of name ${targetIndex}`)
return null
}
} else if (target_slot === LiteGraph.EVENT) {
// TODO: Events
if (LiteGraph.do_add_triggers_slots) {
target_node.changeMode(LGraphEventMode.ON_TRIGGER)
targetIndex = target_node.findInputSlot('onTrigger')
} else {
return null
}
} else if (typeof target_slot === 'number') {
targetIndex = target_slot
} else {

View File

@@ -109,6 +109,8 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
_data?: unknown
/** Centre point of the link, calculated during render only - can be inaccurate */
_pos: Point
/** @todo Clean up - never implemented in comfy. */
_last_time?: number
/** The last canvas 2D path that was used to render this link */
path?: Path2D
/** @inheritdoc */

View File

@@ -118,12 +118,15 @@ export class LiteGraphGlobal {
ACTION = -1 as const
/** helper, will add "On Request" and more in the future */
NODE_MODES = ['Always', 'On Event', 'Never']
NODE_MODES = ['Always', 'On Event', 'Never', 'On Trigger']
/** use with node_box_coloured_by_mode */
NODE_MODES_COLORS = ['#666', '#422', '#333', '#224', '#626']
ALWAYS = LGraphEventMode.ALWAYS
ON_EVENT = LGraphEventMode.ON_EVENT
// ON_EVENT is registered as a deprecation getter in the constructor — see
// Object.defineProperty call below. The numeric slot (1) is preserved for
// v2 ABI; the symbol will be removed in release N+1.
NEVER = LGraphEventMode.NEVER
ON_TRIGGER = LGraphEventMode.ON_TRIGGER
UP = LinkDirection.UP
DOWN = LinkDirection.DOWN
@@ -242,6 +245,12 @@ export class LiteGraphGlobal {
/** [true!] very handy, ALT click to clone and drag the new node */
alt_drag_do_clone_nodes = false
/**
* [true!] will create and connect event slots when using action/events connections,
* !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this
*/
do_add_triggers_slots = false
/** [false!] being events, it is strongly recommended to use them sequentially, one by one */
allow_multi_output_for_events = true
@@ -365,6 +374,15 @@ export class LiteGraphGlobal {
constructor() {
Object.defineProperty(this, 'Classes', { writable: false })
Object.defineProperty(this, 'ON_EVENT', {
get() {
console.warn(
'LiteGraph.ON_EVENT is deprecated; numeric slot 1 is preserved for v2 ABI but the symbol will be removed in release N+1. ON_EVENT is a no-op mode — use NEVER to mute a node.'
)
return 1
},
configurable: true
})
}
Classes = {

View File

@@ -70,6 +70,7 @@ LiteGraphGlobal {
"Always",
"On Event",
"Never",
"On Trigger",
],
"NODE_MODES_COLORS": [
"#666",
@@ -94,6 +95,7 @@ LiteGraphGlobal {
"NO_TITLE": 1,
"Nodes": {},
"ON_EVENT": 1,
"ON_TRIGGER": 3,
"OUTPUT": 2,
"RIGHT": 4,
"ROUND_RADIUS": 8,
@@ -152,6 +154,7 @@ LiteGraphGlobal {
"dialog_close_on_mouse_leave": false,
"dialog_close_on_mouse_leave_delay": 500,
"distance": [Function],
"do_add_triggers_slots": false,
"highlight_selected_group": true,
"isInsideRectangle": [Function],
"leftMouseClickBehavior": "panning",

View File

@@ -82,8 +82,11 @@ export enum TitleMode {
export enum LGraphEventMode {
ALWAYS = 0,
ON_EVENT = 1,
/** @deprecated No-op mode. Numeric slot 1 is preserved for v2 ABI; the
* symbol will be removed in release N+1. Use NEVER to mute a node. */
_UNUSED_1 = 1,
NEVER = 2,
ON_TRIGGER = 3,
BYPASS = 4
}