Spaces:
Configuration error
Configuration error
| import { app } from "scripts/app.js"; | |
| import type { BaseFastGroupsModeChanger } from "../fast_groups_muter.js"; | |
| import { | |
| type LGraph as TLGraph, | |
| type LGraphCanvas as TLGraphCanvas, | |
| LGraphGroup, | |
| Vector4, | |
| } from "typings/litegraph.js"; | |
| /** | |
| * A service that keeps global state that can be shared by multiple FastGroupsMuter or | |
| * FastGroupsBypasser nodes rather than calculate it on it's own. | |
| */ | |
| class FastGroupsService { | |
| private msThreshold = 400; | |
| private msLastUnsorted = 0; | |
| private msLastAlpha = 0; | |
| private msLastPosition = 0; | |
| private groupsUnsorted: LGraphGroup[] = []; | |
| private groupsSortedAlpha: LGraphGroup[] = []; | |
| private groupsSortedPosition: LGraphGroup[] = []; | |
| private readonly fastGroupNodes: BaseFastGroupsModeChanger[] = []; | |
| private runScheduledForMs: number | null = null; | |
| private runScheduleTimeout: number | null = null; | |
| private runScheduleAnimation: number | null = null; | |
| private cachedNodeBoundings: { [key: number]: Vector4 } | null = null; | |
| constructor() { | |
| // Don't need to do anything, wait until a signal. | |
| } | |
| addFastGroupNode(node: BaseFastGroupsModeChanger) { | |
| this.fastGroupNodes.push(node); | |
| // Schedule it because the node may not be ready to refreshWidgets (like, when added it may | |
| // not have cloned properties to filter against, etc.). | |
| this.scheduleRun(8); | |
| } | |
| removeFastGroupNode(node: BaseFastGroupsModeChanger) { | |
| const index = this.fastGroupNodes.indexOf(node); | |
| if (index > -1) { | |
| this.fastGroupNodes.splice(index, 1); | |
| } | |
| // If we have no more group nodes, then clear out data; it could be because of a canvas clear. | |
| if (!this.fastGroupNodes?.length) { | |
| this.clearScheduledRun(); | |
| this.groupsUnsorted = []; | |
| this.groupsSortedAlpha = []; | |
| this.groupsSortedPosition = []; | |
| } | |
| } | |
| private run() { | |
| // We only run if we're scheduled, so if we're not, then bail. | |
| if (!this.runScheduledForMs) { | |
| return; | |
| } | |
| for (const node of this.fastGroupNodes) { | |
| node.refreshWidgets(); | |
| } | |
| this.clearScheduledRun(); | |
| this.scheduleRun(); | |
| } | |
| private scheduleRun(ms = 500) { | |
| // If we got a request for an immediate schedule and already have on scheduled for longer, then | |
| // cancel the long one to expediate a fast one. | |
| if (this.runScheduledForMs && ms < this.runScheduledForMs) { | |
| this.clearScheduledRun(); | |
| } | |
| if (!this.runScheduledForMs && this.fastGroupNodes.length) { | |
| this.runScheduledForMs = ms; | |
| this.runScheduleTimeout = setTimeout(() => { | |
| this.runScheduleAnimation = requestAnimationFrame(() => this.run()); | |
| }, ms); | |
| } | |
| } | |
| private clearScheduledRun() { | |
| this.runScheduleTimeout && clearTimeout(this.runScheduleTimeout); | |
| this.runScheduleAnimation && cancelAnimationFrame(this.runScheduleAnimation); | |
| this.runScheduleTimeout = null; | |
| this.runScheduleAnimation = null; | |
| this.runScheduledForMs = null; | |
| } | |
| /** | |
| * Returns the boundings for all nodes on the graph, then clears it after a short delay. This is | |
| * to increase efficiency by caching the nodes' boundings when multiple groups are on the page. | |
| */ | |
| getBoundingsForAllNodes() { | |
| if (!this.cachedNodeBoundings) { | |
| this.cachedNodeBoundings = {}; | |
| for (const node of app.graph._nodes) { | |
| this.cachedNodeBoundings[node.id] = node.getBounding(); | |
| } | |
| setTimeout(() => { | |
| this.cachedNodeBoundings = null; | |
| }, 50); | |
| } | |
| return this.cachedNodeBoundings; | |
| } | |
| /** | |
| * This overrides `LGraphGroup.prototype.recomputeInsideNodes` to be much more efficient when | |
| * calculating for many groups at once (only compute all nodes once in `getBoundingsForAllNodes`). | |
| */ | |
| recomputeInsideNodesForGroup(group: LGraphGroup) { | |
| const cachedBoundings = this.getBoundingsForAllNodes(); | |
| const nodes = group.graph._nodes; | |
| group._nodes.length = 0; | |
| for (const node of nodes) { | |
| const node_bounding = cachedBoundings[node.id]; | |
| if (!node_bounding || !LiteGraph.overlapBounding(group._bounding, node_bounding)) { | |
| continue; | |
| } | |
| group._nodes.push(node); | |
| } | |
| } | |
| /** | |
| * Everything goes through getGroupsUnsorted, so we only get groups once. However, LiteGraph's | |
| * `recomputeInsideNodes` is inefficient when calling multiple groups (it iterates over all nodes | |
| * each time). So, we'll do our own dang thing, once. | |
| */ | |
| private getGroupsUnsorted(now: number) { | |
| const canvas = app.canvas as TLGraphCanvas; | |
| const graph = app.graph as TLGraph; | |
| if ( | |
| // Don't recalculate nodes if we're moving a group (added by ComfyUI in app.js) | |
| !canvas.selected_group_moving && | |
| (!this.groupsUnsorted.length || now - this.msLastUnsorted > this.msThreshold) | |
| ) { | |
| this.groupsUnsorted = [...graph._groups]; | |
| for (const group of this.groupsUnsorted) { | |
| this.recomputeInsideNodesForGroup(group); | |
| (group as any)._rgthreeHasAnyActiveNode = group._nodes.some( | |
| (n) => n.mode === LiteGraph.ALWAYS, | |
| ); | |
| } | |
| this.msLastUnsorted = now; | |
| } | |
| return this.groupsUnsorted; | |
| } | |
| private getGroupsAlpha(now: number) { | |
| const graph = app.graph as TLGraph; | |
| if (!this.groupsSortedAlpha.length || now - this.msLastAlpha > this.msThreshold) { | |
| this.groupsSortedAlpha = [...this.getGroupsUnsorted(now)].sort((a, b) => { | |
| return a.title.localeCompare(b.title); | |
| }); | |
| this.msLastAlpha = now; | |
| } | |
| return this.groupsSortedAlpha; | |
| } | |
| private getGroupsPosition(now: number) { | |
| const graph = app.graph as TLGraph; | |
| if (!this.groupsSortedPosition.length || now - this.msLastPosition > this.msThreshold) { | |
| this.groupsSortedPosition = [...this.getGroupsUnsorted(now)].sort((a, b) => { | |
| // Sort by y, then x, clamped to 30. | |
| const aY = Math.floor(a._pos[1] / 30); | |
| const bY = Math.floor(b._pos[1] / 30); | |
| if (aY == bY) { | |
| const aX = Math.floor(a._pos[0] / 30); | |
| const bX = Math.floor(b._pos[0] / 30); | |
| return aX - bX; | |
| } | |
| return aY - bY; | |
| }); | |
| this.msLastPosition = now; | |
| } | |
| return this.groupsSortedPosition; | |
| } | |
| getGroups(sort?: string) { | |
| const now = +new Date(); | |
| if (sort === "alphanumeric") { | |
| return this.getGroupsAlpha(now); | |
| } | |
| if (sort === "position") { | |
| return this.getGroupsPosition(now); | |
| } | |
| return this.getGroupsUnsorted(now); | |
| } | |
| } | |
| /** The FastGroupsService singleton. */ | |
| export const SERVICE = new FastGroupsService(); | |