From 4e34d2a93f59600e8b1c698386f578884d6bb4e8 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Sat, 24 Jan 2026 15:15:32 +0800 Subject: [PATCH] fix: enhance recomputeInsideNodes to support nested group processing with visited set --- src/lib/litegraph/src/LGraphGroup.test.ts | 70 ++++++++++++++++++++++- src/lib/litegraph/src/LGraphGroup.ts | 12 +++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/lib/litegraph/src/LGraphGroup.test.ts b/src/lib/litegraph/src/LGraphGroup.test.ts index 589e5a958..64b437b6a 100644 --- a/src/lib/litegraph/src/LGraphGroup.test.ts +++ b/src/lib/litegraph/src/LGraphGroup.test.ts @@ -1,6 +1,6 @@ import { describe, expect } from 'vitest' -import { LGraphGroup } from '@/lib/litegraph/src/litegraph' +import { LGraph, LGraphGroup } from '@/lib/litegraph/src/litegraph' import { test } from './__fixtures__/testExtensions' @@ -9,4 +9,72 @@ describe('LGraphGroup', () => { const link = new LGraphGroup('title', 929) expect(link.serialize()).toMatchSnapshot('Basic') }) + + describe('recomputeInsideNodes', () => { + test('uses visited set to avoid redundant computation', () => { + const graph = new LGraph() + + // Create 4 nested groups: outer -> mid1 -> mid2 -> inner + const outer = new LGraphGroup('outer') + outer.pos = [0, 0] + outer.size = [400, 400] + graph.add(outer) + + const mid1 = new LGraphGroup('mid1') + mid1.pos = [10, 10] + mid1.size = [300, 300] + graph.add(mid1) + + const mid2 = new LGraphGroup('mid2') + mid2.pos = [20, 20] + mid2.size = [200, 200] + graph.add(mid2) + + const inner = new LGraphGroup('inner') + inner.pos = [30, 30] + inner.size = [100, 100] + graph.add(inner) + + // Track the visited set to verify each group is only fully processed once + const visited = new Set() + outer.recomputeInsideNodes(100, visited) + + // All nested groups should be in the visited set + expect(visited.has(outer.id)).toBe(true) + expect(visited.has(mid1.id)).toBe(true) + expect(visited.has(mid2.id)).toBe(true) + expect(visited.has(inner.id)).toBe(true) + expect(visited.size).toBe(4) + + // Verify children relationships are correct + expect(outer.children.has(mid1)).toBe(true) + expect(outer.children.has(mid2)).toBe(true) + expect(outer.children.has(inner)).toBe(true) + expect(mid1.children.has(mid2)).toBe(true) + expect(mid1.children.has(inner)).toBe(true) + expect(mid2.children.has(inner)).toBe(true) + }) + + test('respects maxDepth limit', () => { + const graph = new LGraph() + + const outer = new LGraphGroup('outer') + outer.pos = [0, 0] + outer.size = [300, 300] + graph.add(outer) + + const inner = new LGraphGroup('inner') + inner.pos = [10, 10] + inner.size = [100, 100] + graph.add(inner) + + // With maxDepth=1, inner group is added as child but not processed + outer.recomputeInsideNodes(1) + + // outer should have inner as a child + expect(outer.children.has(inner)).toBe(true) + // inner should not have computed its own children (it was never processed) + expect(inner.children.size).toBe(0) + }) + }) }) diff --git a/src/lib/litegraph/src/LGraphGroup.ts b/src/lib/litegraph/src/LGraphGroup.ts index f4a50c996..ee3197d43 100644 --- a/src/lib/litegraph/src/LGraphGroup.ts +++ b/src/lib/litegraph/src/LGraphGroup.ts @@ -245,10 +245,16 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable { * Recomputes which items (nodes, reroutes, nested groups) are inside this group. * Recursively processes nested groups to ensure their children are also computed. * @param maxDepth Maximum recursion depth for nested groups. Use 1 to skip nested group computation. + * @param visited Set of already visited group IDs to prevent redundant computation. */ - recomputeInsideNodes(maxDepth: number = 100): void { + recomputeInsideNodes( + maxDepth: number = 100, + visited: Set = new Set() + ): void { if (!this.graph) throw new NullGraphError() - if (maxDepth <= 0) return + if (maxDepth <= 0 || visited.has(this.id)) return + + visited.add(this.id) const { nodes, reroutes, groups } = this.graph const children = this._children @@ -277,7 +283,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable { } } for (const group of containedGroups) - group.recomputeInsideNodes(maxDepth - 1) + group.recomputeInsideNodes(maxDepth - 1, visited) groups.sort((a, b) => { if (a === this) {