From 36ddadd9656e25414dedc8f1d8e10b4ecc58a78c Mon Sep 17 00:00:00 2001 From: Matthias von Bargen Date: Wed, 1 Apr 2026 19:33:05 +0200 Subject: [PATCH] init benchmarking in core. No CI integration for now just locally Co-authored-by: Claude Sonnet 4.6 --- packages/core/package.json | 1 + .../Server/Stats/BandwidthTracker.bench.ts | 43 +++++++++++++ .../core/tests/Networking/syncState.bench.ts | 60 +++++++++++++++++++ .../core/tests/Utils/EventEmitter.bench.ts | 36 +++++++++++ .../core/tests/World/StateManager.bench.ts | 48 +++++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 packages/core/tests/Networking/Server/Stats/BandwidthTracker.bench.ts create mode 100644 packages/core/tests/Networking/syncState.bench.ts create mode 100644 packages/core/tests/Utils/EventEmitter.bench.ts create mode 100644 packages/core/tests/World/StateManager.bench.ts diff --git a/packages/core/package.json b/packages/core/package.json index 6fef71d..1261ec3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -37,6 +37,7 @@ "dev": "rm -f tsconfig.build.tsbuildinfo && tsc -p tsconfig.build.json --watch", "lint": "eslint .", "lint:fix": "eslint . --fix", + "bench": "vitest bench", "test": "vitest", "test:ui": "vitest --ui --coverage.enabled true --coverage.reporter html", "test:coverage": "vitest --coverage.enabled true --coverage.reporter json-summary", diff --git a/packages/core/tests/Networking/Server/Stats/BandwidthTracker.bench.ts b/packages/core/tests/Networking/Server/Stats/BandwidthTracker.bench.ts new file mode 100644 index 0000000..33c6536 --- /dev/null +++ b/packages/core/tests/Networking/Server/Stats/BandwidthTracker.bench.ts @@ -0,0 +1,43 @@ +import { bench, describe } from 'vitest' +import BandwidthTracker from '../../../../src/Networking/Server/Stats/BandwidthTracker' + +describe('BandwidthTracker', () => { + bench('recordSent — single id', () => { + const tracker = new BandwidthTracker() + tracker.recordSent('client-1', 128) + }) + + bench('recordSent — accumulation (same id, 100 calls)', () => { + const tracker = new BandwidthTracker() + for (let i = 0; i < 100; i++) { + tracker.recordSent('client-1', 128) + } + }) + + bench('recordSent — 50 unique ids', () => { + const tracker = new BandwidthTracker() + for (let i = 0; i < 50; i++) { + tracker.recordSent(`client-${i}`, 128) + } + }) + + bench('getBandwidthUsage — existing id', () => { + const tracker = new BandwidthTracker() + tracker.recordSent('client-1', 128) + tracker.getBandwidthUsage('client-1') + }) + + bench('getBandwidthUsage — missing id', () => { + const tracker = new BandwidthTracker() + tracker.getBandwidthUsage('client-1') + }) + + bench('reset — 50 entries', () => { + const tracker = new BandwidthTracker() + for (let i = 0; i < 50; i++) { + tracker.recordSent(`client-${i}`, 128) + tracker.recordReceived(`client-${i}`, 64) + } + tracker.reset() + }) +}) diff --git a/packages/core/tests/Networking/syncState.bench.ts b/packages/core/tests/Networking/syncState.bench.ts new file mode 100644 index 0000000..2189716 --- /dev/null +++ b/packages/core/tests/Networking/syncState.bench.ts @@ -0,0 +1,60 @@ +import type EntityState from '../../src/World/Entity/State' +import { bench, describe } from 'vitest' +import NetworkedActor from '../../src/Networking/NetworkedActor' +import NetworkedEntityState from '../../src/Networking/NetworkedEntityState' +import { syncStateStack } from '../../src/Networking/syncState' + +class TestActor extends NetworkedActor { + $typeName = 'testActor' + updateFromNetwork = (_data: object) => {} + update(_delta: number): void {} +} + +class StateA extends NetworkedEntityState { + readonly stateName = 'stateA' + update(_delta: number): EntityState | void {} +} + +class StateB extends NetworkedEntityState { + readonly stateName = 'stateB' + update(_delta: number): EntityState | void {} +} + +const factories = { + stateA: (entity: TestActor) => new StateA(entity), + stateB: (entity: TestActor) => new StateB(entity), +} + +describe('syncStateStack', () => { + bench('no-op (states already match)', () => { + const actor = new TestActor() + actor.state.push(new StateA(actor)) + syncStateStack(actor, [{ stateName: 'stateA' }], factories) + }) + + bench('full replacement (stateB → stateA)', () => { + const actor = new TestActor() + actor.state.push(new StateB(actor)) + syncStateStack(actor, [{ stateName: 'stateA' }], factories) + }) + + bench('add to stack (1 → 2 states)', () => { + const actor = new TestActor() + actor.state.push(new StateA(actor)) + syncStateStack(actor, [{ stateName: 'stateA' }, { stateName: 'stateB' }], factories) + }) + + bench('remove from stack (2 → 1 states)', () => { + const actor = new TestActor() + actor.state.push(new StateA(actor)) + actor.state.push(new StateB(actor)) + syncStateStack(actor, [{ stateName: 'stateA' }], factories) + }) + + bench('clear all states (2 → 0)', () => { + const actor = new TestActor() + actor.state.push(new StateA(actor)) + actor.state.push(new StateB(actor)) + syncStateStack(actor, [], factories) + }) +}) diff --git a/packages/core/tests/Utils/EventEmitter.bench.ts b/packages/core/tests/Utils/EventEmitter.bench.ts new file mode 100644 index 0000000..594b691 --- /dev/null +++ b/packages/core/tests/Utils/EventEmitter.bench.ts @@ -0,0 +1,36 @@ +import { bench, describe } from 'vitest' +import EventEmitter from '../../src/Utils/EventEmitter' + +describe('EventEmitter', () => { + bench('trigger — unknown event (no listeners)', () => { + const emitter = new EventEmitter() + emitter.trigger('tick') + }) + + bench('trigger — 1 listener', () => { + const emitter = new EventEmitter() + emitter.on('tick', () => {}) + emitter.trigger('tick', { delta: 0.016 }) + }) + + bench('trigger — 10 listeners', () => { + const emitter = new EventEmitter() + for (let i = 0; i < 10; i++) { + emitter.on('tick', () => {}) + } + emitter.trigger('tick', { delta: 0.016 }) + }) + + bench('on — register listener', () => { + const emitter = new EventEmitter() + emitter.on('tick', () => {}) + }) + + bench('off — remove 1 of 10 listeners', () => { + const emitter = new EventEmitter() + const cb = () => {} + for (let i = 0; i < 9; i++) emitter.on('tick', () => {}) + emitter.on('tick', cb) + emitter.off('tick', cb) + }) +}) diff --git a/packages/core/tests/World/StateManager.bench.ts b/packages/core/tests/World/StateManager.bench.ts new file mode 100644 index 0000000..1fc5d4d --- /dev/null +++ b/packages/core/tests/World/StateManager.bench.ts @@ -0,0 +1,48 @@ +import { bench, describe } from 'vitest' +import Actor from '../../src/World/Actor' +import EntityState from '../../src/World/Entity/State' +import StateManager from '../../src/World/StateManager' + +class DummyActor extends Actor { + update() {} +} + +class NoopState extends EntityState { + update(_delta: number): EntityState | void {} +} + +class TransitionState extends EntityState { + private next: EntityState + + constructor(entity: DummyActor, next: EntityState) { + super(entity) + this.next = next + } + + update(_delta: number): EntityState | void { + return this.next + } +} + +describe('StateManager.update', () => { + bench('empty state stack (no-op)', () => { + const actor = new DummyActor() + const manager = new StateManager() + manager.update(0.016, actor) + }) + + bench('one state, returns void', () => { + const actor = new DummyActor() + const manager = new StateManager() + manager.state.push(new NoopState(actor)) + manager.update(0.016, actor) + }) + + bench('one state, returns new state (triggers push)', () => { + const actor = new DummyActor() + const manager = new StateManager() + const next = new NoopState(actor) + manager.state.push(new TransitionState(actor, next)) + manager.update(0.016, actor) + }) +})