Dragging previews once per frame instead of on every move (#213)
game-maker / gate (push) Successful in 2m52s
game-maker / suite (push) Successful in 8m43s

Scene view, texture tab and graph called state.preview() on every
pointer move. Each call rebuilds panels, the editor run and generated
images, so a drag in a busy project cost up to 34 ms per move. The
model is still written on every move. Only the preview now waits for
the next frame.

- New FramePreview in src/editor/framePreview.ts. request() goes into
  the move handler, cancel() into the release. The rule lives in one
  place instead of six copies.
- Scene view: object drag, polygon corners and tilemap strokes use it.
- Texture tab: frames, slice edges and collision corners use it.
- Graph: nodes, groups and comments use it through GraphDrags. That
  covers the state machine, shader and particle tabs as well.
- Vector art is unchanged. Its frame already writes the model since
  #129.

Findings:
- The texture tab paid twice per move (232 project events for 120
  moves). Pixi sends a second pointer event 4 to 8 ms after each real
  move as a hover update. It now lands in the same frame for free.
- Inspector scrubbing and gradient stop dragging still preview on
  every input. Both lie outside the three canvases of the issue.

Measured with a throwaway probe, 120 moves at 8 ms, 120 extra objects:
scene 26.2 -> 18.5 ms/move, texture 34.2 -> 17.9, graph 28.8 -> 17.0.
Project events dropped from 120/232/119 to 48/48/41.

Tests:
- ./check.sh: 147 files, 2204 tests, exit 0 (before 146 / 2199).
- 5 new unit tests in tests/editor/frame-preview.test.ts, including
  the real GraphDrags. Counter-checks (direct preview in the graph,
  no cancel on release, one key per name) each fail the expected test.
- e2e 1-16,29,31,36,37,39-43: 708 checks passed.
- e2e 1-16,26,28: 634 checks passed.
- No new e2e check, the check sum stays at 1314.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-09-23 11:08:11 +02:00
1 parent f9d5e8bca5
commit 30fb3a2316
7 files changed
+182 -10

No files matched your search

+10 -3
View File
@@ -11,6 +11,7 @@ import {
travellers,
type Travellers,
} from './annotationDrag';
import { FramePreview } from '../framePreview';
import type { AnnotationView } from './AnnotationView';
import type { GraphCanvasHost } from './graphHost';
import { GRID_STEP } from './graphView';
@@ -36,7 +37,11 @@ export class GraphDrags {
moved: boolean;
} | null = null;
constructor(private readonly host: GraphCanvasHost) {}
private readonly perFrame: FramePreview;
constructor(private readonly host: GraphCanvasHost) {
this.perFrame = new FramePreview(host.state, 'graph-drag');
}
/** A press on a node: the selection travels with it. False when there is no graph to move in. */
grabNodes(global: Point): boolean {
@@ -95,7 +100,7 @@ export class GraphDrags {
const step = this.host.snapping() ? GRID_STEP : 0;
// Each thing snaps for itself, node or box, so what lands on the grid is what was grabbed.
placeTravellers(graph, drag.along, (start) => dragOffset(start, dx, dy, step));
this.host.state.preview();
this.perFrame.request();
}
/** Moves or resizes the dragged box in the model, for the preview; committed on release. */
@@ -118,7 +123,7 @@ export class GraphDrags {
const offset = dragOffset(drag.grabbed.start, dx, dy, step);
placeTravellers(graph, drag.along, () => offset);
}
this.host.state.preview();
this.perFrame.request();
}
/** Puts the previewed boxes back and records the whole gesture as one undo step. False when no box was dragged. */
@@ -126,6 +131,7 @@ export class GraphDrags {
const drag = this.box;
if (!drag) return false;
this.box = null;
this.perFrame.cancel();
const graph = this.host.asset()?.graph;
const assetId = this.host.assetId();
if (!drag.moved || !graph || !assetId) return true;
@@ -140,6 +146,7 @@ export class GraphDrags {
releaseNodes(): void {
const drag = this.nodes;
this.nodes = null;
this.perFrame.cancel();
const graph = this.host.asset()?.graph;
const assetId = this.host.assetId();
if (!drag?.moved || !graph || !assetId) return;