diff options
| author | Sebastiano Tronto <sebastiano@tronto.net> | 2025-08-14 13:32:19 +0200 |
|---|---|---|
| committer | Sebastiano Tronto <sebastiano@tronto.net> | 2025-08-14 13:32:19 +0200 |
| commit | c835bf15da3ce39b856222d352c800f6ebd51acf (patch) | |
| tree | 22f44fb6e766d9758a72182cef1dca7131e7a3a7 /python | |
| parent | 325778cb5f4b169ef7e197e179cfb889808d10c2 (diff) | |
| download | nissy-core-c835bf15da3ce39b856222d352c800f6ebd51acf.tar.gz nissy-core-c835bf15da3ce39b856222d352c800f6ebd51acf.zip | |
Added attempt to make pause / resume work with python module
Diffstat (limited to '')
| -rw-r--r-- | python/examples/pause_resume.py | 35 | ||||
| -rw-r--r-- | python/examples/solve.py | 6 | ||||
| -rw-r--r-- | python/nissy_module.c | 67 |
3 files changed, 98 insertions, 10 deletions
diff --git a/python/examples/pause_resume.py b/python/examples/pause_resume.py new file mode 100644 index 0000000..5daebeb --- /dev/null +++ b/python/examples/pause_resume.py | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | # This example is meant to test the pause / resume / stop mechanism | ||
| 2 | # See the solve.py example for more details on how this works | ||
| 3 | |||
| 4 | import sys, os, time | ||
| 5 | |||
| 6 | sys.path.append(os.getcwd()) | ||
| 7 | sys.path.append(os.getcwd() + os.path.sep + "python") | ||
| 8 | import nissy | ||
| 9 | |||
| 10 | # Function used to pause, resume and kill the solver | ||
| 11 | t0 = time.time() | ||
| 12 | def poll(): | ||
| 13 | t = time.time() - t0 | ||
| 14 | if t < 3: | ||
| 15 | print("[{}s] pausing for the first 3 seconds".format(t)) | ||
| 16 | return nissy.status_pause | ||
| 17 | if t < 6: | ||
| 18 | print("[{}s] Letting the solver run for 3 seconds".format(t)) | ||
| 19 | return nissy.status_run | ||
| 20 | print("[{}s] Too much time has passed, giving up".format(t)) | ||
| 21 | return nissy.status_stop | ||
| 22 | |||
| 23 | solver = "h48h0k4" | ||
| 24 | datapath = "tables" + os.path.sep + solver | ||
| 25 | if os.path.exists(datapath): | ||
| 26 | data = bytearray(open(datapath, "rb").read()) | ||
| 27 | else: | ||
| 28 | data = nissy.gendata("h48h0k4") | ||
| 29 | print("Generated data will NOT be persisted") | ||
| 30 | |||
| 31 | # Scramble is 19 moves optimal, should take a while with the h48h0k4 solver | ||
| 32 | scramble = "R' U' F D R F2 D L F D2 F2 L' U R' L2 D' R2 F2 R2 D L2 U2 R' U' F" | ||
| 33 | cube = nissy.applymoves(nissy.solved_cube, scramble); | ||
| 34 | |||
| 35 | nissy.solve(cube, solver, nissy.nissflag_normal, 0, 9, 3, 20, 4, data, poll) | ||
diff --git a/python/examples/solve.py b/python/examples/solve.py index 75e7914..c2c992b 100644 --- a/python/examples/solve.py +++ b/python/examples/solve.py | |||
| @@ -3,12 +3,10 @@ | |||
| 3 | # Run "./build.sh python", then run this from either the top-level directory | 3 | # Run "./build.sh python", then run this from either the top-level directory |
| 4 | # of the nissy-core repo or from the python subdirectory. | 4 | # of the nissy-core repo or from the python subdirectory. |
| 5 | 5 | ||
| 6 | # Append the directories to the python path so we can load the module | 6 | # Append the directories to the python path and import |
| 7 | import sys, os | 7 | import sys, os |
| 8 | sys.path.append(os.getcwd()) | 8 | sys.path.append(os.getcwd()) |
| 9 | sys.path.append(os.getcwd() + os.path.sep + "python") | 9 | sys.path.append(os.getcwd() + os.path.sep + "python") |
| 10 | |||
| 11 | # Import with a nicer name | ||
| 12 | import nissy | 10 | import nissy |
| 13 | 11 | ||
| 14 | # Choose the solver you prefer | 12 | # Choose the solver you prefer |
| @@ -26,7 +24,7 @@ else: | |||
| 26 | cube = nissy.applymoves(nissy.solved_cube, "U F R2"); | 24 | cube = nissy.applymoves(nissy.solved_cube, "U F R2"); |
| 27 | 25 | ||
| 28 | # Solve! | 26 | # Solve! |
| 29 | solutions = nissy.solve(cube, solver, nissy.nissflag_normal, 0, 9, 3, 20, 4, data) | 27 | solutions = nissy.solve(cube, solver, nissy.nissflag_normal, 0, 9, 3, 20, 4, data, None) |
| 30 | 28 | ||
| 31 | # Print the solutions, one per line | 29 | # Print the solutions, one per line |
| 32 | print("Found ", len(solutions), " solutions:") | 30 | print("Found ", len(solutions), " solutions:") |
diff --git a/python/nissy_module.c b/python/nissy_module.c index bff4ead..23ff589 100644 --- a/python/nissy_module.c +++ b/python/nissy_module.c | |||
| @@ -1,3 +1,9 @@ | |||
| 1 | /* | ||
| 2 | This version of the python module includes an attempt to use the callback | ||
| 3 | functions for pausing / stopping / resuming a solve. It probably cannot | ||
| 4 | work until PEP 703 (freethreading python) is implemented. | ||
| 5 | */ | ||
| 6 | |||
| 1 | #define PY_SSIZE_T_CLEAN | 7 | #define PY_SSIZE_T_CLEAN |
| 2 | #include <Python.h> | 8 | #include <Python.h> |
| 3 | #include <stdbool.h> | 9 | #include <stdbool.h> |
| @@ -6,6 +12,11 @@ | |||
| 6 | 12 | ||
| 7 | #define MAX_SOLUTIONS_SIZE 250000 | 13 | #define MAX_SOLUTIONS_SIZE 250000 |
| 8 | 14 | ||
| 15 | typedef struct { | ||
| 16 | PyObject *callback; | ||
| 17 | PyThreadState *thread_state; | ||
| 18 | } thread_callback_t; | ||
| 19 | |||
| 9 | static bool | 20 | static bool |
| 10 | check_error(long long err) | 21 | check_error(long long err) |
| 11 | { | 22 | { |
| @@ -84,6 +95,32 @@ long_result(long long result) | |||
| 84 | return PyLong_FromLong(result); | 95 | return PyLong_FromLong(result); |
| 85 | } | 96 | } |
| 86 | 97 | ||
| 98 | static int | ||
| 99 | callback_wrapper(void *data) | ||
| 100 | { | ||
| 101 | thread_callback_t *arg = data; | ||
| 102 | int r; | ||
| 103 | PyObject *result; | ||
| 104 | PyGILState_STATE gstate; | ||
| 105 | |||
| 106 | if (arg->callback != NULL && PyCallable_Check(arg->callback) == 1) { | ||
| 107 | |||
| 108 | PyEval_RestoreThread(arg->thread_state); | ||
| 109 | gstate = PyGILState_Ensure(); | ||
| 110 | result = PyObject_CallNoArgs(data); | ||
| 111 | PyGILState_Release(gstate); | ||
| 112 | arg->thread_state = PyEval_SaveThread(); | ||
| 113 | |||
| 114 | if (result == NULL) | ||
| 115 | return 0; | ||
| 116 | Py_XDECREF(result); | ||
| 117 | r = PyLong_AsInt(result); | ||
| 118 | return r >= 0 && r <= 2 ? r : 0; | ||
| 119 | } else { | ||
| 120 | return NISSY_STATUS_RUN; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 87 | PyDoc_STRVAR(inverse_doc, | 124 | PyDoc_STRVAR(inverse_doc, |
| 88 | "inverse(cube)\n" | 125 | "inverse(cube)\n" |
| 89 | "--\n\n" | 126 | "--\n\n" |
| @@ -288,7 +325,7 @@ checkdata(PyObject *self, PyObject *args) | |||
| 288 | 325 | ||
| 289 | PyDoc_STRVAR(solve_doc, | 326 | PyDoc_STRVAR(solve_doc, |
| 290 | "solve(cube, solver, nissflag, minmoves, maxmoves, maxsolutions," | 327 | "solve(cube, solver, nissflag, minmoves, maxmoves, maxsolutions," |
| 291 | " optimal, threads, data)\n" | 328 | " optimal, threads, data, callback)\n" |
| 292 | "--\n\n" | 329 | "--\n\n" |
| 293 | "Solves the given 'cube' with the given 'solver' and other parameters." | 330 | "Solves the given 'cube' with the given 'solver' and other parameters." |
| 294 | "See the documentation for libnissy (in nissy.h) for details.\n" | 331 | "See the documentation for libnissy (in nissy.h) for details.\n" |
| @@ -302,6 +339,9 @@ PyDoc_STRVAR(solve_doc, | |||
| 302 | " - optimal: the largest number of moves from the shortest solution\n" | 339 | " - optimal: the largest number of moves from the shortest solution\n" |
| 303 | " - threads: the number of threads to use (0 for default)\n" | 340 | " - threads: the number of threads to use (0 for default)\n" |
| 304 | " - data: a bytearray containing the data for the solver\n" | 341 | " - data: a bytearray containing the data for the solver\n" |
| 342 | " - callback: a function that returns 0 (run), 1 (stop) or 2 (pause).\n" | ||
| 343 | " Polled by the solver to determine if the user requested the\n" | ||
| 344 | " to pause or stop the solve. Not used by all solvers.\n" | ||
| 305 | "\n" | 345 | "\n" |
| 306 | "Returns: a list with the solutions found\n" | 346 | "Returns: a list with the solutions found\n" |
| 307 | ); | 347 | ); |
| @@ -315,17 +355,29 @@ solve(PyObject *self, PyObject *args) | |||
| 315 | char solutions[MAX_SOLUTIONS_SIZE]; | 355 | char solutions[MAX_SOLUTIONS_SIZE]; |
| 316 | long long stats[NISSY_SIZE_SOLVE_STATS]; | 356 | long long stats[NISSY_SIZE_SOLVE_STATS]; |
| 317 | PyByteArrayObject *data; | 357 | PyByteArrayObject *data; |
| 358 | PyObject *callback; | ||
| 359 | thread_callback_t callback_data; | ||
| 318 | 360 | ||
| 319 | if (!PyArg_ParseTuple(args, "ssIIIIIIY", &cube, &solver, &nissflag, | 361 | if (!PyArg_ParseTuple(args, "ssIIIIIIYO", &cube, &solver, &nissflag, |
| 320 | &minmoves, &maxmoves, &maxsolutions, &optimal, &threads, &data)) | 362 | &minmoves, &maxmoves, &maxsolutions, &optimal, &threads, &data, |
| 363 | &callback)) | ||
| 321 | return NULL; | 364 | return NULL; |
| 322 | 365 | ||
| 323 | Py_BEGIN_ALLOW_THREADS | 366 | printf("Checking callback here:\n"); |
| 367 | PyObject *rrr = PyObject_CallNoArgs(callback); | ||
| 368 | printf("result: %d\n", PyLong_AsInt(rrr)); | ||
| 369 | |||
| 370 | callback_data.callback = callback; | ||
| 371 | callback_data.thread_state = PyEval_SaveThread(); | ||
| 372 | //Py_BEGIN_ALLOW_THREADS | ||
| 373 | |||
| 324 | result = nissy_solve(cube, solver, nissflag, minmoves, maxmoves, | 374 | result = nissy_solve(cube, solver, nissflag, minmoves, maxmoves, |
| 325 | maxsolutions, optimal, threads, data->ob_alloc, | 375 | maxsolutions, optimal, threads, data->ob_alloc, |
| 326 | (unsigned char *)data->ob_bytes, MAX_SOLUTIONS_SIZE, solutions, | 376 | (unsigned char *)data->ob_bytes, MAX_SOLUTIONS_SIZE, solutions, |
| 327 | stats, NULL, NULL); | 377 | stats, callback_wrapper, &callback_data); |
| 328 | Py_END_ALLOW_THREADS | 378 | |
| 379 | PyEval_RestoreThread(callback_data.thread_state); | ||
| 380 | //Py_END_ALLOW_THREADS | ||
| 329 | 381 | ||
| 330 | return stringlist_result(result, solutions); | 382 | return stringlist_result(result, solutions); |
| 331 | } | 383 | } |
| @@ -461,6 +513,9 @@ PyMODINIT_FUNC PyInit_nissy(void) { | |||
| 461 | PyModule_AddIntConstant(module, "nissflag_mixed", NISSY_NISSFLAG_MIXED); | 513 | PyModule_AddIntConstant(module, "nissflag_mixed", NISSY_NISSFLAG_MIXED); |
| 462 | PyModule_AddIntConstant(module, "nissflag_linear", NISSY_NISSFLAG_LINEAR); | 514 | PyModule_AddIntConstant(module, "nissflag_linear", NISSY_NISSFLAG_LINEAR); |
| 463 | PyModule_AddIntConstant(module, "nissflag_all", NISSY_NISSFLAG_ALL); | 515 | PyModule_AddIntConstant(module, "nissflag_all", NISSY_NISSFLAG_ALL); |
| 516 | PyModule_AddIntConstant(module, "status_run", NISSY_STATUS_RUN); | ||
| 517 | PyModule_AddIntConstant(module, "status_stop", NISSY_STATUS_STOP); | ||
| 518 | PyModule_AddIntConstant(module, "status_pause", NISSY_STATUS_PAUSE); | ||
| 464 | 519 | ||
| 465 | return module; | 520 | return module; |
| 466 | } | 521 | } |
