commit fa0fa1fcb494483020a736a2bc2c5c639a2bd870
parent 87852933851674cacbc8b2295d046831db27ab81
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date: Mon, 26 May 2025 17:05:36 +0200
Progress with web version
Diffstat:
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);
}