Files
feuerwerk/src/editor/framePreview.ts
T
adminandClaude Opus 5.5 cb3183227b Keep drags at full speed in large projects (#394)
Dragging a band of a Filter or a Parametric EQ ran at about 30 fps
in a large project. Each frame of a drag announces the changed
project, and every listener of that event ran, also those whose work
nobody sees while the drag goes on.

- FramePreview marks its own frame, read through isDragFrame().
- The Scene view behind another tab skips such a frame and draws on
  coming back, as it already did while a game runs (#265).
- preloadProjectFiles skips it as well. It walks every asset and
  checks every bake, which costs milliseconds in a large project.
- The command that ends a drag emits for itself, and a drag that
  changes nothing ends with a plain preview, so both catch up then.
- This holds for every drag through FramePreview, the knobs of the
  devices in the graph included.

Measured on stein2 with a copy of a project of 162 assets, a band of
a Parametric EQ dragged at 250 pointer moves a second for 3 s:

- Listeners per frame: 17 ms before, 7.4 ms after.
- Hidden Scene view: 814 ms before, 53 ms after.
- preloadProjectFiles: 514 ms before, 0.4 ms after.
- What is left is mostly the Hierarchy and Assets panels on screen
  (about 3 and 1.3 ms a frame). They stay as they are.

Tests:

- Unit frame-preview, play-throttle: 29 passed (1 new)
- Counter-check: without the mark the new test fails
- e2e 34 (melektaus-atx): ALL PASSED, 136 checks
- Gate (melektaus-atx): CHECK EXIT 0, 288 files, 4417 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:45:59 +02:00

46 lines
1.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { cancelFrameWork, onFrame } from '../ui/render';
import type { EditorState } from './state';
let instances = 0;
let inDragFrame = false;
/**
* Whether the `project` event going on is a drag's frame. What nobody sees during it – a hidden view,
* files to load ahead – may wait for the command that ends the drag (#394).
*/
export function isDragFrame(): boolean {
return inDragFrame;
}
/**
* A drag's `state.preview()`, at most once per frame (#213). The gesture still writes the model on
* every pointer move; only the event that rebuilds panels, the run in the editor and generated
* pictures waits for the frame, which a mouse reporting faster than the screen would otherwise
* outrun (#129). `cancel` on release: the command writes the end state and emits for itself.
*/
export class FramePreview {
private readonly key: string;
constructor(
private readonly state: Pick<EditorState, 'preview'>,
name: string,
) {
this.key = `${name}:${(instances += 1)}`;
}
request(): void {
onFrame(this.key, () => {
inDragFrame = true;
try {
this.state.preview();
} finally {
inDragFrame = false;
}
});
}
cancel(): void {
cancelFrameWork(this.key);
}
}