From c835bf15da3ce39b856222d352c800f6ebd51acf Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Thu, 14 Aug 2025 13:32:19 +0200 Subject: Added attempt to make pause / resume work with python module --- python/examples/pause_resume.py | 35 +++++++++++++++++++++ python/examples/solve.py | 6 ++-- python/nissy_module.c | 67 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 python/examples/pause_resume.py (limited to 'python') 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 @@ +# This example is meant to test the pause / resume / stop mechanism +# See the solve.py example for more details on how this works + +import sys, os, time + +sys.path.append(os.getcwd()) +sys.path.append(os.getcwd() + os.path.sep + "python") +import nissy + +# Function used to pause, resume and kill the solver +t0 = time.time() +def poll(): + t = time.time() - t0 + if t < 3: + print("[{}s] pausing for the first 3 seconds".format(t)) + return nissy.status_pause + if t < 6: + print("[{}s] Letting the solver run for 3 seconds".format(t)) + return nissy.status_run + print("[{}s] Too much time has passed, giving up".format(t)) + return nissy.status_stop + +solver = "h48h0k4" +datapath = "tables" + os.path.sep + solver +if os.path.exists(datapath): + data = bytearray(open(datapath, "rb").read()) +else: + data = nissy.gendata("h48h0k4") + print("Generated data will NOT be persisted") + +# Scramble is 19 moves optimal, should take a while with the h48h0k4 solver +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" +cube = nissy.applymoves(nissy.solved_cube, scramble); + +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 @@ # Run "./build.sh python", then run this from either the top-level directory # of the nissy-core repo or from the python subdirectory. -# Append the directories to the python path so we can load the module +# Append the directories to the python path and import import sys, os sys.path.append(os.getcwd()) sys.path.append(os.getcwd() + os.path.sep + "python") - -# Import with a nicer name import nissy # Choose the solver you prefer @@ -26,7 +24,7 @@ else: cube = nissy.applymoves(nissy.solved_cube, "U F R2"); # Solve! -solutions = nissy.solve(cube, solver, nissy.nissflag_normal, 0, 9, 3, 20, 4, data) +solutions = nissy.solve(cube, solver, nissy.nissflag_normal, 0, 9, 3, 20, 4, data, None) # Print the solutions, one per line 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 @@ +/* +This version of the python module includes an attempt to use the callback +functions for pausing / stopping / resuming a solve. It probably cannot +work until PEP 703 (freethreading python) is implemented. +*/ + #define PY_SSIZE_T_CLEAN #include #include @@ -6,6 +12,11 @@ #define MAX_SOLUTIONS_SIZE 250000 +typedef struct { + PyObject *callback; + PyThreadState *thread_state; +} thread_callback_t; + static bool check_error(long long err) { @@ -84,6 +95,32 @@ long_result(long long result) return PyLong_FromLong(result); } +static int +callback_wrapper(void *data) +{ + thread_callback_t *arg = data; + int r; + PyObject *result; + PyGILState_STATE gstate; + + if (arg->callback != NULL && PyCallable_Check(arg->callback) == 1) { + +PyEval_RestoreThread(arg->thread_state); +gstate = PyGILState_Ensure(); + result = PyObject_CallNoArgs(data); +PyGILState_Release(gstate); +arg->thread_state = PyEval_SaveThread(); + + if (result == NULL) + return 0; + Py_XDECREF(result); + r = PyLong_AsInt(result); + return r >= 0 && r <= 2 ? r : 0; + } else { + return NISSY_STATUS_RUN; + } +} + PyDoc_STRVAR(inverse_doc, "inverse(cube)\n" "--\n\n" @@ -288,7 +325,7 @@ checkdata(PyObject *self, PyObject *args) PyDoc_STRVAR(solve_doc, "solve(cube, solver, nissflag, minmoves, maxmoves, maxsolutions," -" optimal, threads, data)\n" +" optimal, threads, data, callback)\n" "--\n\n" "Solves the given 'cube' with the given 'solver' and other parameters." "See the documentation for libnissy (in nissy.h) for details.\n" @@ -302,6 +339,9 @@ PyDoc_STRVAR(solve_doc, " - optimal: the largest number of moves from the shortest solution\n" " - threads: the number of threads to use (0 for default)\n" " - data: a bytearray containing the data for the solver\n" +" - callback: a function that returns 0 (run), 1 (stop) or 2 (pause).\n" +" Polled by the solver to determine if the user requested the\n" +" to pause or stop the solve. Not used by all solvers.\n" "\n" "Returns: a list with the solutions found\n" ); @@ -315,17 +355,29 @@ solve(PyObject *self, PyObject *args) char solutions[MAX_SOLUTIONS_SIZE]; long long stats[NISSY_SIZE_SOLVE_STATS]; PyByteArrayObject *data; + PyObject *callback; + thread_callback_t callback_data; - if (!PyArg_ParseTuple(args, "ssIIIIIIY", &cube, &solver, &nissflag, - &minmoves, &maxmoves, &maxsolutions, &optimal, &threads, &data)) + if (!PyArg_ParseTuple(args, "ssIIIIIIYO", &cube, &solver, &nissflag, + &minmoves, &maxmoves, &maxsolutions, &optimal, &threads, &data, + &callback)) return NULL; - Py_BEGIN_ALLOW_THREADS +printf("Checking callback here:\n"); +PyObject *rrr = PyObject_CallNoArgs(callback); +printf("result: %d\n", PyLong_AsInt(rrr)); + + callback_data.callback = callback; + callback_data.thread_state = PyEval_SaveThread(); + //Py_BEGIN_ALLOW_THREADS + result = nissy_solve(cube, solver, nissflag, minmoves, maxmoves, maxsolutions, optimal, threads, data->ob_alloc, (unsigned char *)data->ob_bytes, MAX_SOLUTIONS_SIZE, solutions, - stats, NULL, NULL); - Py_END_ALLOW_THREADS + stats, callback_wrapper, &callback_data); + + PyEval_RestoreThread(callback_data.thread_state); + //Py_END_ALLOW_THREADS return stringlist_result(result, solutions); } @@ -461,6 +513,9 @@ PyMODINIT_FUNC PyInit_nissy(void) { PyModule_AddIntConstant(module, "nissflag_mixed", NISSY_NISSFLAG_MIXED); PyModule_AddIntConstant(module, "nissflag_linear", NISSY_NISSFLAG_LINEAR); PyModule_AddIntConstant(module, "nissflag_all", NISSY_NISSFLAG_ALL); + PyModule_AddIntConstant(module, "status_run", NISSY_STATUS_RUN); + PyModule_AddIntConstant(module, "status_stop", NISSY_STATUS_STOP); + PyModule_AddIntConstant(module, "status_pause", NISSY_STATUS_PAUSE); return module; } -- cgit v1.3