WebAssembly
Opcjonalny most TS↔WASM dla ciężkiego compute. Instalacja Emscripten, kompilacja C++23, zasady granicy i graceful fallback.
Kiedy WASM ma sens#
WASM jest opcjonalny. Most jest przygotowany, ale dodawaj moduł tylko wtedy, gdy benchmark TS vs WASM wykaże mierzalną korzyść (ADR 0004).
Typowe kandydaci: pathfinding A*, generowanie terenu, symulacje cząsteczek, ciężka geometria.
Zasady granicy TS↔WASM#
- Jedno wywołanie z dużą paczką danych > wiele drobnych wywołań. Przekroczenie granicy kosztuje.
- Nie ujawniaj wewnętrznych klas C++. Cienka fasada
extern "C"lubembind. required: falsenie wywala gry. Brak pliku WASM = graceful fallback do TS.
Instalacja Emscripten#
git clone https://github.com/emscripten-core/emsdk.git
./emsdk/emsdk install latest
./emsdk/emsdk activate latest
source ./emsdk/emsdk_env.sh
Kompilacja#
emcmake cmake -S native/core -B native/core/build-wasm
cmake --build native/core/build-wasm
ls native/core/build-wasm/*.wasm
Skopiuj artefakty do playgroundu:
cp native/core/build-wasm/core.* apps/playground/public/wasm/
Użycie w silniku#
const engine = createEngine({
parent: 'app',
width: 800,
height: 600,
wasm: { wasmUrl: '/wasm/core.wasm', required: false }
})
await engine.getWasm().load()
if (engine.getWasm().isReady()) {
const path = engine.getWasm().call<Vec2i[]>('findPathAStar', grid, start, goal)
}
API mostu#
engine.getWasm().isReady()
engine.getWasm().getState()
engine.getWasm().getError()
engine.getWasm().getInstance()
engine.getWasm().call<T>(name, ...args): T | null
engine.getWasm().reset()
Przykład: pathfinding A*#
TS przekazuje płaską tablicę blocked (Uint8Array), C++ zwraca wektor pozycji:
const grid = {
width: 64,
height: 64,
blocked: new Uint8Array(4096)
}
const path = engine.getWasm().call<Vec2i[]>(
'findPathAStar',
grid,
{ x: 0, y: 0 },
{ x: 63, y: 63 }
)
if (path) {
for (const step of path) {
enemy.moveTo(step.x * TILE, step.y * TILE)
}
}
Implementacja po stronie C++ żyje w native/core/include/pathfinding.hpp. Testuj algorytm natywnie i w przeglądarce (native/core/tests).
Graceful fallback#
async function findPath(grid: Grid, from: Vec2i, to: Vec2i): Promise<Vec2i[]> {
const wasm = engine.getWasm()
if (wasm.isReady()) {
const result = wasm.call<Vec2i[]>('findPathAStar', grid, from, to)
if (result) return result
}
return findPathAStarTS(grid, from, to)
}
Mierz, nie zgaduj
„WASM będzie szybszy” to hipoteza, nie fakt. Dla małych danych narzut kopiowania przez granicę potrafi zjeść cały zysk. Uruchom npm run bench dla obu wariantów i dopiero wtedy decyduj.