Wydajność i benchmarki
Zasady gorącej pętli, dirty sync, benchmarki Node z progami regresji i raport rozmiaru bundla. Liczby z pomiarów, nie z obietnic.
Zasady gorącej pętli#
- Brak alokacji poza jedną kopią listy subskrybentów w
EventBus.emiti przebudową listy komponentów po zmianie struktury. - Dirty sync — render dostaje tylko obiekty brudne. Scena z 79 obiektami wysyła w klatce zwykle 1–3.
- Odczyt
worldO(1) po przeliczeniu; zmiana transformacji unieważnia tylko poddrzewo. - Profiler domyślnie wyłączony —
begin/endzwracają się natychmiast.
Zmierzone liczby#
| Pomiar | Wynik |
|---|---|
| Aktualizacja komponentów, 20 000 obiektów | 0,72 ms/klatkę |
| Aktualizacja komponentów, 2 000 obiektów | ~0,07 ms/klatkę |
| Obiekty brudne w klatce (scena 79 obiektów) | zwykle 1–3 |
| Benchmarki Node | 17 przypadków |
Próg regresji bench:check | 40% |
Bez obietnic sprzętowych
Liczby bezwzględne zależą od maszyny, dlatego bench:check porównuje wyniki z baseline na tej samej maszynie i używa progów względnych. Silnik nie publikuje limitów sprzętowych, których nie zmierzył.
Benchmarki Node#
npm run bench
npm run bench:check
npm run bench:baseline
17 przypadków w kategoriach: objects, queries, collisions, tilemap, assets. Wyniki trafiają do benchmarks/latest.json, a bench:check porównuje je z benchmarks/baseline.json z progiem 40% — kod wyjścia 1 nadaje się do CI.
import { describe, bench } from 'vitest'
describe('queries', () => {
bench('findByTag', () => {
scene.findByTag('enemy')
})
})
Raport rozmiaru bundla#
npm run bundle:report
npm run bundle:check
npm run bundle:baseline
dist/release.json zawiera rozmiar, gzip, brotli i SHA-256 każdego pliku. Progi:
| Zakres | Próg |
|---|---|
| Suma gzip katalogu | +10% względem baseline |
| Pojedynczy plik > 16 kB | +35% |
playground.html (single-file) | +5% |
bundle:check kończy się kodem 1 przy przekroczeniu — działa jako bramka CI bez dodatkowego parsowania.
Checklist wydajnościowy dla gry#
- Mierz, zanim optymalizujesz. Włącz profiler i znajdź najdroższe znaczniki.
- Trzymaj logikę w komponentach z właściwą fazą.
inputprzedupdate, fizyka wfixedUpdate. - Nie alokuj w pętli. Współdziel wektory, tablice i deskryptory; używaj puli obiektów dla pocisków.
- Tagi zamiast skanowania.
findByTagkorzysta z indeksu,findskanuje wszystko. - Ogranicz dirty sync. Zmieniaj tylko to, co naprawdę się rusza; HUD z
setScrollFactor(0). - Assety z budżetem.
sizeReport(limits)idiagnose()wyłapią nieużywane i za ciężkie pliki. - WASM tylko z pomiarem. Porównaj TS vs WASM na realnych danych.
Przykład: pula pocisków#
class BulletPool {
private pool: GameObject[] = []
constructor(private scene: Scene, size = 64) {
for (let i = 0; i < size; i++) {
const bullet = scene.createRect(6, 6, { x: -100, y: -100, fill: '#ffd75e' })
bullet.setActive(false).setVisible(false)
this.pool.push(bullet)
}
}
fire(x: number, y: number, vx: number) {
const bullet = this.pool.find((b) => !b.active)
if (!bullet) return
bullet.setPosition(x, y).setActive(true).setVisible(true)
bullet.add(new TimedDestroy(2))
}
}
Zero alokacji w trakcie strzelania — obiekty powstają raz, przy stworzeniu puli.