nissy-core

The "engine" of nissy, including the H48 optimal solver.
git clone https://git.tronto.net/nissy-core
Download | Log | Files | Refs | README | LICENSE

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 }