commit fcf5a819ef52ab3069f687e6943599fa3ccf96b3
parent cf2896324e60e2dc3890c658378499383100e79c
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date: Wed, 28 May 2025 12:17:55 +0200
Web solver is usable
Diffstat:
3 files changed, 222 insertions(+), 22 deletions(-)
diff --git a/web/http/index.html b/web/http/index.html
@@ -9,12 +9,27 @@
<body>
<input id="scrambleText" placeholder="Type the scramble here...">
<select id="solverSelector">
- <option value="h48h0k4" selected="selected">h48 h=0 k=4 (59Mb)</option>
- <option value="h48h3k2">h48 h=3 k=2 (283Mb)</option>
- <option value="h48h7k2">h48 h=7 k=2 (3.6Gb)</option>
+ <option value="h48h0k4">h48 h=0 k=4 (59Mb)</option>
+ <option value="h48h3k2" selected="selected">h48 h=3 k=2 (283Mb)</option>
+ <!--option value="h48h7k2">h48 h=7 k=2 (3.6Gb)</option-->
</select>
<button id="solveButton">Solve</button>
+ <div id="confirmDownload" style="display:none">
+ <p id="confirmDownloadText">
+ For this solver to work, a large data table is required.
+ You can either download it or generate it locally.
+ </p>
+ <p id="confirmDownloadExtraText">
+ In either case, this data will be stored locally for future use. You
+ can delete it at any time by clearing your browser's data.
+ </p>
+ <button id="confirmDownloadCancel">Cancel</button>
+ <button id="confirmDownloadConfirm">Download</button>
+ <button id="confirmDownloadGenerate">Generate locally</button>
+ </div>
<p id="resultsLabel"></p>
<p id="results"></p>
+ <button id="toggleShowLog">Show nissy log messages</button>
+ <p id="logPane" style="display:none"></p>
</body>
</html>
diff --git a/web/http/nissyapp.mjs b/web/http/nissyapp.mjs
@@ -7,30 +7,149 @@ var scrField = document.getElementById("scrambleText");
var resultsLabel = document.getElementById("resultsLabel");
var resultsText = document.getElementById("results");
var solverSelector = document.getElementById("solverSelector");
+var toggleLog = document.getElementById("toggleShowLog");
+var logPane = document.getElementById("logPane");
var lastCallbackId = 0;
var callbacks = new Map(); // Values: { f: function, arg: object }
var worker = new Worker("./worker.mjs", { type: "module" });
worker.onmessage = (event) => {
if (event.data.command == "log") {
- console.log(event.data.object); // TODO
+ logPane.innerText += event.data.object;
+ console.log(event.data.object);
} else if (!callbacks.has(event.data.id)) {
console.log("[nissy app] Unknown callback " + event.data.id +
" for command " + event.data.command);
} else {
var callback = callbacks.get(event.data.id);
+ callbacks.delete(event.data.id);
callback.f(callback.arg, event.data.arg);
}
};
-function updateResults(label, results, buttonState) {
+function updateResults(label, results, enable) {
resultsLabel.innerText = label;
resultsText.innerText = results;
- solveButton.disable = !buttonState;
+ solveButton.disable = !enable;
+ solverSelector.disable = !enable;
}
+var logVisible = false;
+toggleLog.addEventListener("click", () => {
+ logVisible = !logVisible;
+ if (logVisible) {
+ logPane.style.display = "block";
+ toggleLog.innerText = "Hide nissy log messages";
+ } else {
+ logPane.style.display = "none";
+ toggleLog.innerText = "Show nissy log messages";
+ }
+});
+
solveButton.addEventListener("click", () => {
- updateResults("Loading solutions...", "", false);
+ const solver = solverSelector.options[solverSelector.selectedIndex].value;
+ const scramble = scrField.value;
+ loadDataThenSolve(solver, scramble);
+
+ const solveCallbackId = ++lastCallbackId;
+ callbacks.set(solveCallbackId, {
+ f: (callbackArg, loadResult) => {
+ if (loadResult.success) {
+ startSolve(callbackArg.solver, callbackArg.scramble);
+ } else {
+ updateResults("", "", true);
+ }
+ },
+ arg: { solver: solver, scramble: scramble }
+ });
+
+ const downloadCallbackId = ++lastCallbackId;
+});
+
+function loadDataThenSolve(solver, scramble) {
+ updateResults("Loading solver " + solver + "...", "", false);
+
+ const callbackId = ++lastCallbackId;
+ callbacks.set(callbackId, {
+ f: (callbackArg, loadResult) => {
+ if (loadResult.success)
+ startSolve(callbackArg.solver, callbackArg.scramble);
+ else
+ askDownloadThenSolve(callbackArg.solver, callbackArg.scramble);
+ },
+ arg: { solver: solver, scramble: scramble }
+ });
+
+ worker.postMessage({
+ command: "load solver data",
+ id: callbackId,
+ arg: solver
+ });
+}
+
+function askDownloadThenSolve(solver, scramble) {
+ var confirmDiv = document.getElementById("confirmDownload");
+ var cancel = document.getElementById("confirmDownloadCancel");
+ var download = document.getElementById("confirmDownloadConfirm");
+ var generate = document.getElementById("confirmDownloadGenerate");
+ const cleanup = () => {
+ confirmDiv.style.display = "none";
+
+ // Remove event listeners by cloning the buttons
+ cancel.outerHTML = cancel.outerHTML;
+ download.outerHTML = download.outerHTML;
+ generate.outerHTML = generate.outerHTML;
+ };
+
+ updateResults("", "", false);
+ confirmDiv.style.display = "block";
+
+ cancel.addEventListener("click", cleanup);
+
+ download.addEventListener("click", () => {
+ cleanup();
+ downloadDataThenSolve(solver, scramble);
+ });
+
+ generate.addEventListener("click", () => {
+ cleanup();
+ generateDataThenSolve(solver, scramble);
+ });
+}
+
+const downloadDataThenSolve = (solver, scramble) =>
+ downloadOrGenerateThenSolve(solver, scramble, true);
+
+const generateDataThenSolve = (solver, scramble) =>
+ downloadOrGenerateThenSolve(solver, scramble, false);
+
+function downloadOrGenerateThenSolve(solver, scramble, download) {
+ const msg = download ? "Downloading data for " : "Generating data for ";
+ const command = download ? "download solver data" : "generate solver data";
+
+ updateResults(msg + solver + "...", "", false);
+
+ const callbackId = ++lastCallbackId;
+ callbacks.set(callbackId, {
+ f: (callbackArg, loadResult) => {
+ if (loadResult.success)
+ startSolve(callbackArg.solver, callbackArg.scramble);
+ else
+ updateResults("Failed", loadResult.message, true);
+ },
+ arg: { solver: solver, scramble: scramble }
+ });
+
+ worker.postMessage({
+ command: command,
+ id: callbackId,
+ arg: solver
+ });
+}
+
+function startSolve(solver, scramble) {
+ updateResults("Solving...", "", false);
+
const callbackId = ++lastCallbackId;
callbacks.set(callbackId, {
f: (callbackArg, solveResult) => {
@@ -46,12 +165,13 @@ solveButton.addEventListener("click", () => {
},
arg: "" // Currently unused
});
+
worker.postMessage({
command: "solve",
id: callbackId,
arg: {
- solver: solverSelector.options[solverSelector.selectedIndex].value,
- scramble: scrField.value,
+ solver: solver,
+ scramble: scramble,
minmoves: 0,
maxmoves: 20,
maxsolutions: 1,
@@ -59,4 +179,4 @@ solveButton.addEventListener("click", () => {
threads: window.navigator.hardwareConcurrency,
}
});
-});
+}
diff --git a/web/worker.mjs b/web/worker.mjs
@@ -8,8 +8,10 @@ function log(message) {
nissy.setLogger(nissy._addCallbackFunction(log));
const commands = [
- { name: "solve", exec: solve },
- { name: "download solver data", exec: downloadSolverData },
+ { name: "load solver data", exec: loadSolverDataFromStorage },
+ { name: "download solver data", exec: downloadSolverData },
+ { name: "generate solver data", exec: generateSolverData },
+ { name: "solve", exec: solve },
];
// Message structure:
@@ -25,6 +27,7 @@ onmessage = (event) => {
for (var i = 0; i < commands.length; i++) {
if (commands[i].name == event.data.command) {
commands[i].exec(event.data.id, event.data.arg);
+ console.log("[worker] Received '" + commands[i].name + "'");
return;
}
}
@@ -32,10 +35,41 @@ onmessage = (event) => {
log("[nissy worker] unknown command " + event.data.command);
};
+// Load solver data from storage.
+// Argument: string (the name of the solver)
+async function loadSolverDataFromStorage(id, solver) {
+ const async_return = (success, message = "") => postMessage({
+ command: "load solver data",
+ id: id,
+ arg: {
+ success: success,
+ message: message
+ }
+ });
+
+ if (!(await nissy.isSolverAvailable(solver))) {
+ async_return(false, "Error: solver " + arg.solver +
+ " is not available in this version of nissy.");
+ return;
+ }
+
+ if (await nissy.isSolverLoaded(solver)) {
+ async_return(true);
+ return;
+ }
+
+ if (await nissy.initSolverFromStorage(solver)) {
+ async_return(true);
+ } else {
+ async_return(false, "Could not read data for " + solver +
+ " from local storage");
+ }
+}
+
// Download solver data.
-// Argument: string
+// Argument: string (the name of the solver)
async function downloadSolverData(id, solver) {
- const downloadSolverDataReturn = (success, message = "") => postMessage({
+ const async_return = (success, message = "") => postMessage({
command: "download solver data",
id: id,
arg: {
@@ -44,10 +78,41 @@ async function downloadSolverData(id, solver) {
}
});
- if (await nissy.initSolverDownload(arg.solver, "/tables")) {
- downloadSolverDataReturn(true);
+ if (!(await nissy.isSolverAvailable(solver))) {
+ async_return(false, "Error: solver " + arg.solver +
+ " is not available in this version of nissy.");
+ return;
+ }
+
+ if (await nissy.initSolverDownload(solver, "/tables")) {
+ async_return(true);
+ } else {
+ async_return(false, "Error retrieving solver data");
+ }
+}
+
+// Generate solver data locally.
+// Argument: string (the name of the solver)
+async function generateSolverData(id, solver) {
+ const async_return = (success, message = "") => postMessage({
+ command: "generate solver data",
+ id: id,
+ arg: {
+ success: success,
+ message: message
+ }
+ });
+
+ if (!(await nissy.isSolverAvailable(solver))) {
+ async_return(false, "Error: solver " + arg.solver +
+ " is not available in this version of nissy.");
+ return;
+ }
+
+ if (await nissy.initSolverGenerate(solver)) {
+ async_return(true);
} else {
- downloadSolverDataReturn(false, "Error retrieving the solver data");
+ async_return(false, "Error generating solver data");
}
}
@@ -62,7 +127,7 @@ async function downloadSolverData(id, solver) {
// threads: number
// }
async function solve(id, arg) {
- const solveReturn = (success, solutions = [], message = "") => postMessage({
+ const async_return = (success, solutions = [], message = "") => postMessage({
command: "solve",
id: id,
arg: {
@@ -73,14 +138,14 @@ async function solve(id, arg) {
});
if (!(await nissy.isSolverAvailable(arg.solver))) {
- solveReturn(false, [], "Error: solver " + arg.solver +
+ async_return(false, [], "Error: solver " + arg.solver +
" is not available in this version of nissy.");
return;
}
if (!(await nissy.isSolverLoaded(arg.solver)) &&
!(await nissy.initSolverFromStorage(arg.solver))) {
- solveReturn(false, [], "Error: solver " + arg.solver + " has not been " +
+ async_return(false, [], "Error: solver " + arg.solver + " has not been " +
"loaded. Its data must be donwloaded or generated before using it.");
return;
}
@@ -106,9 +171,9 @@ async function solve(id, arg) {
//TODO: add solution lenght?
var solutions = result.solutions == "" ? [] : result.solutions.split("\n");
solutions.pop(); // Solver always returns string ending in newline
- solveReturn(true, solutions);
+ async_return(true, solutions);
} else {
- solveReturn(false, [],
+ async_return(false, [],
"Error while solving (error " + result.err.value + ")");
}
};