90 lines
3.6 KiB
JavaScript
90 lines
3.6 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 + ")");
|
|
}
|
|
|
|
// 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 stack = module.stackSave();
|
|
try {
|
|
const exitCode = module.callMain(args.map(String));
|
|
return { text: output.join("\n"), exitCode };
|
|
} finally {
|
|
module.stackRestore(stack);
|
|
}
|
|
}
|
|
|
|
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],
|
|
analyseMove: (positionId, matchId, dice, playedPositionId, ply) =>
|
|
["--ply", ply, "analysemove", positionId, matchId, diceText(dice), playedPositionId],
|
|
reference: (positionId, matchId, dice, ply) =>
|
|
["--ply", ply, "reference", positionId, matchId, ...(dice ? [diceText(dice)] : [])],
|
|
};
|
|
|
|
function diceText(dice) {
|
|
return Array.isArray(dice) ? dice.join("") : String(dice);
|
|
}
|