Halve the frame time of a playing scene with a tilemap (#284)
The performance panel showed Physics and Editor as the large parts of a frame in a scene of 222 entities. Nearly all of it came from a tilemap that only stands there. - Whoever writes cells in place now says so with cellsChanged. Only then does cellsVersion compare the content. The comparison ran about 60 times per frame and was the largest single item at 14 percent. - The four writers call it: the painter during and at the end of a stroke, PaintTilesCommand on execute and on undo. A new writer that forgets it stays invisible. - The game view is drawn once per frame in play. With an animated tile in view it was drawn twice, once by the run and once by animate(). animate() now gets the redraw count from the start of the tick. - TilemapField builds the geometry of a mesh only when its source changes: the cells, the tile size, the origin or the image. For that groupByTile and layerRows return the same objects for the same list. - PhysicsSystem skips a tilemap whose source and switch are unchanged. The switch is kept because a map that starts switched off would otherwise have dead colliders once it is switched on. - World.syncToModel looks entities and assets up in a map per pass instead of a linear search per object. Measured on stein2 with a copy of the reference project, ms per frame: Physics 2.64 to 0.97, Drawing 6.89 to 2.56, Editor 1.95 to 1.31. Left open: syncToModel still mirrors every entity every frame. That is its own issue. Tests: - Gate: 198 files, 2887 tests (2883 + 4), exit 0 - e2e 1-17,28,33,39-43,59: 810 checks passed, 40 has +3 - Smokes: 24 / 19 / 21, exit 0 - Three unit tests wrote cells directly and now call cellsChanged - Eleven counter-probes fail. Two did not at first: the stroke needed a check of its own, the physics test has to start with the map switched off. That last one was repeated on stein2 only. - e2e total 1613 is computed, not measured in a full run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6491888f56
commit
cfd288c4b7
15 files changed
+303
-43
No files matched your search
@@ -915,6 +915,8 @@ export class Editor implements PlayHost {
|
||||
private tick(dt: number): void {
|
||||
const started = performance.now();
|
||||
const drawnBefore = this.gameView.drawTime;
|
||||
const sceneDraws = this.viewport.redrawCount;
|
||||
const gameDraws = this.gameView.redrawCount;
|
||||
this.measureFps(dt);
|
||||
// One step for everything below: a world gets exactly the seconds the particle, shader and
|
||||
// animation clocks moved, so the global time scale cannot pull them apart.
|
||||
@@ -922,8 +924,8 @@ export class Editor implements PlayHost {
|
||||
const step = gameClock.advance(dt, this.state.paused || this.play.isGamePaused);
|
||||
this.play.tick(step, timeStep(dt, gameClock.scale, this.state.paused));
|
||||
this.editorRun.tick(step);
|
||||
this.viewport.animate();
|
||||
this.gameView.animate();
|
||||
this.viewport.animate(sceneDraws);
|
||||
this.gameView.animate(gameDraws);
|
||||
this.console.refresh();
|
||||
if (this.tab === 'animation') this.animationEditor.tick(step);
|
||||
if (this.tab === 'tile') this.tileEditor.tick(step);
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { TilemapAsset, TilemapLayer } from '../../core/model/Asset';
|
||||
import { apply as applyPoint, invert } from '../../core/model/matrix';
|
||||
import type { Vec2 } from '../../core/model/Properties';
|
||||
import { findAssetOf } from '../../core/model/Project';
|
||||
import { cellsChanged } from '../../core/model/tilemapMemo';
|
||||
import {
|
||||
cellAt,
|
||||
cellAtPoint,
|
||||
@@ -205,7 +206,9 @@ export class TilemapPainter {
|
||||
else delete layer.cells[key];
|
||||
changed = true;
|
||||
}
|
||||
if (changed) this.perFrame.request();
|
||||
if (!changed) return;
|
||||
cellsChanged(layer.cells);
|
||||
this.perFrame.request();
|
||||
}
|
||||
|
||||
/** Puts the preview back and lets the command do the edit, so one stroke is one undo step. */
|
||||
@@ -215,11 +218,13 @@ export class TilemapPainter {
|
||||
this.perFrame.cancel();
|
||||
if (!stroke) return;
|
||||
const layer = this.strokeLayer(stroke);
|
||||
if (layer)
|
||||
if (layer) {
|
||||
for (const [key, value] of stroke.previous) {
|
||||
if (value) layer.cells[key] = value;
|
||||
else delete layer.cells[key];
|
||||
}
|
||||
cellsChanged(layer.cells);
|
||||
}
|
||||
const item = this.host.items().find((i) => i.id === stroke.objectId);
|
||||
const edits =
|
||||
stroke.tool === 'rect' && item
|
||||
|
||||
Reference in new issue
Block a user