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 (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 }