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>
46 lines
1.2 KiB
TypeScript
46 lines
1.2 KiB
TypeScript
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);
|
||
}
|
||
}
|