Fix slot layout validity on rendering system change

This commit is contained in:
Benjamin Lu
2025-09-04 23:06:41 -07:00
parent d4916fa999
commit 942cec10b4
4 changed files with 94 additions and 17 deletions

View File

@@ -183,6 +183,7 @@ let cleanupNodeManager: (() => void) | null = null
// Slot layout sync management
let slotSync: ReturnType<typeof useSlotLayoutSync> | null = null
let slotSyncStarted = false
let linkSync: ReturnType<typeof useLinkLayoutSync> | null = null
const vueNodeData = ref<ReadonlyMap<string, VueNodeData>>(new Map())
const nodeState = ref<ReadonlyMap<string, NodeState>>(new Map())
@@ -240,12 +241,6 @@ const initializeNodeManager = () => {
const { startSync } = useLayoutSync()
startSync(canvasStore.canvas)
// Initialize slot layout sync for hit detection
slotSync = useSlotLayoutSync()
if (canvasStore.canvas) {
slotSync.start(canvasStore.canvas as LGraphCanvas)
}
// Initialize link layout sync for event-driven updates
linkSync = useLinkLayoutSync()
if (canvasStore.canvas) {
@@ -266,12 +261,6 @@ const disposeNodeManagerAndSyncs = () => {
nodeManager = null
cleanupNodeManager = null
// Clean up slot layout sync
if (slotSync) {
slotSync.stop()
slotSync = null
}
// Clean up link layout sync
if (linkSync) {
linkSync.stop()
@@ -298,6 +287,68 @@ watch(
{ immediate: true }
)
// Ensure slot layout sync starts whenever a canvas is available (LiteGraph mode)
watch(
() => canvasStore.canvas,
(canvas, oldCanvas) => {
if (!canvas) {
// Canvas was removed - stop sync if active
if (slotSync && slotSyncStarted) {
slotSync.stop()
slotSyncStarted = false
}
// Clear any stale slot layouts when canvas is torn down
layoutStore.clearAllSlotLayouts()
return
}
// Canvas changed - restart sync
if (oldCanvas && oldCanvas !== canvas) {
if (slotSync && slotSyncStarted) {
slotSync.stop()
slotSyncStarted = false
}
}
// Start sync if not in Vue mode and not already started
if (!slotSync) slotSync = useSlotLayoutSync()
if (!slotSyncStarted && !isVueNodesEnabled.value) {
const started = slotSync.start(canvas as LGraphCanvas)
slotSyncStarted = started
}
},
{ immediate: true }
)
// On rendering mode change, clear slot layouts and manage slot sync
watch(
() => isVueNodesEnabled.value,
(enabled) => {
// Always clear invalid slot layouts from the prior mode
layoutStore.clearAllSlotLayouts()
if (enabled) {
// Switching TO Vue: Stop slot sync to avoid duplicate registration
if (slotSync && slotSyncStarted) {
slotSync.stop()
slotSyncStarted = false
}
// DOM will re-register via useDomSlotRegistration
} else {
// Switching TO LiteGraph
if (canvasStore.canvas && comfyApp.graph) {
// Ensure slot sync is active
if (!slotSync) slotSync = useSlotLayoutSync()
if (!slotSyncStarted) {
const started = slotSync.start(canvasStore.canvas as LGraphCanvas)
slotSyncStarted = started
}
}
}
},
{ immediate: false }
)
// Transform state for viewport culling
const { syncWithCanvas } = useTransformState()
@@ -726,6 +777,7 @@ onUnmounted(() => {
if (slotSync) {
slotSync.stop()
slotSync = null
slotSyncStarted = false
}
if (linkSync) {
linkSync.stop()

View File

@@ -371,7 +371,19 @@ class LayoutStoreImpl implements LayoutStore {
updateSlotLayout(key: string, layout: SlotLayout): void {
const existing = this.slotLayouts.get(key)
if (!existing) {
if (existing) {
// Short-circuit if bounds and position unchanged (prevents spatial index churn)
if (
existing.bounds.x === layout.bounds.x &&
existing.bounds.y === layout.bounds.y &&
existing.bounds.width === layout.bounds.width &&
existing.bounds.height === layout.bounds.height &&
existing.position.x === layout.position.x &&
existing.position.y === layout.position.y
) {
return
}
} else {
logger.debug('Adding slot:', {
nodeId: layout.nodeId,
type: layout.type,
@@ -419,6 +431,15 @@ class LayoutStoreImpl implements LayoutStore {
}
}
/**
* Clear all slot layouts and their spatial index (O(1) operations)
* Used when switching rendering modes (Vue ↔ LiteGraph)
*/
clearAllSlotLayouts(): void {
this.slotLayouts.clear()
this.slotSpatialIndex.clear()
}
/**
* Update reroute layout data
*/

View File

@@ -16,7 +16,7 @@ import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
* Compute and register slot layouts for a node
* @param node LiteGraph node to process
*/
function computeAndRegisterSlots(node: LGraphNode): void {
export function computeAndRegisterSlots(node: LGraphNode): void {
const nodeId = String(node.id)
const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value
@@ -59,15 +59,16 @@ export function useSlotLayoutSync() {
/**
* Start slot layout sync with full event-driven functionality
* @param canvas LiteGraph canvas instance
* @returns true if sync was actually started, false if early-returned
*/
function start(canvas: LGraphCanvas): void {
function start(canvas: LGraphCanvas): boolean {
// When Vue nodes are enabled, slot DOM registers exact positions.
// Skip calculated registration to avoid conflicts.
if (LiteGraph.vueNodesMode) {
return
return false
}
const graph = canvas?.graph
if (!graph) return
if (!graph) return false
// Initial registration for all nodes in the current graph
for (const node of graph._nodes) {
@@ -135,6 +136,8 @@ export function useSlotLayoutSync() {
graph.onTrigger = origTrigger || undefined
graph.onAfterChange = origAfterChange || undefined
}
return true
}
/**

View File

@@ -297,6 +297,7 @@ export interface LayoutStore {
deleteSlotLayout(key: string): void
deleteNodeSlotLayouts(nodeId: NodeId): void
deleteRerouteLayout(rerouteId: RerouteId): void
clearAllSlotLayouts(): void
// Get layout data
getLinkLayout(linkId: LinkId): LinkLayout | null