Files
Ustagammon/web/engine-wasm.mjs
T
Pierre MARTINandClaude Opus 5.5 b0030521d8 Moteur en WebAssembly : make engine-wasm et suite de contrat
Le moteur et engine-cli sont compilés avec Emscripten (image figée
emscripten/emsdk:3.1.64), en C générique, sans threads ; les données
de build/data/ sont incluses dans engine.data. Le JavaScript appelle
engine_init une fois puis main pour chaque commande : la sortie est le
JSON d'engine-cli, sans code de mise en forme dupliqué.

tests/test_wasm_reference.sh rejoue les 32 positions de référence sous
Node avec le même comparateur (option --cli) et la même tolérance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 19:07:01 +01:00

79 lines
3.0 KiB
JavaScript

// engine-wasm.mjs -- le moteur WebAssembly vu depuis JavaScript.
//
// Copyright (C) 2026 les auteurs d'Ustagammon.
// Licence : GPLv3 ou ultérieure (voir LICENSE).
//
// Le WebAssembly (build/wasm/, voir engine-wasm/wasm.mk) contient le moteur
// et le programme engine-cli. On charge le module une fois, on initialise le
// moteur, puis chaque commande est un appel à main avec les mêmes arguments
// qu'engine-cli en ligne de commande : la sortie est donc le même JSON.
//
// Utilisé par le Web Worker de la page (engine-worker.mjs) et, sous Node,
// par engine-wasm/engine-cli.mjs et engine-wasm/bench.mjs. Le moteur calcule
// sur le fil qui l'appelle : dans le navigateur, toujours dans un Worker.
// Dossier des données dans le système de fichiers virtuel (--preload-file).
const DATA_DIR = "/data";
// createEngineModule : la fonction exportée par build/wasm/engine.mjs.
// wasmDir : où trouver engine.wasm et engine.data, terminé par « / » (une
// adresse dans le navigateur, un chemin de fichier sous Node).
// Renvoie le moteur prêt, avec :
// - runText(args) : { text, exitCode }, la sortie brute d'engine-cli ;
// - run(args) : { result, ms }, le JSON décodé et le temps de calcul
// (lève une erreur si le moteur en signale une) ;
// - memoryBytes() : taille actuelle de la mémoire WebAssembly.
export async function loadEngine(createEngineModule, wasmDir) {
let output = [];
const module = await createEngineModule({
locateFile: (name) => wasmDir + name,
print: (line) => output.push(line),
printErr: (line) => output.push(line),
});
const status = module.ccall("engine_init", "number", ["string"], [DATA_DIR]);
if (status !== 0) {
throw new Error("le moteur n'a pas pu charger ses données (code " + status + ")");
}
function runText(args) {
output = [];
const exitCode = module.callMain(args.map(String));
return { text: output.join("\n"), exitCode };
}
function run(args) {
const start = performance.now();
const { text } = runText(args);
const ms = performance.now() - start;
const result = JSON.parse(text);
if (result.error) {
throw new Error(result.error.message);
}
return { result, ms };
}
return {
runText,
run,
memoryBytes: () => module.HEAPU8.length,
};
}
// Arguments d'engine-cli pour chaque fonction de l'interface. Les dés sont
// un tableau [3, 1] ou une chaîne « 31 ».
export const commands = {
bestMove: (positionId, matchId, dice, ply) =>
["--ply", ply, "bestmove", positionId, matchId, diceText(dice)],
cubeDecision: (positionId, matchId, ply) =>
["--ply", ply, "cube", positionId, matchId],
evaluate: (positionId, matchId, ply) =>
["--ply", ply, "eval", positionId, matchId],
reference: (positionId, matchId, dice, ply) =>
["--ply", ply, "reference", positionId, matchId, ...(dice ? [diceText(dice)] : [])],
};
function diceText(dice) {
return Array.isArray(dice) ? dice.join("") : String(dice);
}