diff options
| author | Sebastiano Tronto <sebastiano@tronto.net> | 2025-05-27 16:45:06 +0200 |
|---|---|---|
| committer | Sebastiano Tronto <sebastiano@tronto.net> | 2025-05-27 16:45:06 +0200 |
| commit | cf2896324e60e2dc3890c658378499383100e79c (patch) | |
| tree | ad81c2dd806f561857824c5f1d34b80f8e20f86b /web | |
| parent | 7c934801f88c640970ad41b5ddd39f4e39609f28 (diff) | |
| download | nissy-core-cf2896324e60e2dc3890c658378499383100e79c.tar.gz nissy-core-cf2896324e60e2dc3890c658378499383100e79c.zip | |
Big progress with web version
Diffstat (limited to 'web')
| -rw-r--r-- | web/http/index.html | 8 | ||||
| -rw-r--r-- | web/http/nissyapp.mjs | 62 | ||||
| -rw-r--r-- | web/http/solve.mjs | 33 | ||||
| -rw-r--r-- | web/http/worker.mjs | 27 | ||||
| -rw-r--r-- | web/worker.mjs | 114 |
5 files changed, 180 insertions, 64 deletions
diff --git a/web/http/index.html b/web/http/index.html index 245205d..c80f7ec 100644 --- a/web/http/index.html +++ b/web/http/index.html | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | <meta charset="utf-8" /> | 4 | <meta charset="utf-8" /> |
| 5 | <meta name="viewport" content="width=device-width" /> | 5 | <meta name="viewport" content="width=device-width" /> |
| 6 | <title>Nissy - H48 solver POC</title> | 6 | <title>Nissy - H48 solver POC</title> |
| 7 | <script type="module" src="./solve.mjs"></script> | 7 | <script type="module" src="./nissyapp.mjs"></script> |
| 8 | </head> | 8 | </head> |
| 9 | <body> | 9 | <body> |
| 10 | <input id="scrambleText" placeholder="Type the scramble here..."> | 10 | <input id="scrambleText" placeholder="Type the scramble here..."> |
| @@ -13,8 +13,8 @@ | |||
| 13 | <option value="h48h3k2">h48 h=3 k=2 (283Mb)</option> | 13 | <option value="h48h3k2">h48 h=3 k=2 (283Mb)</option> |
| 14 | <option value="h48h7k2">h48 h=7 k=2 (3.6Gb)</option> | 14 | <option value="h48h7k2">h48 h=7 k=2 (3.6Gb)</option> |
| 15 | </select> | 15 | </select> |
| 16 | <button id="solveButton">Solve!</button> | 16 | <button id="solveButton">Solve</button> |
| 17 | <p>Solution:</p> | 17 | <p id="resultsLabel"></p> |
| 18 | <p id="solution"></p> | 18 | <p id="results"></p> |
| 19 | </body> | 19 | </body> |
| 20 | </html> | 20 | </html> |
diff --git a/web/http/nissyapp.mjs b/web/http/nissyapp.mjs new file mode 100644 index 0000000..23e74d0 --- /dev/null +++ b/web/http/nissyapp.mjs | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | // Run ./build web from the main folder before running this example. | ||
| 2 | // The necessary modules (including worker.mjs) will be built and / or moved | ||
| 3 | // to this folder when running ./build web. | ||
| 4 | |||
| 5 | var solveButton = document.getElementById("solveButton"); | ||
| 6 | var scrField = document.getElementById("scrambleText"); | ||
| 7 | var resultsLabel = document.getElementById("resultsLabel"); | ||
| 8 | var resultsText = document.getElementById("results"); | ||
| 9 | var solverSelector = document.getElementById("solverSelector"); | ||
| 10 | |||
| 11 | var lastCallbackId = 0; | ||
| 12 | var callbacks = new Map(); // Values: { f: function, arg: object } | ||
| 13 | var worker = new Worker("./worker.mjs", { type: "module" }); | ||
| 14 | worker.onmessage = (event) => { | ||
| 15 | if (event.data.command == "log") { | ||
| 16 | console.log(event.data.object); // TODO | ||
| 17 | } else if (!callbacks.has(event.data.id)) { | ||
| 18 | console.log("[nissy app] Unknown callback " + event.data.id + | ||
| 19 | " for command " + event.data.command); | ||
| 20 | } else { | ||
| 21 | var callback = callbacks.get(event.data.id); | ||
| 22 | callback.f(callback.arg, event.data.arg); | ||
| 23 | } | ||
| 24 | }; | ||
| 25 | |||
| 26 | function updateResults(label, results, buttonState) { | ||
| 27 | resultsLabel.innerText = label; | ||
| 28 | resultsText.innerText = results; | ||
| 29 | solveButton.disable = !buttonState; | ||
| 30 | } | ||
| 31 | |||
| 32 | solveButton.addEventListener("click", () => { | ||
| 33 | updateResults("Loading solutions...", "", false); | ||
| 34 | const callbackId = ++lastCallbackId; | ||
| 35 | callbacks.set(callbackId, { | ||
| 36 | f: (callbackArg, solveResult) => { | ||
| 37 | if (solveResult.success) { | ||
| 38 | const n = solveResult.solutions.length; | ||
| 39 | const label = n == 0 ? "No solution found" : | ||
| 40 | ("Found " + n + " solution" + (n == 1 ? "" : "s")) + ":"; | ||
| 41 | const text = solveResult.solutions.join("\n"); | ||
| 42 | updateResults(label, text, true); | ||
| 43 | } else { | ||
| 44 | updateResults("Unexpected error while solving!", "", true); | ||
| 45 | } | ||
| 46 | }, | ||
| 47 | arg: "" // Currently unused | ||
| 48 | }); | ||
| 49 | worker.postMessage({ | ||
| 50 | command: "solve", | ||
| 51 | id: callbackId, | ||
| 52 | arg: { | ||
| 53 | solver: solverSelector.options[solverSelector.selectedIndex].value, | ||
| 54 | scramble: scrField.value, | ||
| 55 | minmoves: 0, | ||
| 56 | maxmoves: 20, | ||
| 57 | maxsolutions: 1, | ||
| 58 | optimal: 20, | ||
| 59 | threads: window.navigator.hardwareConcurrency, | ||
| 60 | } | ||
| 61 | }); | ||
| 62 | }); | ||
diff --git a/web/http/solve.mjs b/web/http/solve.mjs deleted file mode 100644 index 127e1e3..0000000 --- a/web/http/solve.mjs +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 1 | var solveButton = document.getElementById("solveButton"); | ||
| 2 | var scrField = document.getElementById("scrambleText"); | ||
| 3 | var solutionText = document.getElementById("solution"); | ||
| 4 | var solverSelector = document.getElementById("solverSelector"); | ||
| 5 | |||
| 6 | var worker = new Worker("./worker.mjs", { type: "module" }); | ||
| 7 | worker.onmessage = (e) => { | ||
| 8 | updateResults(e.data); | ||
| 9 | enableSolveButton(); | ||
| 10 | }; | ||
| 11 | |||
| 12 | function updateResults(s) { | ||
| 13 | solutionText.innerText = s; | ||
| 14 | } | ||
| 15 | |||
| 16 | function disableSolveButton() { | ||
| 17 | solveButton.disabled = true; | ||
| 18 | solveButton.innerText = "Solving..."; | ||
| 19 | } | ||
| 20 | |||
| 21 | function enableSolveButton() { | ||
| 22 | solveButton.disabled = false; | ||
| 23 | solveButton.innerText = "Solve!"; | ||
| 24 | } | ||
| 25 | |||
| 26 | solveButton.addEventListener("click", () => { | ||
| 27 | disableSolveButton(); | ||
| 28 | updateResults("Loading solutions..."); | ||
| 29 | worker.postMessage({ | ||
| 30 | solver: solverSelector.options[solverSelector.selectedIndex].value, | ||
| 31 | scramble: scrField.value | ||
| 32 | }); | ||
| 33 | }); | ||
diff --git a/web/http/worker.mjs b/web/http/worker.mjs deleted file mode 100644 index 7ce1070..0000000 --- a/web/http/worker.mjs +++ /dev/null | |||
| @@ -1,27 +0,0 @@ | |||
| 1 | import Nissy from "./nissy_web_module.mjs" | ||
| 2 | |||
| 3 | const nissy = await Nissy(); | ||
| 4 | nissy.setLogger(nissy._addCallbackFunction(console.log)); | ||
| 5 | |||
| 6 | onmessage = (e) => { | ||
| 7 | //TODO: try to load from storage first, then download | ||
| 8 | nissy.initSolverDownload(e.data.solver, "/tables").then( | ||
| 9 | (download_result) => { | ||
| 10 | if (!download_result) { | ||
| 11 | postMessage("Error retrieving the solver data"); | ||
| 12 | } else { | ||
| 13 | var cube = new nissy.Cube(); | ||
| 14 | cube.move(e.data.scramble); | ||
| 15 | |||
| 16 | nissy.solve(e.data.solver, cube, nissy.NissFlag.normal, | ||
| 17 | 0, 17, 1, 99, 4, -1).then( | ||
| 18 | (solve_result) => { | ||
| 19 | if (!solve_result.err.ok()) | ||
| 20 | postMessage("Error while solving (solve returned " + | ||
| 21 | solve_result.err.value + ")"); | ||
| 22 | else | ||
| 23 | postMessage(solve_result.solutions) | ||
| 24 | }); | ||
| 25 | } | ||
| 26 | }); | ||
| 27 | }; | ||
diff --git a/web/worker.mjs b/web/worker.mjs new file mode 100644 index 0000000..dca980e --- /dev/null +++ b/web/worker.mjs | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | import Nissy from "./nissy_web_module.mjs" | ||
| 2 | |||
| 3 | const nissy = await Nissy(); | ||
| 4 | function log(message) { | ||
| 5 | postMessage({ command: "log", id: -1, object: message }); | ||
| 6 | console.log(message); | ||
| 7 | } | ||
| 8 | nissy.setLogger(nissy._addCallbackFunction(log)); | ||
| 9 | |||
| 10 | const commands = [ | ||
| 11 | { name: "solve", exec: solve }, | ||
| 12 | { name: "download solver data", exec: downloadSolverData }, | ||
| 13 | ]; | ||
| 14 | |||
| 15 | // Message structure: | ||
| 16 | // { | ||
| 17 | // command: string // The function to run. | ||
| 18 | // id: number // An identification number for the command, used for | ||
| 19 | // // sending responses back to the caller and having them | ||
| 20 | // // caught by the correct handler. | ||
| 21 | // arg: object // The actual argument of the message, command-specific. | ||
| 22 | // // See below for details. | ||
| 23 | // } | ||
| 24 | onmessage = (event) => { | ||
| 25 | for (var i = 0; i < commands.length; i++) { | ||
| 26 | if (commands[i].name == event.data.command) { | ||
| 27 | commands[i].exec(event.data.id, event.data.arg); | ||
| 28 | return; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | log("[nissy worker] unknown command " + event.data.command); | ||
| 33 | }; | ||
| 34 | |||
| 35 | // Download solver data. | ||
| 36 | // Argument: string | ||
| 37 | async function downloadSolverData(id, solver) { | ||
| 38 | const downloadSolverDataReturn = (success, message = "") => postMessage({ | ||
| 39 | command: "download solver data", | ||
| 40 | id: id, | ||
| 41 | arg: { | ||
| 42 | success: success, | ||
| 43 | message: message | ||
| 44 | } | ||
| 45 | }); | ||
| 46 | |||
| 47 | if (await nissy.initSolverDownload(arg.solver, "/tables")) { | ||
| 48 | downloadSolverDataReturn(true); | ||
| 49 | } else { | ||
| 50 | downloadSolverDataReturn(false, "Error retrieving the solver data"); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | // Solve the cube with the given options. | ||
| 55 | // Argument: { | ||
| 56 | // solver: string | ||
| 57 | // scramble: string | ||
| 58 | // minmoves: number | ||
| 59 | // maxmoves: number | ||
| 60 | // maxsolutions: number | ||
| 61 | // optimal: number | ||
| 62 | // threads: number | ||
| 63 | // } | ||
| 64 | async function solve(id, arg) { | ||
| 65 | const solveReturn = (success, solutions = [], message = "") => postMessage({ | ||
| 66 | command: "solve", | ||
| 67 | id: id, | ||
| 68 | arg: { | ||
| 69 | success: success, | ||
| 70 | solutions: solutions, | ||
| 71 | message: message | ||
| 72 | } | ||
| 73 | }); | ||
| 74 | |||
| 75 | if (!(await nissy.isSolverAvailable(arg.solver))) { | ||
| 76 | solveReturn(false, [], "Error: solver " + arg.solver + | ||
| 77 | " is not available in this version of nissy."); | ||
| 78 | return; | ||
| 79 | } | ||
| 80 | |||
| 81 | if (!(await nissy.isSolverLoaded(arg.solver)) && | ||
| 82 | !(await nissy.initSolverFromStorage(arg.solver))) { | ||
| 83 | solveReturn(false, [], "Error: solver " + arg.solver + " has not been " + | ||
| 84 | "loaded. Its data must be donwloaded or generated before using it."); | ||
| 85 | return; | ||
| 86 | } | ||
| 87 | |||
| 88 | var cube = new nissy.Cube(); | ||
| 89 | cube.move(arg.scramble); | ||
| 90 | |||
| 91 | // TODO: error handling here? | ||
| 92 | const nissFlag = ((str) => { | ||
| 93 | switch (str) { | ||
| 94 | case "inverse": return nissy.NissFlag.inverse; | ||
| 95 | case "mixed": return nissy.NissFlag.mixed; | ||
| 96 | case "linear": return nissy.NissFlag.linear; | ||
| 97 | case "all": return nissy.NissFlag.all; | ||
| 98 | default: return nissy.NissFlag.normal; | ||
| 99 | } | ||
| 100 | })(arg.nissFlag); | ||
| 101 | |||
| 102 | const result = await nissy.solve(arg.solver, cube, nissFlag, arg.minmoves, | ||
| 103 | arg.maxmoves, arg.maxsolutions, arg.optimal, arg.threads, id); | ||
| 104 | |||
| 105 | if (result.err.ok()) { | ||
| 106 | //TODO: add solution lenght? | ||
| 107 | var solutions = result.solutions == "" ? [] : result.solutions.split("\n"); | ||
| 108 | solutions.pop(); // Solver always returns string ending in newline | ||
| 109 | solveReturn(true, solutions); | ||
| 110 | } else { | ||
| 111 | solveReturn(false, [], | ||
| 112 | "Error while solving (error " + result.err.value + ")"); | ||
| 113 | } | ||
| 114 | }; | ||
