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 fa0fa1fcb494483020a736a2bc2c5c639a2bd870
parent 87852933851674cacbc8b2295d046831db27ab81
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Mon, 26 May 2025 17:05:36 +0200

Progress with web version

Diffstat:
Mbuild | 2+-
Mweb/adapter.cpp | 179+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Aweb/http/tables | 2++
Mweb/http/worker.mjs | 30++++++++++++++++++++----------
Mweb/logging.h | 17-----------------
Mweb/storage.cpp | 35++++++++++++++++++++++++++++++++---
Mweb/storage.h | 6++++--
7 files changed, 176 insertions(+), 95 deletions(-)

diff --git a/build b/build @@ -131,7 +131,7 @@ WASMCFLAGS="-std=c11 -fPIC -D_POSIX_C_SOURCE=199309L -pthread" WASMMFLAGS="-DTHREADS=$THREADS -DWASMSIMD" WASMLINKFLAGS="--no-entry -sEXPORT_NAME='Nissy' -sMODULARIZE \ -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=5MB -sPTHREAD_POOL_SIZE=$THREADS \ - -sASYNCIFY -sLINKABLE -sEXPORT_ALL" + -sFETCH -sASYNCIFY -sLINKABLE -sEXPORT_ALL" if (command -v "python3-config" >/dev/null 2>&1) ; then PYTHON3_INCLUDES="$(python3-config --includes)" diff --git a/web/adapter.cpp b/web/adapter.cpp @@ -1,5 +1,12 @@ +extern "C" { + extern int addCallbackFunction(/* args intentionally unspecified */); + extern void callFunction(int, const char *); + extern int callFunctionInt(int); +} + #include "../cpp/nissy.h" #include "storage.h" +#include "logging.h" #include <emscripten.h> #include <emscripten/bind.h> @@ -10,78 +17,130 @@ EM_ASYNC_JS(void, fake_async, (), {}); -extern "C" { - extern int addCallbackFunction(/* args intentionally unspecified */); - extern void callFunction(int, const char *); - extern int callFunctionInt(int); +const std::set<std::string> available_solvers +{ + "h48h0k4", + "h48h1k2", + "h48h2k2", + "h48h3k2", + "h48h7k2", +}; + +std::map<std::string, nissy::solver> loaded_solvers; + +bool is_solver_available(const std::string& name) +{ + return available_solvers.contains(name); } -static int logger_id = -1; +bool is_solver_loaded(const std::string& name) +{ + return loaded_solvers.contains(name); +} -void log(std::string s) +bool check_data(nissy::solver& solver) { - if (logger_id == -1) - return; + log("Checking data integrity " + "(this is done only once per session per solver)...\n"); + + if (!solver.check_data().ok()) { + log("Error! Data is corrupted!\n"); + return false; + } - callFunction(logger_id, s.c_str()); + return true; } -void log_wrapper(const char *cstr, void *data) +bool read_solver_data(nissy::solver& solver) { - log(cstr); + solver.data.resize(solver.size); + + bool success = storage::read(solver.id, solver.size, + reinterpret_cast<char *>(solver.data.data())); + + if (!success) { + log("Could not read data for solver " + + solver.name + " from storage\n"); + return false; + } + + if (!check_data(solver)) { + log("Data for solver " + solver.name + " is corrupt!\n"); + return false; + } + + log("Data for solver " + solver.name + " read from storage\n"); + loaded_solvers.insert({solver.name, solver}); + + return true; } -void set_logger(int id) +bool is_solver_valid(const std::string& name, + std::variant<nissy::solver, nissy::error>& se) { - logger_id = id; - nissy::set_logger(log_wrapper, NULL); + if (std::holds_alternative<nissy::error>(se)) { + log("Invalid solver " + name + "\n"); + return false; + } + + if (!is_solver_available(name)) { + log("Solver " + name + " is not available in this version\n"); + return false; + } + + return true; } -// Some of the solvers are not available to the JS interface because of -// memory limitations. -const std::set<std::string> available_solvers +bool init_solver_from_storage(const std::string& name) { - "h48h0k4", - "h48h1k2", - "h48h2k2", - "h48h3k2", - "h48h7k2", -}; + if (is_solver_loaded(name)) + return true; + auto se = nissy::solver::get(name); + if (!is_solver_valid(name, se)) + return false; + nissy::solver solver = std::get<nissy::solver>(se); -std::map<std::string, nissy::solver> loaded_solvers; + return read_solver_data(solver); +} -// TODO: this should ask the user if they want to download or generate. -bool init_solver(const std::string& name) +bool init_solver_download(const std::string& name, const std::string& urlbase) { + if (is_solver_loaded(name)) + return true; auto se = nissy::solver::get(name); + if (!is_solver_valid(name, se)) + return false; nissy::solver solver = std::get<nissy::solver>(se); - solver.data.resize(solver.size); - if (storage::read(solver.id, solver.size, - reinterpret_cast<char *>(solver.data.data()))) { - log("Data for solver " + solver.name + " read from storage\n"); + if (storage::download(solver.id, urlbase + "/" + solver.id)) { + return read_solver_data(solver); } else { - log("Could not read data for solver " + solver.name + - " from storage, generating it\n"); - auto err = solver.generate_data(); - - if (!err.ok()) { - log("Error generating the data!\n"); - return false; - } + return false; } +} - log("Checking data integrity " - "(this is done only once per session per solver)...\n"); - if (!solver.check_data().ok()) { - log("Error! Data is corrupted!\n"); +bool init_solver_generate(const std::string& name) +{ + if (is_solver_loaded(name)) + return true; + auto se = nissy::solver::get(name); + if (!is_solver_valid(name, se)) + return false; + nissy::solver solver = std::get<nissy::solver>(se); + + if (!solver.generate_data().ok()) { + log("Error generating data for solver " + name + "!\n"); + return false; + } + + if (!check_data(solver)) { + log("Data for solver " + name + " generated incorrectly!\n"); return false; } - loaded_solvers.insert({name, solver}); if (storage::write(solver.id, solver.size, reinterpret_cast<const char *>(solver.data.data()))) { - log("Data for solver " + solver.name + " stored\n"); + log("Data for solver " + name + " stored\n"); } else { log("Error storing the data (the solver is usable, " "but the data will have to be re-generated next " @@ -91,21 +150,6 @@ bool init_solver(const std::string& name) return true; } -bool solver_valid(const std::string& name) -{ - if (loaded_solvers.contains(name) || - (available_solvers.contains(name) && init_solver(name))) - return true; - - auto se = nissy::solver::get(name); - if (std::holds_alternative<nissy::solver>(se)) - log("The solver " + name + " is not available in " - "the web version of Nissy. Use a native version.\n"); - else - log("Invalid solver " + name + "\n"); - return false; -} - int poll_status(void *arg) { if (arg == NULL || *(int *)arg == -1) @@ -127,9 +171,11 @@ nissy::solver::solve_result solve(std::string name, // TODO figure out if there is a better way to do this. fake_async(); - if (!solver_valid(name)) + if (!is_solver_loaded(name)) { + log("Solver " + name + " is invalid or has not been loaded\n"); return nissy::solver::solve_result {.err = nissy::error::INVALID_SOLVER}; + } return loaded_solvers.at(name).solve(cube, nissflag, minmoves, maxmoves, maxsols, optimal, threads, NULL, &poll_status_id); @@ -147,8 +193,10 @@ EMSCRIPTEN_BINDINGS(Nissy) emscripten::class_<nissy::error>("Error") .function("ok", &nissy::error::ok) - .class_property("unsolvableWarning", &nissy::error::UNSOLVABLE_WARNING) - .class_property("unsolvableError", &nissy::error::UNSOLVABLE_ERROR) + .class_property("unsolvableWarning", + &nissy::error::UNSOLVABLE_WARNING) + .class_property("unsolvableError", + &nissy::error::UNSOLVABLE_ERROR) .class_property("invalidCube", &nissy::error::INVALID_CUBE) .class_property("invalidMoves", &nissy::error::INVALID_MOVES) .class_property("invalidTrans", &nissy::error::INVALID_TRANS) @@ -182,6 +230,13 @@ EMSCRIPTEN_BINDINGS(Nissy) .property("solutions", &nissy::solver::solve_result::solutions) ; + emscripten::function("isSolverAvailable", &is_solver_available); + emscripten::function("isSolverLoaded", &is_solver_loaded); + emscripten::function("initSolverFromStorage", + &init_solver_from_storage); + emscripten::function("initSolverDownload", &init_solver_download); + emscripten::function("initSolverGenerate", &init_solver_generate); + emscripten::function("countMoves", &nissy::count_moves); emscripten::function("solve", &solve, emscripten::return_value_policy::take_ownership()); diff --git a/web/http/tables b/web/http/tables @@ -0,0 +1 @@ +../../tables/ +\ No newline at end of file diff --git a/web/http/worker.mjs b/web/http/worker.mjs @@ -4,14 +4,24 @@ const nissy = await Nissy(); nissy.setLogger(nissy._addCallbackFunction(console.log)); onmessage = (e) => { - 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()) - posMessage("Error while solving (solve returned " + - solve_result.err.value + ")"); - else - postMessage(solve_result.solutions) - }); +//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/logging.h b/web/logging.h @@ -1,18 +1,3 @@ -#ifndef LOGGING_H -#define LOGGING_H - -#include "../cpp/nissy.h" -#include <emscripten/bind.h> -#include <string> - -#define LOGGING_EMBIND \ - emscripten::function("addCallbackFunction", &addCallbackFunction); - -extern "C" { - extern int addCallbackFunction(); - extern void callFunction(int, const char *); -} - static int logger_id = -1; void log(std::string s) @@ -33,5 +18,3 @@ void set_logger(int id) logger_id = id; nissy::set_logger(log_wrapper, NULL); } - -#endif diff --git a/web/storage.cpp b/web/storage.cpp @@ -13,13 +13,12 @@ std::string getprefix() { } EM_ASYNC_JS(int, loadfs, (), { - const dir = '/tables'; const inBrowser = typeof window !== 'undefined'; const inWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; + console.assert(inBrowser || inWorker, "Non-browsers not supported"); - if (!(inBrowser || inWorker)) - return; + const dir = '/tables'; if (!FS.analyzePath(dir).exists) FS.mkdir(dir); @@ -38,6 +37,30 @@ EM_ASYNC_JS(int, loadfs, (), { } }); +EM_ASYNC_JS(int, download_and_store, (const char *key, const char *url), { + const inBrowser = typeof window !== 'undefined'; + const inWorker = typeof WorkerGlobalScope !== 'undefined' && + self instanceof WorkerGlobalScope; + console.assert(inBrowser || inWorker, "Non-browsers not supported"); + + url = UTF8ToString(url); + key = UTF8ToString(key); + let response = await fetch(url); + if (!response.ok) { + console.log("Error downloading data for " + key); + console.log("" + response.status + ": " + response.statusText); + return 0; + } + + console.log("Data for " + key + " downloaded, writing to storage..."); + let data = await response.bytes(); + var stream = FS.open("/tables/" + key, "w+"); + FS.write(stream, data, 0, data.length, 0); + FS.close(stream); + console.log("Data for " + key + " stored (" + data.length + " bytes)"); + return 1; +}); + bool storage::read(std::string key, size_t data_size, char *data) { loadfs(); @@ -65,3 +88,9 @@ bool storage::write(std::string key, size_t data_size, const char *data) return !ofs.fail(); } + +int storage::download(std::string key, std::string url) +{ + loadfs(); + return download_and_store(key.c_str(), url.c_str()); +} diff --git a/web/storage.h b/web/storage.h @@ -1,7 +1,9 @@ #include <string> #include <fstream> +#include <emscripten/fetch.h> namespace storage { - bool read(std::string, size_t, char *); - bool write(std::string, size_t, const char *); + bool read(std::string key, size_t data_size, char *data); + bool write(std::string key, size_t data_size, const char *data); + int download(std::string key, std::string url); }