Keep the Sound tab smooth while it pans (#360)
game-maker / gate (push) Successful in 3m51s
game-maker / suite (push) Successful in 15m55s

Panning the Sound tab drew the whole wave anew on every move, one
filled rectangle per pixel column, and gave the ruler's labels new
texts whenever a tick left the area. At 2560 px across that took the
tab down to 16 fps.

- The wave is drawn half the area's width past either side of it. A
  pan within that moves the drawing. Zoom, height, size or a pan
  past the margin draw it anew.
- The bars of the wave share one fill instead of one fill each.
- A ruler label keeps the second it already shows. Before, a pan
  shifted every label by one and each had to be rendered anew.
- A clip's name is given its style only when the style changes.
- The rule for what a drawing covers is pure in `waveView.ts`
  (`shownSeconds`, `drawnSeconds`, `coversShown`).

Measured with a copy of the user's project, "Take 1" with 4 clips,
240 pan moves with the middle button, headless on stein2:

- 2560x1440: main thread 712 -> 373 ms/s, texts rendered 694 -> 65
- 1280x800: main thread 429 -> 225 ms/s

What is left in the profile is Pixi's render pass and the pointer
events, which hovering pays as well.

Tests:
- Unit wave-view: 12 green (1 new), counter-probe red
- e2e 34 (stein2, melektaus-atx was down): ALL PASSED, 92 checks
  (1 new). Counter-probe with the labels given out by position: red,
  7 labels set anew instead of 1.
- Gate (stein2): 272 files, 4210 tests green

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-09 06:19:05 +02:00
1 parent 490fcba88f
commit cf96442835
5 files changed
+160 -15

No files matched your search

+20
View File
@@ -5,7 +5,9 @@
import { describe, expect, it } from 'vitest';
import {
clampWave,
coversShown,
draggedSpan,
drawnSeconds,
edgeAt,
fileReach,
fitWave,
@@ -15,6 +17,7 @@ import {
rulerLabel,
rulerStep,
secondsToX,
shownSeconds,
spanOf,
spanPeak,
storedCut,
@@ -83,6 +86,23 @@ describe('the wave view', () => {
expect(rulerLabel(0.25, 0.05)).toBe('0.25s');
expect(rulerLabel(75, 5)).toBe('1:15');
});
it('draws the wave past the area by a margin, and a pan within it keeps that drawing (#360)', () => {
// 20 s at 100 px/s, 1000 px across: seconds 5 to 15 shown, 0 to 20 with 500 px either side.
const view = { panX: -500, pxPerSecond: 100 };
expect(shownSeconds(view, 20, 1000)).toEqual({ start: 5, end: 15 });
const drawn = drawnSeconds(view, 20, 1000, 500);
expect(drawn).toEqual({ start: 0, end: 20 });
const near = drawnSeconds({ ...view, panX: -1000 }, 20, 1000, 300);
expect(near).toEqual({ start: 7, end: 20 });
// Panned 200 px it still covers what is shown, 400 px past it no longer.
expect(coversShown(near, shownSeconds({ ...view, panX: -800 }, 20, 1000))).toBe(true);
expect(coversShown(near, shownSeconds({ ...view, panX: -600 }, 20, 1000))).toBe(false);
// Nothing of the file in view: nothing to cover, and nothing drawn.
expect(shownSeconds({ panX: 1200, pxPerSecond: 100 }, 20, 1000)).toBeNull();
expect(coversShown(null, null)).toBe(true);
expect(coversShown(null, { start: 0, end: 1 })).toBe(false);
});
});
describe('the cut span', () => {