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