extern "C" { #define PY_SSIZE_T_CLEAN #include #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #include } #include #include "ScriptEngine.hpp" #include /* TODO: - Fix Numpy loading on Linux. - "undefined symbol: PyExc_RecursionError" - This worked in Python 3.7, but because some other crash (regarding GIL interpreter objects, I forgot) was fixed on Python's issue tracker in the last month, I switched to Python 3.8 to solve that problem. - Test build and running on Windows/Mac. (Has not been attempted.) - Allow multiple instances with GIL. - Fix destructors. */ extern rack::Plugin* pluginInstance; static void initPython() { if (Py_IsInitialized()) return; std::string pythonDir = rack::asset::plugin(pluginInstance, "dep/lib/python3.8"); // Set python path std::string sep = ":"; std::string pythonPath = pythonDir; pythonPath += sep + pythonDir + "/lib-dynload"; pythonPath += sep + pythonDir + "/site-packages"; wchar_t* pythonPathW = Py_DecodeLocale(pythonPath.c_str(), NULL); Py_SetPath(pythonPathW); PyMem_RawFree(pythonPathW); // Initialize but don't register signal handlers Py_InitializeEx(0); assert(Py_IsInitialized()); // PyEval_InitThreads(); // Import numpy if (_import_array()) { PyErr_Print(); abort(); } } struct PythonEngine : ScriptEngine { PyObject* mainDict = NULL; PyObject* processFunc = NULL; PyObject* blockObj = NULL; PyInterpreterState* interp = NULL; ~PythonEngine() { if (mainDict) Py_DECREF(mainDict); if (processFunc) Py_DECREF(processFunc); if (blockObj) Py_DECREF(blockObj); if (interp) PyInterpreterState_Delete(interp); } std::string getEngineName() override { return "Python"; } int run(const std::string& path, const std::string& script) override { ProcessBlock* block = getProcessBlock(); initPython(); // PyThreadState* tstate = PyThreadState_Get(); // interp = PyInterpreterState_New(); // PyThreadState_Swap(tstate); // Get globals dictionary PyObject* mainModule = PyImport_AddModule("__main__"); assert(mainModule); DEFER({Py_DECREF(mainModule);}); mainDict = PyModule_GetDict(mainModule); assert(mainDict); // Set context pointer PyObject* engineObj = PyCapsule_New(this, NULL, NULL); PyDict_SetItemString(mainDict, "_engine", engineObj); // Add functions to globals static PyMethodDef native_functions[] = { {"display", nativeDisplay, METH_VARARGS, ""}, {NULL, NULL, 0, NULL}, }; if (PyModule_AddFunctions(mainModule, native_functions)) { WARN("Could not add global functions"); return -1; } // Set config static PyStructSequence_Field configFields[] = { {"frameDivider", ""}, {"bufferSize", ""}, {NULL, NULL}, }; static PyStructSequence_Desc configDesc = {"Config", "", configFields, LENGTHOF(configFields) - 1}; PyTypeObject* configType = PyStructSequence_NewType(&configDesc); assert(configType); PyObject* configObj = PyStructSequence_New(configType); assert(configObj); PyDict_SetItemString(mainDict, "config", configObj); // frameDivider PyStructSequence_SetItem(configObj, 0, PyLong_FromLong(32)); // bufferSize PyStructSequence_SetItem(configObj, 1, PyLong_FromLong(1)); // Compile string PyObject* code = Py_CompileString(script.c_str(), path.c_str(), Py_file_input); if (!code) { PyErr_Print(); return -1; } DEFER({Py_DECREF(code);}); // Evaluate string PyObject* result = PyEval_EvalCode(code, mainDict, mainDict); if (!result) { PyErr_Print(); return -1; } DEFER({Py_DECREF(result);}); // Create block static PyStructSequence_Field blockFields[] = { {"inputs", ""}, {"outputs", ""}, {"knobs", ""}, {"switches", ""}, {"lights", ""}, {"switch_lights", ""}, {NULL, NULL}, }; static PyStructSequence_Desc blockDesc = {"Block", "", blockFields, LENGTHOF(blockFields) - 1}; PyTypeObject* blockType = PyStructSequence_NewType(&blockDesc); assert(blockType); DEBUG("ref %d", Py_REFCNT(blockType)); blockObj = PyStructSequence_New(blockType); assert(blockObj); DEBUG("ref %d", Py_REFCNT(blockObj)); // inputs npy_intp inputsDims[] = {NUM_ROWS, MAX_BUFFER_SIZE}; PyObject* inputs = PyArray_SimpleNewFromData(2, inputsDims, NPY_FLOAT32, block->inputs); PyStructSequence_SetItem(blockObj, 0, inputs); // outputs npy_intp outputsDims[] = {NUM_ROWS, MAX_BUFFER_SIZE}; PyObject* outputs = PyArray_SimpleNewFromData(2, outputsDims, NPY_FLOAT32, block->outputs); PyStructSequence_SetItem(blockObj, 1, outputs); // knobs npy_intp knobsDims[] = {NUM_ROWS}; PyObject* knobs = PyArray_SimpleNewFromData(1, knobsDims, NPY_FLOAT32, block->knobs); PyStructSequence_SetItem(blockObj, 2, knobs); // switches npy_intp switchesDims[] = {NUM_ROWS}; PyObject* switches = PyArray_SimpleNewFromData(1, switchesDims, NPY_BOOL, block->switches); PyStructSequence_SetItem(blockObj, 3, switches); // lights npy_intp lightsDims[] = {NUM_ROWS, 3}; PyObject* lights = PyArray_SimpleNewFromData(2, lightsDims, NPY_FLOAT32, block->lights); PyStructSequence_SetItem(blockObj, 4, lights); // switchLights npy_intp switchLightsDims[] = {NUM_ROWS, 3}; PyObject* switchLights = PyArray_SimpleNewFromData(2, switchLightsDims, NPY_FLOAT32, block->switchLights); PyStructSequence_SetItem(blockObj, 5, switchLights); // Get process function from globals processFunc = PyDict_GetItemString(mainDict, "process"); if (!processFunc) { display("No process() function"); return -1; } if (!PyCallable_Check(processFunc)) { display("process() is not callable"); return -1; } return 0; } int process() override { // DEBUG("ref %d", Py_REFCNT(blockObj)); // Call process() PyObject* args = PyTuple_Pack(1, blockObj); assert(args); DEFER({Py_DECREF(args);}); PyObject* processResult = PyObject_CallObject(processFunc, args); if (!processResult) { PyErr_Print(); // PyObject *ptype, *pvalue, *ptraceback; // PyErr_Fetch(&ptype, &pvalue, &ptraceback); // const char* str = PyUnicode_AsUTF8(pvalue); // if (!str) // return -1; // display(str); return -1; } DEFER({Py_DECREF(processResult);}); // PyThreadState* tstate = PyThreadState_New(interp); // PyEval_RestoreThread(tstate); // PyThreadState_Clear(tstate); // PyThreadState_DeleteCurrent(); return 0; } static PyObject* nativeDisplay(PyObject* self, PyObject* args) { PyObject* mainDict = PyEval_GetGlobals(); assert(mainDict); PyObject* engineObj = PyDict_GetItemString(mainDict, "_engine"); assert(engineObj); PythonEngine* engine = (PythonEngine*) PyCapsule_GetPointer(engineObj, NULL); assert(engine); PyObject* msgO = PyTuple_GetItem(args, 0); if (!msgO) return NULL; PyObject* msgS = PyObject_Str(msgO); DEFER({Py_DECREF(msgS);}); const char* msg = PyUnicode_AsUTF8(msgS); engine->display(msg); Py_INCREF(Py_None); return Py_None; } }; __attribute__((constructor(1000))) static void constructor() { addScriptEngine("py"); }