adapter.cpp (7275B)
1 #include "../cpp/nissy.h" 2 #include "storage.h" 3 #include "logging.h" 4 5 #include <emscripten.h> 6 #include <emscripten/bind.h> 7 #include <functional> 8 #include <map> 9 #include <set> 10 #include <string> 11 #include <vector> 12 13 EM_ASYNC_JS(void, fake_async, (), {}); 14 15 std::map<std::string, nissy::solver> loaded_solvers; 16 17 const std::set<std::string> available_solvers 18 { 19 "h48h1", 20 "h48h2", 21 "h48h3", 22 "h48h4", 23 "h48h5", 24 }; 25 26 bool is_solver_available(const std::string& name) 27 { 28 return available_solvers.contains(name); 29 } 30 31 bool is_solver_loaded(const std::string& name) 32 { 33 return loaded_solvers.contains(name); 34 } 35 36 bool check_data(nissy::solver& solver) 37 { 38 log("Checking data integrity " 39 "(this is done only once per session per solver)...\n"); 40 41 if (!solver.check_data().ok()) { 42 log("Error! Data is corrupted!\n"); 43 return false; 44 } 45 46 return true; 47 } 48 49 bool read_solver_data(nissy::solver& solver) 50 { 51 solver.data.resize(solver.size); 52 53 bool success = storage::read(solver.id, solver.size, 54 reinterpret_cast<char *>(solver.data.data())); 55 56 if (!success) { 57 log("Could not read data for solver " + 58 solver.name + " from storage\n"); 59 return false; 60 } 61 62 if (!check_data(solver)) { 63 log("Data for solver " + solver.name + " is corrupt!\n"); 64 return false; 65 } 66 67 log("Data for solver " + solver.name + " read from storage\n"); 68 loaded_solvers.insert({solver.name, solver}); 69 70 return true; 71 } 72 73 bool is_solver_valid(const std::string& name, 74 std::variant<nissy::solver, nissy::error>& se) 75 { 76 if (std::holds_alternative<nissy::error>(se)) { 77 log("Invalid solver " + name + "\n"); 78 return false; 79 } 80 81 if (!is_solver_available(name)) { 82 log("Solver " + name + " is not available in this version\n"); 83 return false; 84 } 85 86 return true; 87 } 88 89 bool init_solver_from_storage(const std::string& name) 90 { 91 if (is_solver_loaded(name)) 92 return true; 93 auto se = nissy::solver::get(name); 94 if (!is_solver_valid(name, se)) 95 return false; 96 nissy::solver solver = std::get<nissy::solver>(se); 97 98 return read_solver_data(solver); 99 } 100 101 bool init_solver_download(const std::string& name, const std::string& urlbase) 102 { 103 if (is_solver_loaded(name)) 104 return true; 105 auto se = nissy::solver::get(name); 106 if (!is_solver_valid(name, se)) 107 return false; 108 nissy::solver solver = std::get<nissy::solver>(se); 109 110 if (storage::download(solver.id, urlbase + "/" + solver.id)) { 111 return read_solver_data(solver); 112 } else { 113 return false; 114 } 115 } 116 117 bool init_solver_generate(const std::string& name) 118 { 119 if (is_solver_loaded(name)) 120 return true; 121 auto se = nissy::solver::get(name); 122 if (!is_solver_valid(name, se)) 123 return false; 124 nissy::solver solver = std::get<nissy::solver>(se); 125 126 if (!solver.generate_data().ok()) { 127 log("Error generating data for solver " + name + "!\n"); 128 return false; 129 } 130 131 if (!check_data(solver)) { 132 log("Data for solver " + name + " generated incorrectly!\n"); 133 return false; 134 } 135 136 if (storage::write(solver.id, solver.size, 137 reinterpret_cast<const char *>(solver.data.data()))) { 138 log("Data for solver " + name + " stored\n"); 139 } else { 140 log("Error storing the data (the solver is usable, " 141 "but the data will have to be re-generated next " 142 "time you want to use it)\n"); 143 } 144 145 return true; 146 } 147 148 int poll_status(void *arg) 149 { 150 if (arg == nullptr) 151 return nissy::status::RUN.value; 152 153 std::function<int(void)> poll((int (*)(void))*(int *)arg); 154 return poll(); 155 } 156 157 // The parameter js_poll_status is of type int here, but actually it is a 158 // pointer to a JS function. The type will have to be changed to a 64-bit 159 // integer when we move to WASM64. 160 int js_poll_status_global = 0; 161 nissy::solver::solve_result solve(std::string name, 162 nissy::cube cube, nissy::nissflag nissflag, unsigned minmoves, 163 unsigned maxmoves, unsigned maxsols, unsigned optimal, unsigned threads, 164 int js_poll_status) 165 { 166 // Here we use a dirty trick to make this function always return the 167 // same kind of JavaScript object. If we did not do this, the returned 168 // object would be a Promise on the first run of the solver for each 169 // session (because when loading the table some async JS code is 170 // called), and a regular object otherwise. 171 // TODO figure out if there is a better way to do this. 172 fake_async(); 173 174 if (!is_solver_loaded(name)) { 175 log("Solver " + name + " is invalid or has not been loaded\n"); 176 return nissy::solver::solve_result 177 {.err = nissy::error::INVALID_SOLVER}; 178 } 179 180 // TODO: when running multiple solvers at the same time, we could use 181 // poll_status_id as intended (i.e. an id of some sort) 182 js_poll_status_global = js_poll_status; 183 return loaded_solvers.at(name).solve(cube, nissflag, minmoves, 184 maxmoves, maxsols, optimal, threads, 185 poll_status, &js_poll_status_global); 186 } 187 188 EMSCRIPTEN_BINDINGS(Nissy) 189 { 190 emscripten::class_<nissy::nissflag>("NissFlag") 191 .class_property("normal", &nissy::nissflag::NORMAL) 192 .class_property("inverse", &nissy::nissflag::INVERSE) 193 .class_property("mixed", &nissy::nissflag::MIXED) 194 .class_property("linear", &nissy::nissflag::LINEAR) 195 .class_property("all", &nissy::nissflag::ALL) 196 ; 197 198 emscripten::class_<nissy::error>("Error") 199 .function("ok", &nissy::error::ok) 200 .property("value", &nissy::error::value) 201 .class_property("unsolvableWarning", 202 &nissy::error::UNSOLVABLE_WARNING) 203 .class_property("unsolvableError", 204 &nissy::error::UNSOLVABLE_ERROR) 205 .class_property("invalidCube", &nissy::error::INVALID_CUBE) 206 .class_property("invalidMoves", &nissy::error::INVALID_MOVES) 207 .class_property("invalidTrans", &nissy::error::INVALID_TRANS) 208 .class_property("invalidSolver", &nissy::error::INVALID_SOLVER) 209 .class_property("nullPointer", &nissy::error::NULL_POINTER) 210 .class_property("bufferSize", &nissy::error::BUFFER_SIZE) 211 .class_property("data", &nissy::error::DATA) 212 .class_property("options", &nissy::error::OPTIONS) 213 .class_property("unknown", &nissy::error::UNKNOWN) 214 ; 215 216 emscripten::constant("statusRUN", nissy::status::RUN.value); 217 emscripten::constant("statusSTOP", nissy::status::STOP.value); 218 emscripten::constant("statusPAUSE", nissy::status::PAUSE.value); 219 emscripten::class_<nissy::status>("Status") 220 .class_property("run", &nissy::status::RUN) 221 .class_property("stop", &nissy::status::STOP) 222 .class_property("pause", &nissy::status::PAUSE) 223 ; 224 225 emscripten::class_<nissy::cube>("Cube") 226 .constructor<>() 227 .function("move", &nissy::cube::move) 228 .function("transform", &nissy::cube::transform) 229 .function("invert", &nissy::cube::invert) 230 .function("toString", &nissy::cube::to_string) 231 ; 232 233 emscripten::class_<nissy::solver::solve_result>("SolveResult") 234 .property("err", &nissy::solver::solve_result::err) 235 .property("solutions", &nissy::solver::solve_result::solutions) 236 ; 237 238 emscripten::function("isSolverAvailable", &is_solver_available); 239 emscripten::function("isSolverLoaded", &is_solver_loaded); 240 emscripten::function("initSolverFromStorage", 241 &init_solver_from_storage); 242 emscripten::function("initSolverDownload", &init_solver_download); 243 emscripten::function("initSolverGenerate", &init_solver_generate); 244 245 emscripten::function("countMoves", &nissy::count_moves); 246 emscripten::function("solve", &solve, 247 emscripten::return_value_policy::take_ownership()); 248 emscripten::function("setLogger", &set_logger, 249 emscripten::allow_raw_pointers()); 250 }