diff options
Diffstat (limited to '')
| -rwxr-xr-x | build | 2 | ||||
| -rw-r--r-- | web/adapter.cpp | 177 | ||||
| l--------- | web/http/tables | 1 | ||||
| -rw-r--r-- | web/http/worker.mjs | 30 | ||||
| -rw-r--r-- | web/logging.h | 17 | ||||
| -rw-r--r-- | web/storage.cpp | 35 | ||||
| -rw-r--r-- | web/storage.h | 6 |
7 files changed, 174 insertions, 94 deletions
| @@ -131,7 +131,7 @@ WASMCFLAGS="-std=c11 -fPIC -D_POSIX_C_SOURCE=199309L -pthread" | |||
| 131 | WASMMFLAGS="-DTHREADS=$THREADS -DWASMSIMD" | 131 | WASMMFLAGS="-DTHREADS=$THREADS -DWASMSIMD" |
| 132 | WASMLINKFLAGS="--no-entry -sEXPORT_NAME='Nissy' -sMODULARIZE \ | 132 | WASMLINKFLAGS="--no-entry -sEXPORT_NAME='Nissy' -sMODULARIZE \ |
| 133 | -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=5MB -sPTHREAD_POOL_SIZE=$THREADS \ | 133 | -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=5MB -sPTHREAD_POOL_SIZE=$THREADS \ |
| 134 | -sASYNCIFY -sLINKABLE -sEXPORT_ALL" | 134 | -sFETCH -sASYNCIFY -sLINKABLE -sEXPORT_ALL" |
| 135 | 135 | ||
| 136 | if (command -v "python3-config" >/dev/null 2>&1) ; then | 136 | if (command -v "python3-config" >/dev/null 2>&1) ; then |
| 137 | PYTHON3_INCLUDES="$(python3-config --includes)" | 137 | PYTHON3_INCLUDES="$(python3-config --includes)" |
diff --git a/web/adapter.cpp b/web/adapter.cpp index 71a34ab..676390c 100644 --- a/web/adapter.cpp +++ b/web/adapter.cpp | |||
| @@ -1,5 +1,12 @@ | |||
| 1 | extern "C" { | ||
| 2 | extern int addCallbackFunction(/* args intentionally unspecified */); | ||
| 3 | extern void callFunction(int, const char *); | ||
| 4 | extern int callFunctionInt(int); | ||
| 5 | } | ||
| 6 | |||
| 1 | #include "../cpp/nissy.h" | 7 | #include "../cpp/nissy.h" |
| 2 | #include "storage.h" | 8 | #include "storage.h" |
| 9 | #include "logging.h" | ||
| 3 | 10 | ||
| 4 | #include <emscripten.h> | 11 | #include <emscripten.h> |
| 5 | #include <emscripten/bind.h> | 12 | #include <emscripten/bind.h> |
| @@ -10,78 +17,130 @@ | |||
| 10 | 17 | ||
| 11 | EM_ASYNC_JS(void, fake_async, (), {}); | 18 | EM_ASYNC_JS(void, fake_async, (), {}); |
| 12 | 19 | ||
| 13 | extern "C" { | 20 | const std::set<std::string> available_solvers |
| 14 | extern int addCallbackFunction(/* args intentionally unspecified */); | 21 | { |
| 15 | extern void callFunction(int, const char *); | 22 | "h48h0k4", |
| 16 | extern int callFunctionInt(int); | 23 | "h48h1k2", |
| 24 | "h48h2k2", | ||
| 25 | "h48h3k2", | ||
| 26 | "h48h7k2", | ||
| 27 | }; | ||
| 28 | |||
| 29 | std::map<std::string, nissy::solver> loaded_solvers; | ||
| 30 | |||
| 31 | bool is_solver_available(const std::string& name) | ||
| 32 | { | ||
| 33 | return available_solvers.contains(name); | ||
| 17 | } | 34 | } |
| 18 | 35 | ||
| 19 | static int logger_id = -1; | 36 | bool is_solver_loaded(const std::string& name) |
| 37 | { | ||
| 38 | return loaded_solvers.contains(name); | ||
| 39 | } | ||
| 20 | 40 | ||
| 21 | void log(std::string s) | 41 | bool check_data(nissy::solver& solver) |
| 22 | { | 42 | { |
| 23 | if (logger_id == -1) | 43 | log("Checking data integrity " |
| 24 | return; | 44 | "(this is done only once per session per solver)...\n"); |
| 25 | 45 | ||
| 26 | callFunction(logger_id, s.c_str()); | 46 | if (!solver.check_data().ok()) { |
| 47 | log("Error! Data is corrupted!\n"); | ||
| 48 | return false; | ||
| 49 | } | ||
| 50 | |||
| 51 | return true; | ||
| 27 | } | 52 | } |
| 28 | 53 | ||
| 29 | void log_wrapper(const char *cstr, void *data) | 54 | bool read_solver_data(nissy::solver& solver) |
| 30 | { | 55 | { |
| 31 | log(cstr); | 56 | solver.data.resize(solver.size); |
| 57 | |||
| 58 | bool success = storage::read(solver.id, solver.size, | ||
| 59 | reinterpret_cast<char *>(solver.data.data())); | ||
| 60 | |||
| 61 | if (!success) { | ||
| 62 | log("Could not read data for solver " + | ||
| 63 | solver.name + " from storage\n"); | ||
| 64 | return false; | ||
| 65 | } | ||
| 66 | |||
| 67 | if (!check_data(solver)) { | ||
| 68 | log("Data for solver " + solver.name + " is corrupt!\n"); | ||
| 69 | return false; | ||
| 70 | } | ||
| 71 | |||
| 72 | log("Data for solver " + solver.name + " read from storage\n"); | ||
| 73 | loaded_solvers.insert({solver.name, solver}); | ||
| 74 | |||
| 75 | return true; | ||
| 32 | } | 76 | } |
| 33 | 77 | ||
| 34 | void set_logger(int id) | 78 | bool is_solver_valid(const std::string& name, |
| 79 | std::variant<nissy::solver, nissy::error>& se) | ||
| 35 | { | 80 | { |
| 36 | logger_id = id; | 81 | if (std::holds_alternative<nissy::error>(se)) { |
| 37 | nissy::set_logger(log_wrapper, NULL); | 82 | log("Invalid solver " + name + "\n"); |
| 83 | return false; | ||
| 84 | } | ||
| 85 | |||
| 86 | if (!is_solver_available(name)) { | ||
| 87 | log("Solver " + name + " is not available in this version\n"); | ||
| 88 | return false; | ||
| 89 | } | ||
| 90 | |||
| 91 | return true; | ||
| 38 | } | 92 | } |
| 39 | 93 | ||
| 40 | // Some of the solvers are not available to the JS interface because of | 94 | bool init_solver_from_storage(const std::string& name) |
| 41 | // memory limitations. | ||
| 42 | const std::set<std::string> available_solvers | ||
| 43 | { | 95 | { |
| 44 | "h48h0k4", | 96 | if (is_solver_loaded(name)) |
| 45 | "h48h1k2", | 97 | return true; |
| 46 | "h48h2k2", | 98 | auto se = nissy::solver::get(name); |
| 47 | "h48h3k2", | 99 | if (!is_solver_valid(name, se)) |
| 48 | "h48h7k2", | 100 | return false; |
| 49 | }; | 101 | nissy::solver solver = std::get<nissy::solver>(se); |
| 50 | 102 | ||
| 51 | std::map<std::string, nissy::solver> loaded_solvers; | 103 | return read_solver_data(solver); |
| 104 | } | ||
| 52 | 105 | ||
| 53 | // TODO: this should ask the user if they want to download or generate. | 106 | bool init_solver_download(const std::string& name, const std::string& urlbase) |
| 54 | bool init_solver(const std::string& name) | ||
| 55 | { | 107 | { |
| 108 | if (is_solver_loaded(name)) | ||
| 109 | return true; | ||
| 56 | auto se = nissy::solver::get(name); | 110 | auto se = nissy::solver::get(name); |
| 111 | if (!is_solver_valid(name, se)) | ||
| 112 | return false; | ||
| 57 | nissy::solver solver = std::get<nissy::solver>(se); | 113 | nissy::solver solver = std::get<nissy::solver>(se); |
| 58 | 114 | ||
| 59 | solver.data.resize(solver.size); | 115 | if (storage::download(solver.id, urlbase + "/" + solver.id)) { |
| 60 | if (storage::read(solver.id, solver.size, | 116 | return read_solver_data(solver); |
| 61 | reinterpret_cast<char *>(solver.data.data()))) { | ||
| 62 | log("Data for solver " + solver.name + " read from storage\n"); | ||
| 63 | } else { | 117 | } else { |
| 64 | log("Could not read data for solver " + solver.name + | 118 | return false; |
| 65 | " from storage, generating it\n"); | 119 | } |
| 66 | auto err = solver.generate_data(); | 120 | } |
| 67 | 121 | ||
| 68 | if (!err.ok()) { | 122 | bool init_solver_generate(const std::string& name) |
| 69 | log("Error generating the data!\n"); | 123 | { |
| 70 | return false; | 124 | if (is_solver_loaded(name)) |
| 71 | } | 125 | return true; |
| 126 | auto se = nissy::solver::get(name); | ||
| 127 | if (!is_solver_valid(name, se)) | ||
| 128 | return false; | ||
| 129 | nissy::solver solver = std::get<nissy::solver>(se); | ||
| 130 | |||
| 131 | if (!solver.generate_data().ok()) { | ||
| 132 | log("Error generating data for solver " + name + "!\n"); | ||
| 133 | return false; | ||
| 72 | } | 134 | } |
| 73 | 135 | ||
| 74 | log("Checking data integrity " | 136 | if (!check_data(solver)) { |
| 75 | "(this is done only once per session per solver)...\n"); | 137 | log("Data for solver " + name + " generated incorrectly!\n"); |
| 76 | if (!solver.check_data().ok()) { | ||
| 77 | log("Error! Data is corrupted!\n"); | ||
| 78 | return false; | 138 | return false; |
| 79 | } | 139 | } |
| 80 | loaded_solvers.insert({name, solver}); | ||
| 81 | 140 | ||
| 82 | if (storage::write(solver.id, solver.size, | 141 | if (storage::write(solver.id, solver.size, |
| 83 | reinterpret_cast<const char *>(solver.data.data()))) { | 142 | reinterpret_cast<const char *>(solver.data.data()))) { |
| 84 | log("Data for solver " + solver.name + " stored\n"); | 143 | log("Data for solver " + name + " stored\n"); |
| 85 | } else { | 144 | } else { |
| 86 | log("Error storing the data (the solver is usable, " | 145 | log("Error storing the data (the solver is usable, " |
| 87 | "but the data will have to be re-generated next " | 146 | "but the data will have to be re-generated next " |
| @@ -91,21 +150,6 @@ bool init_solver(const std::string& name) | |||
| 91 | return true; | 150 | return true; |
| 92 | } | 151 | } |
| 93 | 152 | ||
| 94 | bool solver_valid(const std::string& name) | ||
| 95 | { | ||
| 96 | if (loaded_solvers.contains(name) || | ||
| 97 | (available_solvers.contains(name) && init_solver(name))) | ||
| 98 | return true; | ||
| 99 | |||
| 100 | auto se = nissy::solver::get(name); | ||
| 101 | if (std::holds_alternative<nissy::solver>(se)) | ||
| 102 | log("The solver " + name + " is not available in " | ||
| 103 | "the web version of Nissy. Use a native version.\n"); | ||
| 104 | else | ||
| 105 | log("Invalid solver " + name + "\n"); | ||
| 106 | return false; | ||
| 107 | } | ||
| 108 | |||
| 109 | int poll_status(void *arg) | 153 | int poll_status(void *arg) |
| 110 | { | 154 | { |
| 111 | if (arg == NULL || *(int *)arg == -1) | 155 | if (arg == NULL || *(int *)arg == -1) |
| @@ -127,9 +171,11 @@ nissy::solver::solve_result solve(std::string name, | |||
| 127 | // TODO figure out if there is a better way to do this. | 171 | // TODO figure out if there is a better way to do this. |
| 128 | fake_async(); | 172 | fake_async(); |
| 129 | 173 | ||
| 130 | if (!solver_valid(name)) | 174 | if (!is_solver_loaded(name)) { |
| 175 | log("Solver " + name + " is invalid or has not been loaded\n"); | ||
| 131 | return nissy::solver::solve_result | 176 | return nissy::solver::solve_result |
| 132 | {.err = nissy::error::INVALID_SOLVER}; | 177 | {.err = nissy::error::INVALID_SOLVER}; |
| 178 | } | ||
| 133 | 179 | ||
| 134 | return loaded_solvers.at(name).solve(cube, nissflag, minmoves, | 180 | return loaded_solvers.at(name).solve(cube, nissflag, minmoves, |
| 135 | maxmoves, maxsols, optimal, threads, NULL, &poll_status_id); | 181 | maxmoves, maxsols, optimal, threads, NULL, &poll_status_id); |
| @@ -147,8 +193,10 @@ EMSCRIPTEN_BINDINGS(Nissy) | |||
| 147 | 193 | ||
| 148 | emscripten::class_<nissy::error>("Error") | 194 | emscripten::class_<nissy::error>("Error") |
| 149 | .function("ok", &nissy::error::ok) | 195 | .function("ok", &nissy::error::ok) |
| 150 | .class_property("unsolvableWarning", &nissy::error::UNSOLVABLE_WARNING) | 196 | .class_property("unsolvableWarning", |
| 151 | .class_property("unsolvableError", &nissy::error::UNSOLVABLE_ERROR) | 197 | &nissy::error::UNSOLVABLE_WARNING) |
| 198 | .class_property("unsolvableError", | ||
| 199 | &nissy::error::UNSOLVABLE_ERROR) | ||
| 152 | .class_property("invalidCube", &nissy::error::INVALID_CUBE) | 200 | .class_property("invalidCube", &nissy::error::INVALID_CUBE) |
| 153 | .class_property("invalidMoves", &nissy::error::INVALID_MOVES) | 201 | .class_property("invalidMoves", &nissy::error::INVALID_MOVES) |
| 154 | .class_property("invalidTrans", &nissy::error::INVALID_TRANS) | 202 | .class_property("invalidTrans", &nissy::error::INVALID_TRANS) |
| @@ -182,6 +230,13 @@ EMSCRIPTEN_BINDINGS(Nissy) | |||
| 182 | .property("solutions", &nissy::solver::solve_result::solutions) | 230 | .property("solutions", &nissy::solver::solve_result::solutions) |
| 183 | ; | 231 | ; |
| 184 | 232 | ||
| 233 | emscripten::function("isSolverAvailable", &is_solver_available); | ||
| 234 | emscripten::function("isSolverLoaded", &is_solver_loaded); | ||
| 235 | emscripten::function("initSolverFromStorage", | ||
| 236 | &init_solver_from_storage); | ||
| 237 | emscripten::function("initSolverDownload", &init_solver_download); | ||
| 238 | emscripten::function("initSolverGenerate", &init_solver_generate); | ||
| 239 | |||
| 185 | emscripten::function("countMoves", &nissy::count_moves); | 240 | emscripten::function("countMoves", &nissy::count_moves); |
| 186 | emscripten::function("solve", &solve, | 241 | emscripten::function("solve", &solve, |
| 187 | emscripten::return_value_policy::take_ownership()); | 242 | emscripten::return_value_policy::take_ownership()); |
diff --git a/web/http/tables b/web/http/tables new file mode 120000 index 0000000..b3afcac --- /dev/null +++ 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 index 831ef09..7ce1070 100644 --- a/web/http/worker.mjs +++ b/web/http/worker.mjs | |||
| @@ -4,14 +4,24 @@ const nissy = await Nissy(); | |||
| 4 | nissy.setLogger(nissy._addCallbackFunction(console.log)); | 4 | nissy.setLogger(nissy._addCallbackFunction(console.log)); |
| 5 | 5 | ||
| 6 | onmessage = (e) => { | 6 | onmessage = (e) => { |
| 7 | var cube = new nissy.Cube(); | 7 | //TODO: try to load from storage first, then download |
| 8 | cube.move(e.data.scramble); | 8 | nissy.initSolverDownload(e.data.solver, "/tables").then( |
| 9 | nissy.solve(e.data.solver, cube, nissy.NissFlag.normal, 0, 17, 1, 99, 4, -1) | 9 | (download_result) => { |
| 10 | .then((solve_result) => { | 10 | if (!download_result) { |
| 11 | if (!solve_result.err.ok()) | 11 | postMessage("Error retrieving the solver data"); |
| 12 | posMessage("Error while solving (solve returned " + | 12 | } else { |
| 13 | solve_result.err.value + ")"); | 13 | var cube = new nissy.Cube(); |
| 14 | else | 14 | cube.move(e.data.scramble); |
| 15 | postMessage(solve_result.solutions) | 15 | |
| 16 | }); | 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 | }); | ||
| 17 | }; | 27 | }; |
diff --git a/web/logging.h b/web/logging.h index b20a595..9ed4835 100644 --- a/web/logging.h +++ b/web/logging.h | |||
| @@ -1,18 +1,3 @@ | |||
| 1 | #ifndef LOGGING_H | ||
| 2 | #define LOGGING_H | ||
| 3 | |||
| 4 | #include "../cpp/nissy.h" | ||
| 5 | #include <emscripten/bind.h> | ||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #define LOGGING_EMBIND \ | ||
| 9 | emscripten::function("addCallbackFunction", &addCallbackFunction); | ||
| 10 | |||
| 11 | extern "C" { | ||
| 12 | extern int addCallbackFunction(); | ||
| 13 | extern void callFunction(int, const char *); | ||
| 14 | } | ||
| 15 | |||
| 16 | static int logger_id = -1; | 1 | static int logger_id = -1; |
| 17 | 2 | ||
| 18 | void log(std::string s) | 3 | void log(std::string s) |
| @@ -33,5 +18,3 @@ void set_logger(int id) | |||
| 33 | logger_id = id; | 18 | logger_id = id; |
| 34 | nissy::set_logger(log_wrapper, NULL); | 19 | nissy::set_logger(log_wrapper, NULL); |
| 35 | } | 20 | } |
| 36 | |||
| 37 | #endif | ||
diff --git a/web/storage.cpp b/web/storage.cpp index 4d0959f..cbf9e9c 100644 --- a/web/storage.cpp +++ b/web/storage.cpp | |||
| @@ -13,13 +13,12 @@ std::string getprefix() { | |||
| 13 | } | 13 | } |
| 14 | 14 | ||
| 15 | EM_ASYNC_JS(int, loadfs, (), { | 15 | EM_ASYNC_JS(int, loadfs, (), { |
| 16 | const dir = '/tables'; | ||
| 17 | const inBrowser = typeof window !== 'undefined'; | 16 | const inBrowser = typeof window !== 'undefined'; |
| 18 | const inWorker = typeof WorkerGlobalScope !== 'undefined' && | 17 | const inWorker = typeof WorkerGlobalScope !== 'undefined' && |
| 19 | self instanceof WorkerGlobalScope; | 18 | self instanceof WorkerGlobalScope; |
| 19 | console.assert(inBrowser || inWorker, "Non-browsers not supported"); | ||
| 20 | 20 | ||
| 21 | if (!(inBrowser || inWorker)) | 21 | const dir = '/tables'; |
| 22 | return; | ||
| 23 | 22 | ||
| 24 | if (!FS.analyzePath(dir).exists) | 23 | if (!FS.analyzePath(dir).exists) |
| 25 | FS.mkdir(dir); | 24 | FS.mkdir(dir); |
| @@ -38,6 +37,30 @@ EM_ASYNC_JS(int, loadfs, (), { | |||
| 38 | } | 37 | } |
| 39 | }); | 38 | }); |
| 40 | 39 | ||
| 40 | EM_ASYNC_JS(int, download_and_store, (const char *key, const char *url), { | ||
| 41 | const inBrowser = typeof window !== 'undefined'; | ||
| 42 | const inWorker = typeof WorkerGlobalScope !== 'undefined' && | ||
| 43 | self instanceof WorkerGlobalScope; | ||
| 44 | console.assert(inBrowser || inWorker, "Non-browsers not supported"); | ||
| 45 | |||
| 46 | url = UTF8ToString(url); | ||
| 47 | key = UTF8ToString(key); | ||
| 48 | let response = await fetch(url); | ||
| 49 | if (!response.ok) { | ||
| 50 | console.log("Error downloading data for " + key); | ||
| 51 | console.log("" + response.status + ": " + response.statusText); | ||
| 52 | return 0; | ||
| 53 | } | ||
| 54 | |||
| 55 | console.log("Data for " + key + " downloaded, writing to storage..."); | ||
| 56 | let data = await response.bytes(); | ||
| 57 | var stream = FS.open("/tables/" + key, "w+"); | ||
| 58 | FS.write(stream, data, 0, data.length, 0); | ||
| 59 | FS.close(stream); | ||
| 60 | console.log("Data for " + key + " stored (" + data.length + " bytes)"); | ||
| 61 | return 1; | ||
| 62 | }); | ||
| 63 | |||
| 41 | bool storage::read(std::string key, size_t data_size, char *data) | 64 | bool storage::read(std::string key, size_t data_size, char *data) |
| 42 | { | 65 | { |
| 43 | loadfs(); | 66 | loadfs(); |
| @@ -65,3 +88,9 @@ bool storage::write(std::string key, size_t data_size, const char *data) | |||
| 65 | 88 | ||
| 66 | return !ofs.fail(); | 89 | return !ofs.fail(); |
| 67 | } | 90 | } |
| 91 | |||
| 92 | int storage::download(std::string key, std::string url) | ||
| 93 | { | ||
| 94 | loadfs(); | ||
| 95 | return download_and_store(key.c_str(), url.c_str()); | ||
| 96 | } | ||
diff --git a/web/storage.h b/web/storage.h index fa6b748..b491742 100644 --- a/web/storage.h +++ b/web/storage.h | |||
| @@ -1,7 +1,9 @@ | |||
| 1 | #include <string> | 1 | #include <string> |
| 2 | #include <fstream> | 2 | #include <fstream> |
| 3 | #include <emscripten/fetch.h> | ||
| 3 | 4 | ||
| 4 | namespace storage { | 5 | namespace storage { |
| 5 | bool read(std::string, size_t, char *); | 6 | bool read(std::string key, size_t data_size, char *data); |
| 6 | bool write(std::string, size_t, const char *); | 7 | bool write(std::string key, size_t data_size, const char *data); |
| 8 | int download(std::string key, std::string url); | ||
| 7 | } | 9 | } |
