Speed: Remove some optimizations that weren't optimizing (#6209)

## Summary

Simplify the TransformPane.

## Changes

- **What**: Remove the settling and culling composables. Gets rid of a
frequent event emission and some event handling addition/removals.

## Review Focus

In testing with a huge workflow in Vue mode, it was a lot faster without
these than with.
Can you check to see if you experience the same benefits?

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6209-Speed-Remove-some-optimizations-that-weren-t-optimizing-2946d73d3650815197a4df3c58a61575)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Alexander Brown
2025-12-02 21:28:52 -08:00
committed by GitHub
parent 2bf45f23dc
commit 662974b222
8 changed files with 2 additions and 477 deletions

View File

@@ -115,89 +115,6 @@ describe('TransformPane', () => {
const transformState = useTransformState()
expect(transformState.syncWithCanvas).toHaveBeenCalledWith(mockCanvas)
})
it('should emit transform update timing', async () => {
const mockCanvas = createMockCanvas()
const wrapper = mount(TransformPane, {
props: {
canvas: mockCanvas
}
})
await nextTick()
// Allow RAF to execute
vi.advanceTimersToNextFrame()
expect(wrapper.emitted('transformUpdate')).toBeTruthy()
})
})
describe('canvas event listeners', () => {
it('should add event listeners to canvas on mount', async () => {
const mockCanvas = createMockCanvas()
mount(TransformPane, {
props: {
canvas: mockCanvas
}
})
await nextTick()
expect(mockCanvas.canvas.addEventListener).toHaveBeenCalledWith(
'wheel',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.addEventListener).not.toHaveBeenCalledWith(
'pointerdown',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.addEventListener).not.toHaveBeenCalledWith(
'pointerup',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.addEventListener).not.toHaveBeenCalledWith(
'pointercancel',
expect.any(Function),
expect.any(Object)
)
})
it('should remove event listeners on unmount', async () => {
const mockCanvas = createMockCanvas()
const wrapper = mount(TransformPane, {
props: {
canvas: mockCanvas
}
})
await nextTick()
wrapper.unmount()
expect(mockCanvas.canvas.removeEventListener).toHaveBeenCalledWith(
'wheel',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.removeEventListener).not.toHaveBeenCalledWith(
'pointerdown',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.removeEventListener).not.toHaveBeenCalledWith(
'pointerup',
expect.any(Function),
expect.any(Object)
)
expect(mockCanvas.canvas.removeEventListener).not.toHaveBeenCalledWith(
'pointercancel',
expect.any(Function),
expect.any(Object)
)
})
})
describe('interaction state management', () => {
@@ -214,9 +131,7 @@ describe('TransformPane', () => {
const transformPane = wrapper.find('[data-testid="transform-pane"]')
// Initially should not have interacting class
expect(transformPane.classes()).not.toContain(
'transform-pane--interacting'
)
expect(transformPane.classes()).not.toContain('will-change-transform')
})
it('should handle pointer events for node delegation', async () => {

View File

@@ -1,12 +1,7 @@
<template>
<div
data-testid="transform-pane"
:class="
cn(
'absolute inset-0 w-full h-full pointer-events-none',
isInteracting ? 'transform-pane--interacting' : 'will-change-auto'
)
"
class="absolute inset-0 w-full h-full pointer-events-none"
:style="transformStyle"
>
<!-- Vue nodes will be rendered here -->
@@ -16,12 +11,9 @@
<script setup lang="ts">
import { useRafFn } from '@vueuse/core'
import { computed } from 'vue'
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import { useTransformSettling } from '@/renderer/core/layout/transform/useTransformSettling'
import { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
import { cn } from '@/utils/tailwindUtil'
interface TransformPaneProps {
canvas?: LGraphCanvas
@@ -31,29 +23,13 @@ const props = defineProps<TransformPaneProps>()
const { transformStyle, syncWithCanvas } = useTransformState()
const canvasElement = computed(() => props.canvas?.canvas)
const { isTransforming: isInteracting } = useTransformSettling(canvasElement, {
settleDelay: 512
})
const emit = defineEmits<{
transformUpdate: []
}>()
useRafFn(
() => {
if (!props.canvas) {
return
}
syncWithCanvas(props.canvas)
emit('transformUpdate')
},
{ immediate: true }
)
</script>
<style scoped>
.transform-pane--interacting {
will-change: transform;
}
</style>

View File

@@ -1,84 +0,0 @@
import { useDebounceFn, useEventListener } from '@vueuse/core'
import { ref } from 'vue'
import type { MaybeRefOrGetter } from 'vue'
interface TransformSettlingOptions {
/**
* Delay in ms before transform is considered "settled" after last interaction
* @default 200
*/
settleDelay?: number
/**
* Whether to use passive event listeners (better performance but can't preventDefault)
* @default true
*/
passive?: boolean
}
/**
* Tracks when canvas zoom transforms are actively changing vs settled.
*
* This composable helps optimize rendering quality during zoom transformations.
* When the user is actively zooming, we can reduce rendering quality
* for better performance. Once the transform "settles" (stops changing), we can
* trigger high-quality re-rasterization.
*
* The settling concept prevents constant quality switching during interactions
* by waiting for a period of inactivity before considering the transform complete.
*
* Uses VueUse's useEventListener for automatic cleanup and useDebounceFn for
* efficient settle detection.
*
* @example
* ```ts
* const { isTransforming } = useTransformSettling(canvasRef, {
* settleDelay: 200
* })
*
* // Use in CSS classes or rendering logic
* const cssClass = computed(() => ({
* 'low-quality': isTransforming.value,
* 'high-quality': !isTransforming.value
* }))
* ```
*/
export function useTransformSettling(
target: MaybeRefOrGetter<HTMLElement | null | undefined>,
options: TransformSettlingOptions = {}
) {
const { settleDelay = 200, passive = true } = options
const isTransforming = ref(false)
/**
* Mark transform as active
*/
const markTransformActive = () => {
isTransforming.value = true
}
/**
* Mark transform as settled (debounced)
*/
const markTransformSettled = useDebounceFn(() => {
isTransforming.value = false
}, settleDelay)
/**
* Handle zoom transform event - mark active then queue settle
*/
const handleWheel = () => {
markTransformActive()
void markTransformSettled()
}
// Register wheel event listener with auto-cleanup
useEventListener(target, 'wheel', handleWheel, {
capture: true,
passive
})
return {
isTransforming
}
}