| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283 |
- // Terrain budget benchmark under plain Node (readme §5: an op + flood fill + colliders in 2 ms).
- // Usage: npm run bench:node
- // The vitest benchmarks run through the test runner's module transform, which slows hot loops
- // that read imported bindings; this script measures what the server actually runs.
- import { World } from 'planck';
- import { loadCharacterStats, loadMap, loadMaterials, resolveAssetsDir } from '../src/server/assets';
- import { Game } from '../src/server/game';
- import { TerrainColliders } from '../src/server/physics/terrainColliders';
- import { Rng } from '../src/shared/rng';
- import { DetachFinder } from '../src/shared/terrain/detach';
- import { applyCells } from '../src/shared/terrain/events';
- import { applyOp } from '../src/shared/terrain/ops';
- const assets = resolveAssetsDir();
- const materials = loadMaterials(assets);
- const WARMUP = 3;
- function report(label: string, samples: number[]): void {
- const warm = samples.slice(WARMUP).sort((a, b) => a - b);
- const pick = (q: number): string => warm[Math.min(warm.length - 1, Math.floor(warm.length * q))].toFixed(2);
- const mean = warm.reduce((a, b) => a + b, 0) / warm.length;
- console.log(`${label.padEnd(44)} mean ${mean.toFixed(2)} median ${pick(0.5)} p99 ${pick(0.99)} max ${pick(1)} ms`);
- }
- function newGame(): Game {
- const { definition, terrain } = loadMap(assets, 'gateau-etages', materials);
- return new Game({ map: definition, terrain, materials, seed: 1, loadCharacter: (id) => loadCharacterStats(assets, id) });
- }
- // 1. Ordinary ops on a cake turning into Swiss cheese: op + detach search + colliders + step.
- {
- const { terrain } = loadMap(assets, 'gateau-etages', materials);
- const world = new World();
- const colliders = new TerrainColliders(world, terrain);
- colliders.rebuildDirty();
- const finder = new DetachFinder(terrain);
- const rng = new Rng(7);
- const samples: number[] = [];
- for (let i = 0; i < 1500; i++) {
- const t = performance.now();
- const { changed } = applyOp(terrain, { kind: 'carve', x: rng.int(300, 1700), y: rng.int(300, 900), r: 40, power: 99 }, materials);
- if (changed) {
- for (const c of finder.find(changed)) {
- const { x0, y0, x1, y1 } = c.bounds;
- applyCells(terrain, 'detach', x0, y0, x1 - x0, y1 - y0, c.mask);
- }
- }
- colliders.rebuildDirty();
- world.step(1 / 60);
- samples.push(performance.now() - t);
- }
- report('carve r=40 (op + detach + colliders + step)', samples.slice(200 - WARMUP));
- }
- // 2. One op severing the last link of the top tier (~90k px come loose): the worst single op.
- {
- const samples: number[] = [];
- for (let round = 0; round < WARMUP + 10; round++) {
- const game = newGame();
- for (let x = 760; x <= 1250; x += 12) {
- game.queueOp({ kind: 'carve', x, y: 462, r: 14, power: 99 });
- game.step();
- }
- game.queueOp({ kind: 'carve', x: 1268, y: 462, r: 16, power: 99 });
- const t = performance.now();
- game.step();
- samples.push(performance.now() - t);
- }
- report('single op severing a whole tier (full tick)', samples);
- }
- // 3. A 44-op cut in a single tick (e.g. several players at once).
- {
- const samples: number[] = [];
- for (let round = 0; round < WARMUP + 10; round++) {
- const game = newGame();
- for (let x = 760; x <= 1278; x += 12) game.queueOp({ kind: 'carve', x, y: 462, r: 14, power: 99 });
- const t = performance.now();
- game.step();
- samples.push(performance.now() - t);
- }
- report('44-op cut in one tick (full tick)', samples);
- }
|