nissy-core

The "engine" of nissy, including the H48 optimal solver.
git clone https://git.tronto.net/nissy-core
Download | Log | Files | Refs | README | LICENSE

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:
M.gitignore | 2+-
Mbuild | 8+++++---
Mcpp/nissy.cpp | 2+-
Mweb/http/index.html | 8++++----
Aweb/http/nissyapp.mjs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dweb/http/solve.mjs | 33---------------------------------
Dweb/http/worker.mjs | 27---------------------------
Aweb/worker.mjs | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 + ")"); + } +};