commit cf2896324e60e2dc3890c658378499383100e79c
parent 7c934801f88c640970ad41b5ddd39f4e39609f28
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date: Tue, 27 May 2025 16:45:06 +0200
Big progress with web version
Diffstat:
8 files changed, 187 insertions(+), 69 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -21,8 +21,8 @@ runtest
runcpp
runtest.js
runtest.wasm
-web/*.wasm
web/http/nissy_web_module.*
+web/http/worker.mjs
web/nissy_web_module.*
runtool
tools/.DS_Store
diff --git a/build b/build
@@ -207,7 +207,8 @@ odflags() {
build_clean() {
run rm -rf -- *.o *.so *.a run runtest runtool runcpp \
- web/nissy_web_module.* web/*.wasm web/http/*.mjs web/http/*.wasm
+ web/nissy_web_module.* web/http/nissy_web_module.* \
+ web/http/worker.mjs
}
build_nissy() {
@@ -273,8 +274,9 @@ build_web() {
$CPPFLAGS $(odflags) $WASMLINKFLAGS \
--js-library web/callback.js -o web/"$obj".mjs \
cpp/nissy.cpp web/storage.cpp web/adapter.cpp nissy.o || exit 1
- cp web/"$obj".mjs web/http
- cp web/"$obj".wasm web/http
+ cp web/"$obj".mjs web/http/
+ cp web/"$obj".wasm web/http/
+ cp web/worker.mjs web/http/
}
dotest() {
diff --git a/cpp/nissy.cpp b/cpp/nissy.cpp
@@ -183,7 +183,7 @@ namespace nissy {
result.solutions.data(), stats,
poll_status, poll_status_data);
- int size = result.solutions.find_first_of('\0') + 1;
+ int size = result.solutions.find_first_of('\0');
result.solutions.resize(size);
result.err = error{err};
diff --git a/web/http/index.html b/web/http/index.html
@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Nissy - H48 solver POC</title>
- <script type="module" src="./solve.mjs"></script>
+ <script type="module" src="./nissyapp.mjs"></script>
</head>
<body>
<input id="scrambleText" placeholder="Type the scramble here...">
@@ -13,8 +13,8 @@
<option value="h48h3k2">h48 h=3 k=2 (283Mb)</option>
<option value="h48h7k2">h48 h=7 k=2 (3.6Gb)</option>
</select>
- <button id="solveButton">Solve!</button>
- <p>Solution:</p>
- <p id="solution"></p>
+ <button id="solveButton">Solve</button>
+ <p id="resultsLabel"></p>
+ <p id="results"></p>
</body>
</html>
diff --git a/web/http/nissyapp.mjs b/web/http/nissyapp.mjs
@@ -0,0 +1,62 @@
+// Run ./build web from the main folder before running this example.
+// The necessary modules (including worker.mjs) will be built and / or moved
+// to this folder when running ./build web.
+
+var solveButton = document.getElementById("solveButton");
+var scrField = document.getElementById("scrambleText");
+var resultsLabel = document.getElementById("resultsLabel");
+var resultsText = document.getElementById("results");
+var solverSelector = document.getElementById("solverSelector");
+
+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
+ } 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);
+ callback.f(callback.arg, event.data.arg);
+ }
+};
+
+function updateResults(label, results, buttonState) {
+ resultsLabel.innerText = label;
+ resultsText.innerText = results;
+ solveButton.disable = !buttonState;
+}
+
+solveButton.addEventListener("click", () => {
+ updateResults("Loading solutions...", "", false);
+ const callbackId = ++lastCallbackId;
+ callbacks.set(callbackId, {
+ f: (callbackArg, solveResult) => {
+ if (solveResult.success) {
+ const n = solveResult.solutions.length;
+ const label = n == 0 ? "No solution found" :
+ ("Found " + n + " solution" + (n == 1 ? "" : "s")) + ":";
+ const text = solveResult.solutions.join("\n");
+ updateResults(label, text, true);
+ } else {
+ updateResults("Unexpected error while solving!", "", true);
+ }
+ },
+ arg: "" // Currently unused
+ });
+ worker.postMessage({
+ command: "solve",
+ id: callbackId,
+ arg: {
+ solver: solverSelector.options[solverSelector.selectedIndex].value,
+ scramble: scrField.value,
+ minmoves: 0,
+ maxmoves: 20,
+ maxsolutions: 1,
+ optimal: 20,
+ threads: window.navigator.hardwareConcurrency,
+ }
+ });
+});
diff --git a/web/http/solve.mjs b/web/http/solve.mjs
@@ -1,33 +0,0 @@
-var solveButton = document.getElementById("solveButton");
-var scrField = document.getElementById("scrambleText");
-var solutionText = document.getElementById("solution");
-var solverSelector = document.getElementById("solverSelector");
-
-var worker = new Worker("./worker.mjs", { type: "module" });
-worker.onmessage = (e) => {
- updateResults(e.data);
- enableSolveButton();
-};
-
-function updateResults(s) {
- solutionText.innerText = s;
-}
-
-function disableSolveButton() {
- solveButton.disabled = true;
- solveButton.innerText = "Solving...";
-}
-
-function enableSolveButton() {
- solveButton.disabled = false;
- solveButton.innerText = "Solve!";
-}
-
-solveButton.addEventListener("click", () => {
- disableSolveButton();
- updateResults("Loading solutions...");
- worker.postMessage({
- solver: solverSelector.options[solverSelector.selectedIndex].value,
- scramble: scrField.value
- });
-});
diff --git a/web/http/worker.mjs b/web/http/worker.mjs
@@ -1,27 +0,0 @@
-import Nissy from "./nissy_web_module.mjs"
-
-const nissy = await Nissy();
-nissy.setLogger(nissy._addCallbackFunction(console.log));
-
-onmessage = (e) => {
-//TODO: try to load from storage first, then download
- nissy.initSolverDownload(e.data.solver, "/tables").then(
- (download_result) => {
- if (!download_result) {
- postMessage("Error retrieving the solver data");
- } else {
- var cube = new nissy.Cube();
- cube.move(e.data.scramble);
-
- nissy.solve(e.data.solver, cube, nissy.NissFlag.normal,
- 0, 17, 1, 99, 4, -1).then(
- (solve_result) => {
- if (!solve_result.err.ok())
- postMessage("Error while solving (solve returned " +
- solve_result.err.value + ")");
- else
- postMessage(solve_result.solutions)
- });
- }
- });
-};
diff --git a/web/worker.mjs b/web/worker.mjs
@@ -0,0 +1,114 @@
+import Nissy from "./nissy_web_module.mjs"
+
+const nissy = await Nissy();
+function log(message) {
+ postMessage({ command: "log", id: -1, object: message });
+ console.log(message);
+}
+nissy.setLogger(nissy._addCallbackFunction(log));
+
+const commands = [
+ { name: "solve", exec: solve },
+ { name: "download solver data", exec: downloadSolverData },
+];
+
+// Message structure:
+// {
+// command: string // The function to run.
+// id: number // An identification number for the command, used for
+// // sending responses back to the caller and having them
+// // caught by the correct handler.
+// arg: object // The actual argument of the message, command-specific.
+// // See below for details.
+// }
+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);
+ return;
+ }
+ }
+
+ log("[nissy worker] unknown command " + event.data.command);
+};
+
+// Download solver data.
+// Argument: string
+async function downloadSolverData(id, solver) {
+ const downloadSolverDataReturn = (success, message = "") => postMessage({
+ command: "download solver data",
+ id: id,
+ arg: {
+ success: success,
+ message: message
+ }
+ });
+
+ if (await nissy.initSolverDownload(arg.solver, "/tables")) {
+ downloadSolverDataReturn(true);
+ } else {
+ downloadSolverDataReturn(false, "Error retrieving the solver data");
+ }
+}
+
+// Solve the cube with the given options.
+// Argument: {
+// solver: string
+// scramble: string
+// minmoves: number
+// maxmoves: number
+// maxsolutions: number
+// optimal: number
+// threads: number
+// }
+async function solve(id, arg) {
+ const solveReturn = (success, solutions = [], message = "") => postMessage({
+ command: "solve",
+ id: id,
+ arg: {
+ success: success,
+ solutions: solutions,
+ message: message
+ }
+ });
+
+ if (!(await nissy.isSolverAvailable(arg.solver))) {
+ solveReturn(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 " +
+ "loaded. Its data must be donwloaded or generated before using it.");
+ return;
+ }
+
+ var cube = new nissy.Cube();
+ cube.move(arg.scramble);
+
+ // TODO: error handling here?
+ const nissFlag = ((str) => {
+ switch (str) {
+ case "inverse": return nissy.NissFlag.inverse;
+ case "mixed": return nissy.NissFlag.mixed;
+ case "linear": return nissy.NissFlag.linear;
+ case "all": return nissy.NissFlag.all;
+ default: return nissy.NissFlag.normal;
+ }
+ })(arg.nissFlag);
+
+ const result = await nissy.solve(arg.solver, cube, nissFlag, arg.minmoves,
+ arg.maxmoves, arg.maxsolutions, arg.optimal, arg.threads, id);
+
+ if (result.err.ok()) {
+ //TODO: add solution lenght?
+ var solutions = result.solutions == "" ? [] : result.solutions.split("\n");
+ solutions.pop(); // Solver always returns string ending in newline
+ solveReturn(true, solutions);
+ } else {
+ solveReturn(false, [],
+ "Error while solving (error " + result.err.value + ")");
+ }
+};