perf(imageCaption): single global draw hook + viewport culling + change detection#84
Open
1756141021 wants to merge 1 commit into
Open
perf(imageCaption): single global draw hook + viewport culling + change detection#841756141021 wants to merge 1 commit into
1756141021 wants to merge 1 commit into
Conversation
…ge detection Fixes yawiii#83. Three optimizations to imageCaption position update path; the canvas-pan FPS hit reported in yawiii#83 was a combination of all three. 1. Single global onDrawBackground hook Before: every assistant wrapped app.canvas.onDrawBackground individually, producing N nested function calls per frame in a workflow with N visible assistants. The cleanup function could only restore the topmost wrapper, so once another wrap was added on top, the lower one became permanent and the chain grew monotonically. After: a single class-level wrap iterates ImageCaption._activePositionUpdaters once per frame. Each assistant adds its updater to the Set on setup, removes it on cleanup. Zero per-assistant wrap accumulation. 2. Viewport culling in updatePosition Before: updatePosition computed bounding box → screen coords → DOM writes for every assistant every frame, even when the owning node was far outside the visible canvas area. After: early-return + display:none if node bounds don't intersect canvas.ds.visible_area. Skips both the layout math and the DOM write for off-screen assistants. 3. Change detection Before: every frame wrote to containerDiv.style.left/bottom/setProperty regardless of whether values changed, forcing browser Recalculate Style on every assistant per frame. After: cache last applied screen position and scale; only write to DOM when one of those changed. Tested on a ~200-node workflow at canvas scale 0.05 (whole graph visible): - Before: 14 fps, 179 ms worst frame, Recalculate Style ~68% of frame budget - After: matches the no-Prompt-Assistant baseline The behavior is equivalent for the user; only the performance profile changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #83.
Problem
In a ~200-node workflow at low canvas zoom (whole graph visible), panning drops to ~14 fps with worst frames over 170 ms. DevTools Performance traces this to
imageCaption.jsupdatePositionconsuming ~62% of frame budget and triggering ~68% of frame budget in Recalculate Style.Three layered fixes (all in
js/modules/imageCaption.js)1. Single global onDrawBackground hook
Each assistant currently wraps
app.canvas.onDrawBackgroundindividually. With N assistants this builds an N-deep nested call chain executed every frame. The cleanup function only restores the topmost wrapper, so any wrapper not on top becomes permanent — the chain grows monotonically.Replaced with a class-level Set
ImageCaption._activePositionUpdatersand a single global wrap that iterates the Set once per frame. Each assistant adds its updater on setup, removes it on cleanup. No more per-assistant wrap accumulation.2. Viewport culling in
updatePositionupdatePositionwas running the full bounding-box → screen-coords → DOM-write path for every assistant every frame, even when the owning node was completely off-screen.Added an early-return +
display:nonewhennode.getBounding()does not intersectcanvas.ds.visible_area. Skips both the math and the DOM write for invisible assistants.3. Change detection on style writes
Every frame wrote
style.left,style.bottom, andstyle.setProperty('--assistant-scale', ...)regardless of whether values changed. Each write triggers browser Recalculate Style.Cached the last applied screen X/Y and scale; writes to DOM only happen when one of those changed.
Behavior
User-visible behavior is unchanged — assistants follow their nodes exactly as before. Only the per-frame cost changes.
Measurements (200-node workflow, canvas scale ~0.05)
After-numbers match the baseline measured with the plugin uninstalled.
Notes
onNodeMoved,ds.onModified) have a similar wrap-chain pattern and could get the same treatment in a follow-up; this PR focuses on the dominant draw-hook path called from Severe canvas pan FPS drop at low zoom: imageCaption updatePosition runs per-instance per frame, no viewport culling #83