/** * The performance overlay (#275): the meter averages each area per frame over half a second, the * editor's share is what its tick took beyond the systems and the drawing, and the world reports * each system it ticked – and only those. */ import { describe, expect, it } from 'vitest'; import { FrameMeter, performanceLines, possibleFps } from '../../src/editor/frameMeter'; import type { System } from '../../src/runtime/systems/System'; import { World } from '../../src/runtime/World'; import { makeSpriteScene } from '../helpers'; /** One frame of 0.1 s: behaviors 2 ms, physics 1 ms, the tick 10 ms in all, 3 ms of it drawing. */ function frame(meter: FrameMeter): boolean { meter.addSystem('behaviors', 2); meter.addSystem('physics', 1); return meter.endFrame(0.1, 10, 3, 0.5); } describe('FrameMeter', () => { it('has nothing to show before half a second has passed', () => { const meter = new FrameMeter(); for (let i = 0; i < 4; i++) expect(frame(meter)).toBe(false); expect(meter.average).toBeNull(); }); it('averages every area per frame over the window', () => { const meter = new FrameMeter(); for (let i = 0; i < 4; i++) frame(meter); meter.addSystem('behaviors', 7); meter.addSystem('animation', 0.5); expect(meter.endFrame(0.1, 12, 3, 0.5)).toBe(true); expect(meter.average).toEqual({ behaviors: (4 * 2 + 7) / 5, physics: (4 * 1) / 5, animation: 0.5 / 5, drawing: 3.5, // 10 - 3 - 3 in the first four frames, 12 - 7.5 - 3 in the last. editor: (4 * 4 + 1.5) / 5, }); }); it('starts the next window from zero', () => { const meter = new FrameMeter(); for (let i = 0; i < 5; i++) frame(meter); for (let i = 0; i < 4; i++) meter.endFrame(0.1, 1, 0, 0); expect(meter.endFrame(0.1, 1, 0, 0)).toBe(true); expect(meter.average).toEqual({ behaviors: 0, physics: 0, animation: 0, drawing: 0, editor: 1 }); }); it('never gives the editor less than nothing', () => { const meter = new FrameMeter(); meter.addSystem('physics', 5); meter.endFrame(0.5, 4, 1, 0); expect(meter.average?.editor).toBe(0); }); it('forgets everything on reset', () => { const meter = new FrameMeter(); for (let i = 0; i < 5; i++) frame(meter); meter.addSystem('behaviors', 100); meter.reset(); expect(meter.average).toBeNull(); for (let i = 0; i < 4; i++) meter.endFrame(0.1, 1, 0, 0); meter.endFrame(0.1, 1, 0, 0); expect(meter.average?.behaviors).toBe(0); }); }); describe('performanceLines', () => { it('shows dashes until there is something measured', () => { expect(performanceLines({ fps: null, entities: 1, times: null })).toEqual([ 'Possible – fps', '– fps · 1 entity', 'Behaviors – ms', 'Physics – ms', 'Animation – ms', 'Drawing – ms', 'Editor – ms', ]); }); it('lines the numbers up in milliseconds with two decimals', () => { const times = { behaviors: 1.234, physics: 12.5, animation: 0, drawing: 3, editor: 0.006 }; expect(performanceLines({ fps: 60, entities: 128, times })).toEqual([ 'Possible 59 fps', '60 fps · 128 entities', 'Behaviors 1.23 ms', 'Physics 12.50 ms', 'Animation 0.00 ms', 'Drawing 3.00 ms', 'Editor 0.01 ms', ]); }); }); describe('possibleFps', () => { it('is a second over what a frame takes in all areas, rounded down', () => { expect(possibleFps({ behaviors: 0.5, physics: 0.25, animation: 0, drawing: 0.75, editor: 0.5 })).toBe( 500, ); expect(possibleFps({ behaviors: 0.1, physics: 0.1, animation: 0.1, drawing: 0.1, editor: 0.3 })).toBe( 1428, ); }); it('has no number while nothing took any time', () => { expect(possibleFps({ behaviors: 0, physics: 0, animation: 0, drawing: 0, editor: 0 })).toBeNull(); }); }); describe('World.timeSystem', () => { function counting(runsWhilePaused = false): System & { runs: number } { return { runs: 0, runsWhilePaused, update() { this.runs++; }, }; } it('reports every system it ticks, once a tick', () => { const f = makeSpriteScene(); const world = World.instantiate(f.project, f.sceneId); const a = counting(); const b = counting(); world.addSystem(a).addSystem(b); const told: System[] = []; world.timeSystem = (system, ms) => { expect(ms).toBeGreaterThanOrEqual(0); told.push(system); }; world.tick(1 / 60); world.tick(1 / 60); expect(told).toEqual([a, b, a, b]); expect(a.runs).toBe(2); }); it('does not report a system a paused frame skips', () => { const f = makeSpriteScene(); const world = World.instantiate(f.project, f.sceneId); const still = counting(); const awake = counting(true); world.addSystem(still).addSystem(awake); const told: System[] = []; world.timeSystem = (system) => told.push(system); world.paused = true; world.tick(1 / 60); expect(told).toEqual([awake]); expect(still.runs).toBe(0); }); });