mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-21 23:09:39 +00:00
Rely on RO for resize, and batch
This commit is contained in:
@@ -467,6 +467,25 @@ class LayoutStoreImpl implements LayoutStore {
|
||||
this.slotLayouts.set(key, layout)
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch update slot layouts and spatial index in one pass
|
||||
*/
|
||||
batchUpdateSlotLayouts(
|
||||
updates: Array<{ key: string; layout: SlotLayout }>
|
||||
): void {
|
||||
if (!updates.length) return
|
||||
|
||||
// Update spatial index and map entries
|
||||
for (const { key, layout } of updates) {
|
||||
if (this.slotLayouts.has(key)) {
|
||||
this.slotSpatialIndex.update(key, layout.bounds)
|
||||
} else {
|
||||
this.slotSpatialIndex.insert(key, layout.bounds)
|
||||
}
|
||||
this.slotLayouts.set(key, layout)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete slot layout data
|
||||
*/
|
||||
|
||||
@@ -330,4 +330,7 @@ export interface LayoutStore {
|
||||
batchUpdateNodeBounds(
|
||||
updates: Array<{ nodeId: NodeId; bounds: Bounds }>
|
||||
): void
|
||||
batchUpdateSlotLayouts(
|
||||
updates: Array<{ key: string; layout: SlotLayout }>
|
||||
): void
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
|
||||
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
|
||||
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||
import type { Point } from '@/renderer/core/layout/types'
|
||||
import type { Point, SlotLayout } from '@/renderer/core/layout/types'
|
||||
|
||||
type SlotEntry = {
|
||||
el: HTMLElement
|
||||
@@ -55,30 +55,53 @@ function runBatchedMeasure() {
|
||||
|
||||
for (const nodeId of Array.from(pendingNodes)) {
|
||||
pendingNodes.delete(nodeId)
|
||||
const node = nodeRegistry.get(nodeId)
|
||||
if (!node) continue
|
||||
if (!node.screenToCanvas) continue
|
||||
const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value
|
||||
if (!nodeLayout) continue
|
||||
measureNodeSlotsNow(nodeId, originLeft, originTop)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [slotKey, entry] of node.slots) {
|
||||
const rect = entry.el.getBoundingClientRect()
|
||||
const centerScreen = {
|
||||
x: rect.left + rect.width / 2 - originLeft,
|
||||
y: rect.top + rect.height / 2 - originTop
|
||||
}
|
||||
const centerCanvas = node.screenToCanvas(centerScreen)
|
||||
function measureNodeSlotsNow(
|
||||
nodeId: string,
|
||||
originLeft?: number,
|
||||
originTop?: number
|
||||
) {
|
||||
const node = nodeRegistry.get(nodeId)
|
||||
if (!node) return
|
||||
if (!node.screenToCanvas) return
|
||||
const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value
|
||||
if (!nodeLayout) return
|
||||
|
||||
// Cache offset relative to node position for fast updates later
|
||||
entry.cachedOffset = {
|
||||
x: centerCanvas.x - nodeLayout.position.x,
|
||||
y: centerCanvas.y - nodeLayout.position.y
|
||||
}
|
||||
// Compute origin lazily if not provided
|
||||
let originL = originLeft
|
||||
let originT = originTop
|
||||
if (originL == null || originT == null) {
|
||||
const container = document.getElementById('graph-canvas-container')
|
||||
const originRect = container?.getBoundingClientRect()
|
||||
originL = originRect?.left ?? 0
|
||||
originT = originRect?.top ?? 0
|
||||
}
|
||||
|
||||
// Persist layout in canvas coordinates
|
||||
const size = LiteGraph.NODE_SLOT_HEIGHT
|
||||
const half = size / 2
|
||||
layoutStore.updateSlotLayout(slotKey, {
|
||||
const batch: Array<{ key: string; layout: SlotLayout }> = []
|
||||
|
||||
for (const [slotKey, entry] of node.slots) {
|
||||
const rect = entry.el.getBoundingClientRect()
|
||||
const centerScreen = {
|
||||
x: rect.left + rect.width / 2 - (originL ?? 0),
|
||||
y: rect.top + rect.height / 2 - (originT ?? 0)
|
||||
}
|
||||
const centerCanvas = node.screenToCanvas(centerScreen)
|
||||
|
||||
// Cache offset relative to node position for fast updates later
|
||||
entry.cachedOffset = {
|
||||
x: centerCanvas.x - nodeLayout.position.x,
|
||||
y: centerCanvas.y - nodeLayout.position.y
|
||||
}
|
||||
|
||||
// Persist layout in canvas coordinates
|
||||
const size = LiteGraph.NODE_SLOT_HEIGHT
|
||||
const half = size / 2
|
||||
batch.push({
|
||||
key: slotKey,
|
||||
layout: {
|
||||
nodeId,
|
||||
index: entry.index,
|
||||
type: entry.isInput ? 'input' : 'output',
|
||||
@@ -89,9 +112,10 @@ function runBatchedMeasure() {
|
||||
width: size,
|
||||
height: size
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (batch.length) layoutStore.batchUpdateSlotLayouts(batch)
|
||||
}
|
||||
|
||||
function updateNodeSlotsFromCache(nodeId: string) {
|
||||
@@ -100,31 +124,39 @@ function updateNodeSlotsFromCache(nodeId: string) {
|
||||
const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value
|
||||
if (!nodeLayout) return
|
||||
|
||||
const batch: Array<{ key: string; layout: SlotLayout }> = []
|
||||
|
||||
for (const [slotKey, entry] of node.slots) {
|
||||
if (!entry.cachedOffset) {
|
||||
// schedule a remeasure to seed offset
|
||||
scheduleNodeMeasure(nodeId)
|
||||
continue
|
||||
}
|
||||
|
||||
const centerCanvas = {
|
||||
x: nodeLayout.position.x + entry.cachedOffset.x,
|
||||
y: nodeLayout.position.y + entry.cachedOffset.y
|
||||
}
|
||||
const size = LiteGraph.NODE_SLOT_HEIGHT
|
||||
const half = size / 2
|
||||
layoutStore.updateSlotLayout(slotKey, {
|
||||
nodeId,
|
||||
index: entry.index,
|
||||
type: entry.isInput ? 'input' : 'output',
|
||||
position: { x: centerCanvas.x, y: centerCanvas.y },
|
||||
bounds: {
|
||||
x: centerCanvas.x - half,
|
||||
y: centerCanvas.y - half,
|
||||
width: size,
|
||||
height: size
|
||||
batch.push({
|
||||
key: slotKey,
|
||||
layout: {
|
||||
nodeId,
|
||||
index: entry.index,
|
||||
type: entry.isInput ? 'input' : 'output',
|
||||
position: { x: centerCanvas.x, y: centerCanvas.y },
|
||||
bounds: {
|
||||
x: centerCanvas.x - half,
|
||||
y: centerCanvas.y - half,
|
||||
width: size,
|
||||
height: size
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (batch.length) layoutStore.batchUpdateSlotLayouts(batch)
|
||||
}
|
||||
|
||||
export function useSlotElementTracking(options: {
|
||||
@@ -159,8 +191,18 @@ export function useSlotElementTracking(options: {
|
||||
nodeRef,
|
||||
(newLayout, oldLayout) => {
|
||||
if (newLayout && oldLayout) {
|
||||
// Update from cache on any position/size change
|
||||
updateNodeSlotsFromCache(nodeId)
|
||||
const moved =
|
||||
newLayout.position.x !== oldLayout.position.x ||
|
||||
newLayout.position.y !== oldLayout.position.y
|
||||
const resized =
|
||||
newLayout.size.width !== oldLayout.size.width ||
|
||||
newLayout.size.height !== oldLayout.size.height
|
||||
|
||||
// Only update from cache on move-only changes.
|
||||
// On resizes (or move+resize), let ResizeObserver remeasure slots accurately.
|
||||
if (moved && !resized) {
|
||||
updateNodeSlotsFromCache(nodeId)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ flush: 'post' }
|
||||
@@ -197,3 +239,10 @@ export function useSlotElementTracking(options: {
|
||||
remeasure: () => scheduleNodeMeasure(nodeId)
|
||||
}
|
||||
}
|
||||
|
||||
export function remeasureNodeSlotsNow(
|
||||
nodeId: string,
|
||||
origin?: { left: number; top: number }
|
||||
) {
|
||||
measureNodeSlotsNow(nodeId, origin?.left, origin?.top)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||
import type { Point } from '@/renderer/core/layout/types'
|
||||
import type { Bounds, NodeId } from '@/renderer/core/layout/types'
|
||||
|
||||
import { remeasureNodeSlotsNow } from './useSlotElementTracking'
|
||||
|
||||
// Per-element conversion context
|
||||
const elementConversion = new WeakMap<
|
||||
HTMLElement,
|
||||
@@ -65,6 +67,8 @@ const trackingConfigs: Map<string, ElementTrackingConfig> = new Map([
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
// Group updates by type, then flush via each config's handler
|
||||
const updatesByType = new Map<string, ElementBoundsUpdate[]>()
|
||||
// Track nodes whose slots should be remeasured after node size changes
|
||||
const nodesNeedingSlotRemeasure = new Set<string>()
|
||||
|
||||
// Read container origin once per batch to avoid repeated layout reads
|
||||
const container = document.getElementById('graph-canvas-container')
|
||||
@@ -127,6 +131,11 @@ const resizeObserver = new ResizeObserver((entries) => {
|
||||
updatesByType.set(elementType, updates)
|
||||
}
|
||||
updates.push({ id: elementId, bounds })
|
||||
|
||||
// If this entry is a node, mark it for slot remeasure
|
||||
if (elementType === 'node' && elementId) {
|
||||
nodesNeedingSlotRemeasure.add(elementId)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush per-type
|
||||
@@ -134,6 +143,13 @@ const resizeObserver = new ResizeObserver((entries) => {
|
||||
const config = trackingConfigs.get(type)
|
||||
if (config && updates.length) config.updateHandler(updates)
|
||||
}
|
||||
|
||||
// After node bounds are updated, refresh slot cached offsets and layouts
|
||||
if (nodesNeedingSlotRemeasure.size > 0) {
|
||||
for (const nodeId of nodesNeedingSlotRemeasure) {
|
||||
remeasureNodeSlotsNow(nodeId, { left: originLeft, top: originTop })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user