mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 14:27:40 +00:00
Add subgraph skeleton classes (#997)
Allows downstream consumers to use subgraph types ahead of impl.
This commit is contained in:
@@ -48,13 +48,17 @@ export interface LGraphConfig {
|
||||
links_ontop?: any
|
||||
}
|
||||
|
||||
export interface BaseLGraph {
|
||||
readonly rootGraph: LGraph
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
* + onNodeAdded: when a new node is added to the graph
|
||||
* + onNodeRemoved: when a node inside this graph is removed
|
||||
*/
|
||||
export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
|
||||
export class LGraph implements LinkNetwork, BaseLGraph, Serialisable<SerialisableGraph> {
|
||||
static serialisedSchemaVersion = 1 as const
|
||||
|
||||
static STATUS_STOPPED = 1
|
||||
@@ -147,6 +151,14 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
|
||||
return this.#reroutes
|
||||
}
|
||||
|
||||
get rootGraph(): LGraph {
|
||||
return this
|
||||
}
|
||||
|
||||
get isRootGraph(): boolean {
|
||||
return this.rootGraph === this
|
||||
}
|
||||
|
||||
/** @deprecated See {@link state}.{@link LGraphState.lastNodeId lastNodeId} */
|
||||
get last_node_id() {
|
||||
return this.state.lastNodeId
|
||||
|
||||
@@ -61,6 +61,7 @@ import {
|
||||
import { NodeInputSlot } from "./node/NodeInputSlot"
|
||||
import { Reroute, type RerouteId } from "./Reroute"
|
||||
import { stringOrEmpty } from "./strings"
|
||||
import { Subgraph } from "./subgraph/Subgraph"
|
||||
import {
|
||||
CanvasItem,
|
||||
LGraphEventMode,
|
||||
@@ -260,6 +261,8 @@ export class LGraphCanvas {
|
||||
shouldSetCursor: true,
|
||||
}
|
||||
|
||||
declare subgraph?: Subgraph
|
||||
|
||||
#updateCursorStyle() {
|
||||
if (!this.state.shouldSetCursor) return
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ import type { LGraphNode } from "./LGraphNode"
|
||||
import type { CanvasEventDetail } from "./types/events"
|
||||
import type { RenderShape, TitleMode } from "./types/globalEnums"
|
||||
|
||||
// Must remain above LiteGraphGlobal (circular dependency due to abstract factory behaviour in `configure`)
|
||||
export type { Subgraph } from "./subgraph/Subgraph"
|
||||
|
||||
import { LiteGraphGlobal } from "./LiteGraphGlobal"
|
||||
import { loadPolyfills } from "./polyfills"
|
||||
|
||||
@@ -142,9 +145,13 @@ export {
|
||||
TitleMode,
|
||||
} from "./types/globalEnums"
|
||||
export type {
|
||||
ExportedSubgraph,
|
||||
ExportedSubgraphInstance,
|
||||
ExportedSubgraphIONode,
|
||||
ISerialisedGraph,
|
||||
SerialisableGraph,
|
||||
SerialisableLLink,
|
||||
SubgraphIO,
|
||||
} from "./types/serialisation"
|
||||
export type { IWidget } from "./types/widgets"
|
||||
export { isColorable } from "./utils/type"
|
||||
|
||||
74
src/subgraph/Subgraph.ts
Normal file
74
src/subgraph/Subgraph.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { ExportedSubgraph, ExposedWidget, Serialisable, SerialisableGraph } from "@/types/serialisation"
|
||||
|
||||
import { type BaseLGraph, LGraph } from "@/LGraph"
|
||||
|
||||
import { SubgraphInput } from "./SubgraphInput"
|
||||
import { SubgraphInputNode } from "./SubgraphInputNode"
|
||||
import { SubgraphOutput } from "./SubgraphOutput"
|
||||
import { SubgraphOutputNode } from "./SubgraphOutputNode"
|
||||
|
||||
/** Internal; simplifies type definitions. */
|
||||
export type GraphOrSubgraph = LGraph | Subgraph
|
||||
|
||||
/** A subgraph definition. */
|
||||
export class Subgraph extends LGraph implements BaseLGraph, Serialisable<ExportedSubgraph> {
|
||||
/** The display name of the subgraph. */
|
||||
name: string
|
||||
|
||||
readonly inputNode = new SubgraphInputNode(this)
|
||||
readonly outputNode = new SubgraphOutputNode(this)
|
||||
|
||||
/** Ordered list of inputs to the subgraph itself. Similar to a reroute, with the input side in the graph, and the output side in the subgraph. */
|
||||
readonly inputs: SubgraphInput[]
|
||||
/** Ordered list of outputs from the subgraph itself. Similar to a reroute, with the input side in the subgraph, and the output side in the graph. */
|
||||
readonly outputs: SubgraphOutput[]
|
||||
/** A list of node widgets displayed in the parent graph, on the subgraph object. */
|
||||
readonly widgets: ExposedWidget[]
|
||||
|
||||
override get rootGraph(): LGraph {
|
||||
return this.parents[0]
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
get pathToRootGraph(): readonly [LGraph, ...GraphOrSubgraph[]] {
|
||||
return [...this.parents, this]
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly parents: readonly [LGraph, ...GraphOrSubgraph[]],
|
||||
data: ExportedSubgraph,
|
||||
) {
|
||||
if (!parents.length) throw new Error("Subgraph must have at least one parent")
|
||||
|
||||
const cloned = structuredClone(data)
|
||||
const { name, inputs, outputs, widgets } = cloned
|
||||
super()
|
||||
|
||||
this.name = name
|
||||
this.inputs = inputs?.map(x => new SubgraphInput(x, this.inputNode)) ?? []
|
||||
this.outputs = outputs?.map(x => new SubgraphOutput(x, this.outputNode)) ?? []
|
||||
this.widgets = widgets ?? []
|
||||
|
||||
this.configure(cloned)
|
||||
}
|
||||
|
||||
override asSerialisable(): ExportedSubgraph & Required<Pick<SerialisableGraph, "nodes" | "groups" | "extra">> {
|
||||
return {
|
||||
id: this.id,
|
||||
version: LGraph.serialisedSchemaVersion,
|
||||
state: this.state,
|
||||
revision: this.revision,
|
||||
config: this.config,
|
||||
name: this.name,
|
||||
inputNode: this.inputNode.asSerialisable(),
|
||||
outputNode: this.outputNode.asSerialisable(),
|
||||
inputs: this.inputs.map(x => x.asSerialisable()),
|
||||
outputs: this.outputs.map(x => x.asSerialisable()),
|
||||
widgets: [...this.widgets],
|
||||
nodes: this.nodes.map(node => node.serialize()),
|
||||
groups: this.groups.map(group => group.serialize()),
|
||||
links: [...this.links.values()].map(x => x.asSerialisable()),
|
||||
extra: this.extra,
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/subgraph/SubgraphIONodeBase.ts
Normal file
82
src/subgraph/SubgraphIONodeBase.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { Subgraph } from "./Subgraph"
|
||||
import type { SubgraphInput } from "./SubgraphInput"
|
||||
import type { SubgraphOutput } from "./SubgraphOutput"
|
||||
import type { Point, Positionable, ReadOnlyRect, Rect } from "@/interfaces"
|
||||
import type { NodeId } from "@/LGraphNode"
|
||||
import type { ExportedSubgraphIONode, Serialisable } from "@/types/serialisation"
|
||||
|
||||
import { isPointInRect, snapPoint } from "@/measure"
|
||||
|
||||
export abstract class SubgraphIONodeBase implements Positionable, Serialisable<ExportedSubgraphIONode> {
|
||||
static margin = 10
|
||||
static defaultWidth = 100
|
||||
static roundedRadius = 10
|
||||
|
||||
readonly #boundingRect: Float32Array = new Float32Array(4)
|
||||
readonly #pos: Point = this.#boundingRect.subarray(0, 2)
|
||||
readonly #size: Point = this.#boundingRect.subarray(2, 4)
|
||||
|
||||
abstract readonly id: NodeId
|
||||
|
||||
get boundingRect(): Rect {
|
||||
return this.#boundingRect
|
||||
}
|
||||
|
||||
selected: boolean = false
|
||||
pinned: boolean = false
|
||||
|
||||
get pos() {
|
||||
return this.#pos
|
||||
}
|
||||
|
||||
set pos(value) {
|
||||
if (!value || value.length < 2) return
|
||||
|
||||
this.#pos[0] = value[0]
|
||||
this.#pos[1] = value[1]
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.#size
|
||||
}
|
||||
|
||||
set size(value) {
|
||||
if (!value || value.length < 2) return
|
||||
|
||||
this.#size[0] = value[0]
|
||||
this.#size[1] = value[1]
|
||||
}
|
||||
|
||||
abstract readonly slots: SubgraphInput[] | SubgraphOutput[]
|
||||
|
||||
constructor(
|
||||
/** The subgraph that this node belongs to. */
|
||||
readonly subgraph: Subgraph,
|
||||
) {}
|
||||
|
||||
move(deltaX: number, deltaY: number): void {
|
||||
this.pos[0] += deltaX
|
||||
this.pos[1] += deltaY
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
snapToGrid(snapTo: number): boolean {
|
||||
return this.pinned ? false : snapPoint(this.pos, snapTo)
|
||||
}
|
||||
|
||||
containsPoint(point: Point): boolean {
|
||||
return isPointInRect(point, this.boundingRect)
|
||||
}
|
||||
|
||||
asSerialisable(): ExportedSubgraphIONode {
|
||||
return {
|
||||
id: this.id,
|
||||
bounding: serialiseRect(this.boundingRect),
|
||||
pinned: this.pinned ? true : undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function serialiseRect(rect: ReadOnlyRect): [number, number, number, number] {
|
||||
return [rect[0], rect[1], rect[2], rect[3]]
|
||||
}
|
||||
24
src/subgraph/SubgraphInput.ts
Normal file
24
src/subgraph/SubgraphInput.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { Point, ReadOnlyRect } from "@/interfaces"
|
||||
|
||||
import { SubgraphSlot } from "./SubgraphSlotBase"
|
||||
|
||||
export class SubgraphInput extends SubgraphSlot {
|
||||
get labelPos(): Point {
|
||||
const [x, y, , height] = this.boundingRect
|
||||
return [x, y + height * 0.5]
|
||||
}
|
||||
|
||||
/** For inputs, x is the right edge of the input node. */
|
||||
override arrange(rect: ReadOnlyRect): void {
|
||||
const [right, top, width, height] = rect
|
||||
const { boundingRect: b, pos } = this
|
||||
|
||||
b[0] = right - width
|
||||
b[1] = top
|
||||
b[2] = width
|
||||
b[3] = height
|
||||
|
||||
pos[0] = right - height * 0.5
|
||||
pos[1] = top + height * 0.5
|
||||
}
|
||||
}
|
||||
12
src/subgraph/SubgraphInputNode.ts
Normal file
12
src/subgraph/SubgraphInputNode.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Positionable } from "@/interfaces"
|
||||
import type { NodeId } from "@/LGraphNode"
|
||||
|
||||
import { SubgraphIONodeBase } from "./SubgraphIONodeBase"
|
||||
|
||||
export class SubgraphInputNode extends SubgraphIONodeBase implements Positionable {
|
||||
readonly id: NodeId = -10
|
||||
|
||||
get slots() {
|
||||
return this.subgraph.inputs
|
||||
}
|
||||
}
|
||||
23
src/subgraph/SubgraphOutput.ts
Normal file
23
src/subgraph/SubgraphOutput.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { Point, ReadOnlyRect } from "@/interfaces"
|
||||
|
||||
import { SubgraphSlot } from "./SubgraphSlotBase"
|
||||
|
||||
export class SubgraphOutput extends SubgraphSlot {
|
||||
get labelPos(): Point {
|
||||
const [x, y, , height] = this.boundingRect
|
||||
return [x + height, y + height * 0.5]
|
||||
}
|
||||
|
||||
override arrange(rect: ReadOnlyRect): void {
|
||||
const [left, top, width, height] = rect
|
||||
const { boundingRect: b, pos } = this
|
||||
|
||||
b[0] = left
|
||||
b[1] = top
|
||||
b[2] = width
|
||||
b[3] = height
|
||||
|
||||
pos[0] = left + height * 0.5
|
||||
pos[1] = top + height * 0.5
|
||||
}
|
||||
}
|
||||
12
src/subgraph/SubgraphOutputNode.ts
Normal file
12
src/subgraph/SubgraphOutputNode.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Positionable } from "@/interfaces"
|
||||
import type { NodeId } from "@/LGraphNode"
|
||||
|
||||
import { SubgraphIONodeBase } from "./SubgraphIONodeBase"
|
||||
|
||||
export class SubgraphOutputNode extends SubgraphIONodeBase implements Positionable {
|
||||
readonly id: NodeId = -20
|
||||
|
||||
get slots() {
|
||||
return this.subgraph.outputs
|
||||
}
|
||||
}
|
||||
64
src/subgraph/SubgraphSlotBase.ts
Normal file
64
src/subgraph/SubgraphSlotBase.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { SubgraphIONodeBase } from "./SubgraphIONodeBase"
|
||||
import type { Point, ReadOnlyRect, Rect } from "@/interfaces"
|
||||
import type { LinkId } from "@/LLink"
|
||||
import type { Serialisable, SubgraphIO } from "@/types/serialisation"
|
||||
|
||||
import { LiteGraph } from "@/litegraph"
|
||||
import { SlotBase } from "@/node/SlotBase"
|
||||
import { createUuidv4, type UUID } from "@/utils/uuid"
|
||||
|
||||
/** Shared base class for the slots used on Subgraph . */
|
||||
export abstract class SubgraphSlot extends SlotBase implements SubgraphIO, Serialisable<SubgraphIO> {
|
||||
static get defaultHeight() {
|
||||
return LiteGraph.NODE_SLOT_HEIGHT
|
||||
}
|
||||
|
||||
readonly #pos: Point = new Float32Array(2)
|
||||
|
||||
readonly id: UUID
|
||||
readonly parent: SubgraphIONodeBase
|
||||
override type: string
|
||||
|
||||
readonly linkIds: LinkId[] = []
|
||||
|
||||
readonly boundingRect: Rect = [0, 0, 0, SubgraphSlot.defaultHeight]
|
||||
|
||||
override get pos() {
|
||||
return this.#pos
|
||||
}
|
||||
|
||||
override set pos(value) {
|
||||
if (!value || value.length < 2) return
|
||||
|
||||
this.#pos[0] = value[0]
|
||||
this.#pos[1] = value[1]
|
||||
}
|
||||
|
||||
/** Whether this slot is connected to another slot. */
|
||||
override get isConnected() {
|
||||
return this.linkIds.length > 0
|
||||
}
|
||||
|
||||
/** The display name of this slot. */
|
||||
get displayName() {
|
||||
return this.label ?? this.localized_name ?? this.name
|
||||
}
|
||||
|
||||
abstract get labelPos(): Point
|
||||
|
||||
constructor(slot: SubgraphIO, parent: SubgraphIONodeBase) {
|
||||
super(slot.name, slot.type, slot.boundingRect)
|
||||
|
||||
Object.assign(this, slot)
|
||||
this.id = slot.id ?? createUuidv4()
|
||||
this.type = slot.type
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
abstract arrange(rect: ReadOnlyRect): void
|
||||
|
||||
asSerialisable(): SubgraphIO {
|
||||
const { id, name, type, linkIds, localized_name, label, dir, shape, color_off, color_on, pos, boundingRect } = this
|
||||
return { id, name, type, linkIds, localized_name, label, dir, shape, color_off, color_on, pos, boundingRect }
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export interface BaseExportedGraph {
|
||||
/** Definitions of re-usable objects that are referenced elsewhere in this exported graph. */
|
||||
definitions?: {
|
||||
/** The base definition of subgraphs used in this workflow. That is, what you see when you open / edit a subgraph. */
|
||||
subgraphs?: Record<UUID, ExportedSubgraph>
|
||||
subgraphs?: ExportedSubgraph[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export interface ISerialisedNode {
|
||||
}
|
||||
|
||||
/** Properties of nodes that are used by subgraph instances. */
|
||||
type NodeSubgraphSharedProps = Omit<ISerialisedNode, "type" | "outputs" | "inputs" | "properties" | "showAdvanced" | "widgets_values">
|
||||
type NodeSubgraphSharedProps = Omit<ISerialisedNode, "properties" | "showAdvanced">
|
||||
|
||||
/** A single instance of a subgraph; where it is used on a graph, any customisation to shape / colour etc. */
|
||||
export interface ExportedSubgraphInstance extends NodeSubgraphSharedProps {
|
||||
@@ -126,16 +126,18 @@ export interface ISerialisedGraph extends BaseExportedGraph {
|
||||
export interface ExportedSubgraph extends SerialisableGraph {
|
||||
/** The display name of the subgraph. */
|
||||
name: string
|
||||
inputNode: ExportedSubgraphIONode
|
||||
outputNode: ExportedSubgraphIONode
|
||||
/** Ordered list of inputs to the subgraph itself. Similar to a reroute, with the input side in the graph, and the output side in the subgraph. */
|
||||
inputs: SubgraphIO[]
|
||||
inputs?: SubgraphIO[]
|
||||
/** Ordered list of outputs from the subgraph itself. Similar to a reroute, with the input side in the subgraph, and the output side in the graph. */
|
||||
outputs: SubgraphIO[]
|
||||
outputs?: SubgraphIO[]
|
||||
/** A list of node widgets displayed in the parent graph, on the subgraph object. */
|
||||
widgets: ExposedWidget[]
|
||||
widgets?: ExposedWidget[]
|
||||
}
|
||||
|
||||
/** Properties shared by subgraph and node I/O slots. */
|
||||
type SubgraphIOShared = Omit<INodeSlot, "nameLocked" | "locked" | "removable" | "boundingRect" | "_floatingLinks">
|
||||
type SubgraphIOShared = Omit<INodeSlot, "nameLocked" | "locked" | "removable" | "_floatingLinks">
|
||||
|
||||
/** Subgraph I/O slots */
|
||||
export interface SubgraphIO extends SubgraphIOShared {
|
||||
@@ -143,6 +145,8 @@ export interface SubgraphIO extends SubgraphIOShared {
|
||||
id: UUID
|
||||
/** The data type this slot uses. Unlike nodes, this does not support legacy numeric types. */
|
||||
type: string
|
||||
/** Links connected to this slot, or `undefined` if not connected. An ouptut slot should only ever have one link. */
|
||||
linkIds?: LinkId[]
|
||||
}
|
||||
|
||||
/** A reference to a node widget shown in the parent graph */
|
||||
@@ -209,3 +213,9 @@ export interface SerialisableLLink {
|
||||
/** ID of the last reroute (from input to output) that this link passes through, otherwise `undefined` */
|
||||
parentId?: RerouteId
|
||||
}
|
||||
|
||||
export interface ExportedSubgraphIONode {
|
||||
id: NodeId
|
||||
bounding: [number, number, number, number]
|
||||
pinned?: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user