// bench-page.mjs -- page de mesure (bench.html) : chronomètre le moteur // dans un Web Worker et produit le rapport à copier. // // Copyright (C) 2026 les auteurs d'Ustagammon. // Licence : GPLv3 ou ultérieure (voir LICENSE). import { DOWNLOADED_FILES, DOWNLOAD_LIMIT_BYTES, formatReport, measureTimes, summarize } from "./benchmark.mjs"; import { startEngine } from "./engine-client.mjs"; const $ = (id) => document.getElementById(id); // Taille d'un fichier du site, brute et compressée gzip (comme le ferait un // serveur qui compresse ses réponses). // name est relatif à la racine du site, un dossier au-dessus de cette page. async function fileSize(name) { const blob = await (await fetch("../" + name, { cache: "no-store" })).blob(); const gzipped = blob.stream().pipeThrough(new CompressionStream("gzip")); const gzipBytes = (await new Response(gzipped).arrayBuffer()).byteLength; return { name, bytes: blob.size, gzipBytes }; } function addRow(cells) { const row = $("table").insertRow(); for (const text of cells) { row.insertCell().textContent = text; } } async function measure() { $("start").disabled = true; $("results").hidden = true; $("table").replaceChildren(); $("status").textContent = "Chargement du moteur…"; const start = performance.now(); const engine = startEngine(); await engine.ready; const loadMs = performance.now() - start; const { positions } = await (await fetch("../wasm/positions.json")).json(); let memoryBytes = 0; const times = await measureTimes({ bestMove: (...args) => engine.bestMove(...args).then(keepMemory), cubeDecision: (...args) => engine.cubeDecision(...args).then(keepMemory), analyseMove: (...args) => engine.analyseMove(...args).then(keepMemory), }, positions, (done, total) => { $("status").textContent = "Calcul " + done + " sur " + total + "…"; }); function keepMemory(answer) { memoryBytes = answer.memoryBytes; return answer; } $("status").textContent = "Mesure de la taille à télécharger…"; const files = await Promise.all(DOWNLOADED_FILES.map(fileSize)); for (const s of summarize(times)) { addRow([s.label, Math.round(s.medianMs) + " ms", Math.round(s.maxMs) + " ms", s.limitMs + " ms", s.ok ? "OK" : "dépassé"]); } const gzipTotal = files.reduce((sum, f) => sum + f.gzipBytes, 0); addRow(["Taille compressée", "", (gzipTotal / 1e6).toFixed(1).replace(".", ",") + " Mo", DOWNLOAD_LIMIT_BYTES / 1e6 + " Mo", gzipTotal < DOWNLOAD_LIMIT_BYTES ? "OK" : "dépassé"]); $("report").value = formatReport({ environment: navigator.userAgent, files, loadMs, memoryBytes, times, }); $("results").hidden = false; $("status").textContent = "Mesure terminée."; } // Copie dans le presse-papiers. L'API moderne n'existe que sur les pages // sécurisées (https ou localhost) : sur le réseau local en http, on // sélectionne le texte et on utilise l'ancienne commande de copie. async function copyReport() { try { if (navigator.clipboard) { await navigator.clipboard.writeText($("report").value); } else { $("report").select(); if (!document.execCommand("copy")) { throw new Error(); } } $("copy-status").textContent = "Rapport copié : collez-le dans la conversation."; } catch { $("report").select(); $("copy-status").textContent = "Copie impossible : le texte est sélectionné, " + "copiez-le à la main (appui long, puis Copier)."; } } $("start").onclick = () => measure().catch((error) => { $("status").textContent = "La mesure a échoué : " + error.message; $("status").classList.add("error"); $("start").disabled = false; }); $("copy").onclick = copyReport;