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 #include #include #include #include #include EM_ASYNC_JS(void, fake_async, (), {}); const std::set available_solvers { "h48h0k4", "h48h1k2", "h48h2k2", "h48h3k2", "h48h4k2", "h48h5k2", }; std::map loaded_solvers; bool is_solver_available(const std::string& name) { return available_solvers.contains(name); } bool is_solver_loaded(const std::string& name) { return loaded_solvers.contains(name); } bool check_data(nissy::solver& solver) { 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; } return true; } bool read_solver_data(nissy::solver& solver) { solver.data.resize(solver.size); bool success = storage::read(solver.id, solver.size, reinterpret_cast(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; } bool is_solver_valid(const std::string& name, std::variant& se) { if (std::holds_alternative(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; } bool init_solver_from_storage(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(se); return read_solver_data(solver); } 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(se); if (storage::download(solver.id, urlbase + "/" + solver.id)) { return read_solver_data(solver); } else { return false; } } 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(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; } if (storage::write(solver.id, solver.size, reinterpret_cast(solver.data.data()))) { 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 " "time you want to use it)\n"); } return true; } int poll_status(void *arg) { if (arg == NULL || *(int *)arg == -1) return nissy::status::RUN.value; return callFunctionInt(*(int *)arg); } nissy::solver::solve_result solve(std::string name, nissy::cube cube, nissy::nissflag nissflag, unsigned minmoves, unsigned maxmoves, unsigned maxsols, unsigned optimal, unsigned threads, int poll_status_id) { // Here we use a dirty trick to make this function always return the // same kind of JavaScript object. If we did not do this, the returned // object would be a Promise on the first run of the solver for each // session (because when loading the table some async JS code is // called), and a regular object otherwise. // TODO figure out if there is a better way to do this. fake_async(); 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, poll_status, &poll_status_id); } EMSCRIPTEN_BINDINGS(Nissy) { emscripten::class_("NissFlag") .class_property("normal", &nissy::nissflag::NORMAL) .class_property("inverse", &nissy::nissflag::INVERSE) .class_property("mixed", &nissy::nissflag::MIXED) .class_property("linear", &nissy::nissflag::LINEAR) .class_property("all", &nissy::nissflag::ALL) ; emscripten::class_("Error") .function("ok", &nissy::error::ok) .property("value", &nissy::error::value) .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) .class_property("invalidSolver", &nissy::error::INVALID_SOLVER) .class_property("nullPointer", &nissy::error::NULL_POINTER) .class_property("bufferSize", &nissy::error::BUFFER_SIZE) .class_property("data", &nissy::error::DATA) .class_property("options", &nissy::error::OPTIONS) .class_property("unknown", &nissy::error::UNKNOWN) ; emscripten::constant("statusRUN", nissy::status::RUN.value); emscripten::constant("statusSTOP", nissy::status::STOP.value); emscripten::constant("statusPAUSE", nissy::status::PAUSE.value); emscripten::class_("Status") .class_property("run", &nissy::status::RUN) .class_property("stop", &nissy::status::STOP) .class_property("pause", &nissy::status::PAUSE) ; emscripten::class_("Cube") .constructor<>() .function("move", &nissy::cube::move) .function("transform", &nissy::cube::transform) .function("invert", &nissy::cube::invert) .function("toString", &nissy::cube::to_string) ; emscripten::class_("SolveResult") .property("err", &nissy::solver::solve_result::err) .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()); emscripten::function("setLogger", &set_logger, emscripten::allow_raw_pointers()); emscripten::function("addCallbackFunction", &addCallbackFunction); }