mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 19:21:54 +00:00
[feat] Implement callback-driven widget updates
- Chain widget callbacks to trigger immediate Vue state updates - Remove need for RAF polling of widget values - Add performance tracking for callback vs RAF updates - Implement proper FPS tracking with 1-second intervals This change makes widget updates reactive and immediate rather than waiting for the next RAF cycle, improving responsiveness.
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import type { LGraph, LGraphNode } from '@comfyorg/litegraph'
|
import type { LGraph, LGraphNode } from '@comfyorg/litegraph'
|
||||||
import { nextTick, reactive, readonly } from 'vue'
|
import { nextTick, reactive, readonly } from 'vue'
|
||||||
|
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||||
|
|
||||||
export interface NodeState {
|
export interface NodeState {
|
||||||
visible: boolean
|
visible: boolean
|
||||||
@@ -20,10 +21,13 @@ export interface NodeMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PerformanceMetrics {
|
export interface PerformanceMetrics {
|
||||||
|
fps: number
|
||||||
frameTime: number
|
frameTime: number
|
||||||
updateTime: number
|
updateTime: number
|
||||||
nodeCount: number
|
nodeCount: number
|
||||||
culledCount: number
|
culledCount: number
|
||||||
|
callbackUpdateCount: number
|
||||||
|
rafUpdateCount: number
|
||||||
adaptiveQuality: boolean
|
adaptiveQuality: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,13 +96,51 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
|
|||||||
|
|
||||||
// Performance tracking
|
// Performance tracking
|
||||||
const performanceMetrics = reactive<PerformanceMetrics>({
|
const performanceMetrics = reactive<PerformanceMetrics>({
|
||||||
|
fps: 0,
|
||||||
frameTime: 0,
|
frameTime: 0,
|
||||||
updateTime: 0,
|
updateTime: 0,
|
||||||
nodeCount: 0,
|
nodeCount: 0,
|
||||||
culledCount: 0,
|
culledCount: 0,
|
||||||
|
callbackUpdateCount: 0,
|
||||||
|
rafUpdateCount: 0,
|
||||||
adaptiveQuality: false
|
adaptiveQuality: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// FPS tracking state
|
||||||
|
let lastFrameTime = performance.now()
|
||||||
|
let frameCount = 0
|
||||||
|
let fpsUpdateInterval: number | null = null
|
||||||
|
|
||||||
|
const updateFPS = () => {
|
||||||
|
frameCount++
|
||||||
|
const now = performance.now()
|
||||||
|
const elapsed = now - lastFrameTime
|
||||||
|
|
||||||
|
if (elapsed >= 1000) {
|
||||||
|
performanceMetrics.fps = Math.round((frameCount * 1000) / elapsed)
|
||||||
|
performanceMetrics.frameTime = elapsed / frameCount
|
||||||
|
frameCount = 0
|
||||||
|
lastFrameTime = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startFPSTracking = () => {
|
||||||
|
if (fpsUpdateInterval) return
|
||||||
|
|
||||||
|
const trackFrame = () => {
|
||||||
|
updateFPS()
|
||||||
|
fpsUpdateInterval = requestAnimationFrame(trackFrame)
|
||||||
|
}
|
||||||
|
fpsUpdateInterval = requestAnimationFrame(trackFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopFPSTracking = () => {
|
||||||
|
if (fpsUpdateInterval) {
|
||||||
|
cancelAnimationFrame(fpsUpdateInterval)
|
||||||
|
fpsUpdateInterval = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update batching
|
// Update batching
|
||||||
const pendingUpdates = new Set<string>()
|
const pendingUpdates = new Set<string>()
|
||||||
const criticalUpdates = new Set<string>()
|
const criticalUpdates = new Set<string>()
|
||||||
@@ -126,6 +168,7 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
|
|||||||
// Extract safe widget data
|
// Extract safe widget data
|
||||||
const safeWidgets = node.widgets?.map((widget) => {
|
const safeWidgets = node.widgets?.map((widget) => {
|
||||||
try {
|
try {
|
||||||
|
// TODO: Use widget.getReactiveData() once TypeScript types are updated
|
||||||
let value = widget.value
|
let value = widget.value
|
||||||
|
|
||||||
// For combo widgets, if value is undefined, use the first option as default
|
// For combo widgets, if value is undefined, use the first option as default
|
||||||
@@ -181,6 +224,32 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
|
|||||||
return nodeRefs.get(id)
|
return nodeRefs.get(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setupNodeWidgetCallbacks = (node: LGraphNode) => {
|
||||||
|
const nodeId = String(node.id)
|
||||||
|
|
||||||
|
node.widgets?.forEach(widget => {
|
||||||
|
widget.callback = useChainCallback(widget.callback, () => {
|
||||||
|
try {
|
||||||
|
const currentData = vueNodeData.get(nodeId)
|
||||||
|
if (currentData?.widgets) {
|
||||||
|
const updatedWidgets = currentData.widgets.map(w =>
|
||||||
|
w.name === widget.name
|
||||||
|
? { ...w, value: widget.value }
|
||||||
|
: w
|
||||||
|
)
|
||||||
|
vueNodeData.set(nodeId, {
|
||||||
|
...currentData,
|
||||||
|
widgets: updatedWidgets
|
||||||
|
})
|
||||||
|
}
|
||||||
|
performanceMetrics.callbackUpdateCount++
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`[useGraphNodeManager] Failed to update Vue state for widget ${widget.name}:`, error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Uncomment when needed for future features
|
// Uncomment when needed for future features
|
||||||
// const getNodeMetadata = (node: LGraphNode): NodeMetadata => {
|
// const getNodeMetadata = (node: LGraphNode): NodeMetadata => {
|
||||||
// let metadata = nodeMetadata.get(node)
|
// let metadata = nodeMetadata.get(node)
|
||||||
@@ -295,8 +364,13 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
|
|||||||
|
|
||||||
// Most performant: Direct position sync without re-setting entire node
|
// Most performant: Direct position sync without re-setting entire node
|
||||||
const detectChangesInRAF = () => {
|
const detectChangesInRAF = () => {
|
||||||
|
const startTime = performance.now()
|
||||||
|
|
||||||
if (!graph?._nodes) return
|
if (!graph?._nodes) return
|
||||||
|
|
||||||
|
let positionUpdates = 0
|
||||||
|
let sizeUpdates = 0
|
||||||
|
|
||||||
// Update reactive positions and sizes
|
// Update reactive positions and sizes
|
||||||
for (const node of graph._nodes) {
|
for (const node of graph._nodes) {
|
||||||
const id = String(node.id)
|
const id = String(node.id)
|
||||||
@@ -309,6 +383,7 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
|
|||||||
currentPos.y !== node.pos[1]
|
currentPos.y !== node.pos[1]
|
||||||
) {
|
) {
|
||||||
nodePositions.set(id, { x: node.pos[0], y: node.pos[1] })
|
nodePositions.set(id, { x: node.pos[0], y: node.pos[1] })
|
||||||
|
positionUpdates++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -317,12 +392,21 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
|
|||||||
currentSize.height !== node.size[1]
|
currentSize.height !== node.size[1]
|
||||||
) {
|
) {
|
||||||
nodeSizes.set(id, { width: node.size[0], height: node.size[1] })
|
nodeSizes.set(id, { width: node.size[0], height: node.size[1] })
|
||||||
|
sizeUpdates++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update performance metrics
|
// Update performance metrics
|
||||||
performanceMetrics.frameTime = performance.now()
|
const endTime = performance.now()
|
||||||
performanceMetrics.updateTime++
|
performanceMetrics.updateTime = endTime - startTime
|
||||||
|
performanceMetrics.nodeCount = vueNodeData.size
|
||||||
|
performanceMetrics.culledCount = Array.from(nodeState.values()).filter(
|
||||||
|
state => state.culled
|
||||||
|
).length
|
||||||
|
|
||||||
|
if (positionUpdates > 0 || sizeUpdates > 0) {
|
||||||
|
performanceMetrics.rafUpdateCount++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setupEventListeners = (): (() => void) => {
|
const setupEventListeners = (): (() => void) => {
|
||||||
@@ -351,6 +435,9 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
|
|||||||
nodePositions.set(id, { x: node.pos[0], y: node.pos[1] })
|
nodePositions.set(id, { x: node.pos[0], y: node.pos[1] })
|
||||||
nodeSizes.set(id, { width: node.size[0], height: node.size[1] })
|
nodeSizes.set(id, { width: node.size[0], height: node.size[1] })
|
||||||
attachMetadata(node)
|
attachMetadata(node)
|
||||||
|
|
||||||
|
setupNodeWidgetCallbacks(node)
|
||||||
|
|
||||||
if (originalOnNodeAdded) {
|
if (originalOnNodeAdded) {
|
||||||
void originalOnNodeAdded(node)
|
void originalOnNodeAdded(node)
|
||||||
}
|
}
|
||||||
@@ -369,6 +456,9 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
|
|||||||
|
|
||||||
// Initial sync
|
// Initial sync
|
||||||
syncWithGraph()
|
syncWithGraph()
|
||||||
|
|
||||||
|
// Start FPS tracking
|
||||||
|
startFPSTracking()
|
||||||
|
|
||||||
// Return cleanup function
|
// Return cleanup function
|
||||||
return () => {
|
return () => {
|
||||||
@@ -381,6 +471,9 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
|
|||||||
clearTimeout(batchTimeoutId)
|
clearTimeout(batchTimeoutId)
|
||||||
batchTimeoutId = null
|
batchTimeoutId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop FPS tracking
|
||||||
|
stopFPSTracking()
|
||||||
|
|
||||||
// Clear state
|
// Clear state
|
||||||
nodeRefs.clear()
|
nodeRefs.clear()
|
||||||
|
|||||||
Reference in New Issue
Block a user