Étape 0d.2 : plateau jouable en PWA hors ligne, aide intégrée #13
@@ -0,0 +1,37 @@
|
||||
// Le moteur WebAssembly répond indéfiniment : une partie en navigateur
|
||||
// envoie des centaines de commandes au même moteur chargé une fois.
|
||||
//
|
||||
// Copyright (C) 2026 les auteurs d'Ustagammon.
|
||||
// Licence : GPLv3 ou ultérieure (voir LICENSE).
|
||||
//
|
||||
// Régression : callMain (Emscripten) laisse ses arguments sur la pile du
|
||||
// WebAssembly (64 Ko). Sans remise en place après chaque commande
|
||||
// (web/engine-wasm.mjs), la pile débordait vers la 500e commande : erreur
|
||||
// « memory access out of bounds » sous Node, moteur muet dans Firefox,
|
||||
// onglet planté dans Chromium.
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// Compilé dans build/web/tests/ : la racine du dépôt est trois dossiers plus
|
||||
// haut.
|
||||
const root = new URL("../../../", import.meta.url);
|
||||
|
||||
interface RawEngine {
|
||||
runText(args: string[]): { text: string; exitCode: number };
|
||||
}
|
||||
|
||||
test("le moteur répond à 3000 commandes à la suite", async () => {
|
||||
const module = await import(new URL("build/wasm/engine.mjs", root).href);
|
||||
const wrapper = await import(new URL("web/engine-wasm.mjs", root).href);
|
||||
const engine: RawEngine = await wrapper.loadEngine(
|
||||
module.default,
|
||||
fileURLToPath(new URL("build/wasm/", root)),
|
||||
);
|
||||
for (let i = 0; i < 3000; i++) {
|
||||
const { text, exitCode } = engine.runText(["position", "4HPwATDgc/ABMA", "AQEAAAAAAAAA"]);
|
||||
assert.equal(exitCode, 0, `commande ${i}`);
|
||||
assert.match(text, /"pip_count": 167/, `commande ${i}`);
|
||||
}
|
||||
});
|
||||
+6
-2
@@ -33,14 +33,18 @@ ENGINE_INCLUDES := -I$(COMPAT) -I$(COMPAT)/glib-shim -I$(GNUBG) -I$(GNUBG)/lib -
|
||||
# - main n'est pas lancé au chargement, et le programme ne s'arrête pas à sa
|
||||
# sortie : le moteur reste chargé entre deux commandes ;
|
||||
# - mémoire extensible : les bases de fin de partie occupent plusieurs
|
||||
# mégaoctets.
|
||||
# mégaoctets ;
|
||||
# - stackSave et stackRestore : callMain laisse ses arguments sur la pile
|
||||
# (64 Ko) ; sans restauration après chaque commande, elle déborde au bout
|
||||
# de quelques centaines de commandes et corrompt la mémoire du moteur
|
||||
# (voir web/engine-wasm.mjs).
|
||||
LINK_FLAGS := -O3 \
|
||||
-sMODULARIZE -sEXPORT_ES6 -sEXPORT_NAME=createEngineModule \
|
||||
-sENVIRONMENT=web,worker,node \
|
||||
-sINVOKE_RUN=0 -sEXIT_RUNTIME=0 \
|
||||
-sALLOW_MEMORY_GROWTH \
|
||||
-sEXPORTED_FUNCTIONS=_main,_engine_init \
|
||||
-sEXPORTED_RUNTIME_METHODS=callMain,ccall,HEAPU8 \
|
||||
-sEXPORTED_RUNTIME_METHODS=callMain,ccall,HEAPU8,stackSave,stackRestore \
|
||||
--preload-file $(BUILD)/data@/data
|
||||
|
||||
DATA := $(BUILD)/data/gnubg.wd $(BUILD)/data/gnubg_os0.bd $(BUILD)/data/gnubg_ts0.bd \
|
||||
|
||||
+11
-2
@@ -36,10 +36,19 @@ export async function loadEngine(createEngineModule, wasmDir) {
|
||||
throw new Error("le moteur n'a pas pu charger ses données (code " + status + ")");
|
||||
}
|
||||
|
||||
// callMain copie les arguments sur la pile du WebAssembly sans jamais
|
||||
// les retirer : on remet la pile en place après chaque commande, sinon
|
||||
// elle déborde au bout de quelques centaines de commandes (environ 540
|
||||
// pendant une partie) et le moteur plante ou ne répond plus.
|
||||
function runText(args) {
|
||||
output = [];
|
||||
const exitCode = module.callMain(args.map(String));
|
||||
return { text: output.join("\n"), exitCode };
|
||||
const stack = module.stackSave();
|
||||
try {
|
||||
const exitCode = module.callMain(args.map(String));
|
||||
return { text: output.join("\n"), exitCode };
|
||||
} finally {
|
||||
module.stackRestore(stack);
|
||||
}
|
||||
}
|
||||
|
||||
function run(args) {
|
||||
|
||||
Reference in New Issue
Block a user