From 37a2ebd4bffa99d465396fde0acfc52284ee3b7c Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Fri, 30 May 2025 14:06:34 +0200 Subject: More improvements to web solver --- src/solvers/h48/solve.h | 16 ++++----- web/adapter.cpp | 5 +-- web/http/index.html | 77 ++++++++++++++++++++++++++-------------- web/http/nissyapp.mjs | 94 ++++++++++++++++++++++++++++++++++++++++++++++--- web/http/worker.mjs | 28 +++++++++++++-- 5 files changed, 176 insertions(+), 44 deletions(-) diff --git a/src/solvers/h48/solve.h b/src/solvers/h48/solve.h index bf34a2b..348c122 100644 --- a/src/solvers/h48/solve.h +++ b/src/solvers/h48/solve.h @@ -417,8 +417,8 @@ solve_h48( ) { int i, ntasks, eoesep_table_index; - bool td, fp; - _Atomic int status; + bool td; + _Atomic int status, prev_status; size_t lastused; int8_t d; dfsarg_solve_h48_t arg[THREADS]; @@ -556,7 +556,6 @@ solve_h48( /* Log solutions and handle pause / stop / resume */ if (poll_status != NULL && d >= 15 && NISSY_CANSLEEP) { td = false; - fp = true; while (!td && status != NISSY_STATUS_STOP) { msleep(BASE_SLEEP_TIME); @@ -565,13 +564,14 @@ solve_h48( lastused = sollist.used; pthread_mutex_unlock(&solutions_mutex); + prev_status = status; status = poll_status(poll_status_data); - if (status == NISSY_STATUS_PAUSE && fp) { - LOG("[H48 solve] Paused\n"); - fp = false; + if (status != prev_status) { + if (status == NISSY_STATUS_PAUSE) + LOG("[H48 solve] Paused\n"); + if (status == NISSY_STATUS_RUN) + LOG("[H48 solve] Resumed\n"); } - if (status == NISSY_STATUS_RUN) - fp = true; for (td = true, i = 0; i < threads; i++) td = td && arg[i].thread_done; diff --git a/web/adapter.cpp b/web/adapter.cpp index 31d00fe..0022b0d 100644 --- a/web/adapter.cpp +++ b/web/adapter.cpp @@ -145,7 +145,7 @@ bool init_solver_generate(const std::string& name) } else { log("Error storing the data (the solver is usable, " "but the data will have to be re-generated next " - "time you want to use it)"); + "time you want to use it)\n"); } return true; @@ -179,7 +179,7 @@ nissy::solver::solve_result solve(std::string name, } return loaded_solvers.at(name).solve(cube, nissflag, minmoves, - maxmoves, maxsols, optimal, threads, NULL, &poll_status_id); + maxmoves, maxsols, optimal, threads, poll_status, &poll_status_id); } EMSCRIPTEN_BINDINGS(Nissy) @@ -194,6 +194,7 @@ EMSCRIPTEN_BINDINGS(Nissy) emscripten::class_("Error") .function("ok", &nissy::error::ok) + .property("value", &nissy::error::value) .class_property("unsolvableWarning", &nissy::error::UNSOLVABLE_WARNING) .class_property("unsolvableError", diff --git a/web/http/index.html b/web/http/index.html index 04ddf4c..bc6a37d 100644 --- a/web/http/index.html +++ b/web/http/index.html @@ -1,13 +1,16 @@ - - - - Nissy - H48 solver POC - - - - + + + + Nissy - H48 solver POC + + + + + +
+ - -

-

- - - + + +
+ +
+
+ +
+
+ +
+ + +
+ + + + +

+

+ + + + + diff --git a/web/http/nissyapp.mjs b/web/http/nissyapp.mjs index 1f9fa72..bdc553a 100644 --- a/web/http/nissyapp.mjs +++ b/web/http/nissyapp.mjs @@ -3,16 +3,31 @@ // to this folder when running ./build web. var solveButton = document.getElementById("solveButton"); +var pauseResumeButton = document.getElementById("pauseResumeButton"); +var cancelSolveButton = document.getElementById("cancelSolveButton"); 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 minSlider = document.getElementById("minMovesSlider"); +var maxSlider = document.getElementById("maxMovesSlider"); +var minLabel = document.getElementById("minLabel"); +var maxLabel = document.getElementById("maxLabel"); +var maxSolsInput = document.getElementById("maxSolutions"); +var solveStatus = "run"; // For now this is global var lastCallbackId = 0; var callbacks = new Map(); // Values: { f: function, arg: object } var worker = new Worker("./worker.mjs", { type: "module" }); + +// Periodically send status updates to the worker + +const sendStatusUpdateToWorker = () => + worker.postMessage({command: "update status", id: -1, arg: solveStatus }); +setInterval(() => sendStatusUpdateToWorker(), 500); + worker.onmessage = (event) => { if (event.data.command == "log") { logPane.innerText += event.data.object; @@ -32,8 +47,48 @@ function updateResults(label, results, enable) { resultsText.innerText = results; solveButton.disabled = !enable; solverSelector.disabled = !enable; + minSlider.disabled = !enable; + maxSlider.disabled = !enable; + maxSolsInput.disabled = !enable; } +scrField.addEventListener("input", (e) => { + const scramble = scrField.value; + + const callbackId = ++lastCallbackId; + callbacks.set(callbackId, { + f: (callbackArg, validateResult) => { + if (validateResult) { + scrField.style.border = ""; + } else { + scrField.style.border = "2px solid red"; + } + }, + arg: scramble + }); + + worker.postMessage({ + command: "validate scramble", + id: callbackId, + arg: scramble + }); +}); + +minSlider.addEventListener("input", () => + minLabel.innerText = "Minimum moves: " + minSlider.value); + +maxSlider.addEventListener("input", () => + maxLabel.innerText = "Maximum moves: " + maxSlider.value); + +maxSolsInput.addEventListener("keyup", () => { + if (maxSolsInput.value != "") { + if (parseInt(maxSolsInput.value) < parseInt(maxSolsInput.min)) + maxSolsInput.value = maxSolsInput.min; + if (parseInt(maxSolsInput.value) > parseInt(maxSolsInput.max)) + maxSolsInput.value = maxSolsInput.max; + } +}); + var logVisible = false; toggleLog.addEventListener("click", () => { logVisible = !logVisible; @@ -67,7 +122,7 @@ solveButton.addEventListener("click", () => { worker.postMessage({ command: "validate scramble", - id: lastCallbackId, + id: callbackId, arg: scramble }); }); @@ -110,7 +165,10 @@ function askDownloadThenSolve(solver, scramble) { updateResults("", "", false); confirmDiv.style.display = "block"; - cancel.addEventListener("click", cleanup); + cancel.addEventListener("click", () => { + cleanup(); + updateResults("", "", true); + }); download.addEventListener("click", () => { cleanup(); @@ -153,12 +211,38 @@ function downloadOrGenerateThenSolve(solver, scramble, download) { }); } +pauseResumeButton.addEventListener("click", (e) => { + if (pauseResumeButton.innerText == "Pause") { + solveStatus = "pause"; + pauseResumeButton.innerText = "Resume"; + updateResults("Solver paused - click Resume to continue", "", false); + } else { + solveStatus = "run"; + pauseResumeButton.innerText = "Pause"; + updateResults("Solving...", "", false); + } +}); + +cancelSolveButton.addEventListener("click", (e) => { + solveStatus = "stop"; + pauseResumeButton.innerText = "Pause"; +}); + function startSolve(solver, scramble) { updateResults("Solving...", "", false); const callbackId = ++lastCallbackId; + pauseResumeButton.disabled = false; + cancelSolveButton.disabled = false; + solveStatus = "run"; + callbacks.set(callbackId, { f: (callbackArg, solveResult) => { + pauseResumeButton.disabled = true; + cancelSolveButton.disabled = true; + pauseResumeButton.innerText = "Pause"; + solveStatus = "stop"; + if (solveResult.success) { const n = solveResult.solutions.length; const label = n == 0 ? "No solution found" : @@ -178,9 +262,9 @@ function startSolve(solver, scramble) { arg: { solver: solver, scramble: scramble, - minmoves: 0, - maxmoves: 20, - maxsolutions: 1, + minmoves: minSlider.value, + maxmoves: maxSlider.value, + maxsolutions: maxSolsInput.value, optimal: 20, threads: window.navigator.hardwareConcurrency, } diff --git a/web/http/worker.mjs b/web/http/worker.mjs index 12de1dc..d912e8b 100644 --- a/web/http/worker.mjs +++ b/web/http/worker.mjs @@ -4,12 +4,19 @@ const nissy = await Nissy(); const log = (msg) => postMessage({ command: "log", id: -1, object: msg }); nissy.setLogger(nissy._addCallbackFunction(log)); +var solveStatus = nissy.statusRUN; // For now this is a global variable +const pollStatusCallback = nissy._addCallbackFunction(() => { +console.log("Calling pollstatus, returning " + solveStatus); +return solveStatus; +}); + const commands = [ { name: "load solver data", exec: loadSolverDataFromStorage }, { name: "download solver data", exec: downloadSolverData }, { name: "generate solver data", exec: generateSolverData }, { name: "validate scramble", exec: validateScramble }, { name: "solve", exec: solve }, + { name: "update status", exec: updateStatus }, ]; // Message structure: @@ -161,10 +168,11 @@ async function solve(id, arg) { return; } + solveStatus = nissy.statusRUN; + var cube = new nissy.Cube(); cube.move(arg.scramble); - // TODO: error handling here? const nissFlag = ((str) => { switch (str) { case "inverse": return nissy.NissFlag.inverse; @@ -176,15 +184,29 @@ async function solve(id, arg) { })(arg.nissFlag); const result = await nissy.solve(arg.solver, cube, nissFlag, arg.minmoves, - arg.maxmoves, arg.maxsolutions, arg.optimal, arg.threads, id); + arg.maxmoves, arg.maxsolutions, arg.optimal, arg.threads, + pollStatusCallback); 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 + for (var i = 0; i < solutions.length; i++) { + const movecount = await nissy.countMoves(solutions[i]); + solutions[i] += " (" + movecount.value + ")"; + } async_return(true, solutions); } else { async_return(false, [], "Error while solving (error " + result.err.value + ")"); } }; + +function updateStatus(id, arg) { + if (arg == "stop") { + solveStatus = nissy.statusSTOP; + } else if (arg == "pause") { + solveStatus = nissy.statusPAUSE; + } else { + solveStatus = nissy.statusRUN; + } +}; -- cgit v1.3