Keep a tilemap with outlined solids at 60 fps in scene and play
A project with three tilemap layers, two of them with collision on water and hill tiles, ran at 33 to 52 fps. A CPU profile showed that almost all of the time went into the collision geometry of the map. It was rebuilt from scratch every frame although nothing had changed. The animated water makes the views redraw every frame, so the cost was never skipped. - New module tilemapMemo keeps what is derived from the cells: the tiles of a layer, the blocking cells, the rectangles and polygons. It is reused as long as the content stays the same. - A brush stroke paints into the existing cells object. The memo therefore compares the content cell by cell, not the identity. The comparison walks two arrays in insertion order. - Clipped outlines are kept per outline list. The model always replaces that list instead of changing it. - PhysicsSystem no longer rebuilds the shapes of a tilemap each tick while its cells, placement and settings are unchanged. Before, it also built a JSON key from all world shapes every tick. - The overlaid layers after Set tile in a run stay stable. Otherwise the memo would miss on every call after the first edit. The #184 work only helped Move and raycasts, not this path. Measured on stein2 (i5-7500T, HD 630), script time per frame: - Scene, nothing selected: 17.7 ms, 52 fps -> 5.8 ms, 60 fps - Tilemap selected: 26.2 ms, 38 fps -> 8.6 ms, 60 fps - Play: 29.9 ms, 33 fps -> 8.7 ms, 60 fps About 3 ms per frame remain. The content comparison costs roughly 60 us per 700-cell layer and runs about 40 times a frame. The mesh geometries in TilemapField are still rebuilt every frame. Tests: - ./check.sh: 162 files, 2377 tests, CHECK EXIT 0 (before 2370) - e2e 1-5 and 39-43: ALL PASSED, 289 checks - 7 new unit tests in tests/core/tilemap-memo.test.ts - 5 counter checks (content ignored, no memo, outline by kind only, physics always rebuilds, layer settings ignored) each fail their test. All originals are back, checked with cmp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
021a6dcb56
commit
03e7a78c3d
10 files changed
+491
-79
No files matched your search
@@ -13,6 +13,7 @@ import { type Neighbours, type TileQuadrants, tileQuadrants } from './autotile';
|
||||
import { type TileKind, tileKind } from './tile';
|
||||
import type { PropertyBag } from './Properties';
|
||||
import { findAssetOf, type Project } from './Project';
|
||||
import { cellsTileIds, cellsVersion, KeyedMemo } from './tilemapMemo';
|
||||
|
||||
export const TILEMAP_COLUMNS_KEY = 'columns';
|
||||
export const TILEMAP_ROWS_KEY = 'rows';
|
||||
@@ -124,17 +125,29 @@ export interface TileDraw {
|
||||
* so the result is the same list twice. Cells naming a deleted tile are skipped rather than
|
||||
* dropped from the model: an undo brings the tile, and with it the drawing, back.
|
||||
*/
|
||||
export function layerTiles(project: Project, props: PropertyBag, layer: TilemapLayer): TileDraw[] {
|
||||
export function layerTiles(project: Project, props: PropertyBag, layer: TilemapLayer): readonly TileDraw[] {
|
||||
const kinds = new Map<string, TileKind>();
|
||||
const kindOf = (id: string): TileKind | null => {
|
||||
const known = kinds.get(id);
|
||||
if (known) return known;
|
||||
for (const id of cellsTileIds(layer.cells)) {
|
||||
const tile: TileAsset | undefined = findAssetOf(project, id, 'tile');
|
||||
if (!tile) return null;
|
||||
const kind = tileKind(tile);
|
||||
kinds.set(id, kind);
|
||||
return kind;
|
||||
};
|
||||
if (tile) kinds.set(id, tileKind(tile));
|
||||
}
|
||||
const key = [
|
||||
tilemapColumns(props),
|
||||
tilemapRows(props),
|
||||
cellsVersion(layer.cells),
|
||||
[...kinds].map(([id, kind]) => `${id}:${kind}`).join(','),
|
||||
].join('|');
|
||||
return drawn.get(key, () => workOutLayerTiles(props, layer, kinds));
|
||||
}
|
||||
|
||||
const drawn = new KeyedMemo<readonly TileDraw[]>();
|
||||
|
||||
function workOutLayerTiles(
|
||||
props: PropertyBag,
|
||||
layer: TilemapLayer,
|
||||
kinds: Map<string, TileKind>,
|
||||
): TileDraw[] {
|
||||
const kindOf = (id: string): TileKind | null => kinds.get(id) ?? null;
|
||||
const tiles: TileDraw[] = [];
|
||||
for (const [key, tileId] of Object.entries(layer.cells)) {
|
||||
const at = parseCellKey(key);
|
||||
|
||||
Reference in new issue
Block a user