diff --git a/python_scripts/loop_test.py b/python_scripts/loop_test.py new file mode 100644 index 00000000..bcd42ac4 --- /dev/null +++ b/python_scripts/loop_test.py @@ -0,0 +1,5 @@ +import emu + +while True: + print("PY: Frame") + emu.frameadvance() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8ef081d4..3ff7348b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -224,6 +224,11 @@ else () ) endif() +# Python and pybind11 configuration +find_package(pybind11 REQUIRED) +set(PYTHON_LIBS pybind11::embed) +set(PYTHON_ENGINE_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/python-engine.cpp) + if ( ${ZLIB_FOUND} ) message( STATUS "Using System zlib ${ZLIB_VERSION_STRING}" ) @@ -292,6 +297,7 @@ set(SRC_CORE ${CMAKE_CURRENT_SOURCE_DIR}/wave.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x6502.cpp ${LUA_ENGINE_SOURCE} + ${PYTHON_ENGINE_SOURCE} ${ZLIB_SOURCE} ${CMAKE_CURRENT_SOURCE_DIR}/boards/01-222.cpp ${CMAKE_CURRENT_SOURCE_DIR}/boards/09-034a.cpp @@ -632,6 +638,7 @@ target_link_libraries( ${APP_NAME} ${ASAN_LDFLAGS} ${MINIZIP_LDFLAGS} ${ZLIB_LIBRARIES} ${LUA_LDFLAGS} ${X264_LDFLAGS} ${X265_LDFLAGS} ${LIBAV_LDFLAGS} ${SYS_LIBS} + ${PYTHON_LIBS} ) if (WIN32) diff --git a/src/drivers/Qt/fceuWrapper.cpp b/src/drivers/Qt/fceuWrapper.cpp index ce61ecaa..c5076fcb 100644 --- a/src/drivers/Qt/fceuWrapper.cpp +++ b/src/drivers/Qt/fceuWrapper.cpp @@ -58,6 +58,8 @@ #include "../../fceulua.h" #endif +#include "../../fceupython.h" + #include "common/os_utils.h" #include "common/configSys.h" #include "../../oldmovie.h" @@ -1068,6 +1070,8 @@ int fceuWrapperInit( int argc, char *argv[] ) { s = fi.canonicalFilePath().toStdString(); } + + FCEU_LoadPythonCode(s.c_str()); } g_config->getOption("SDL.NewPPU", &newppu); diff --git a/src/fceu.cpp b/src/fceu.cpp index 28396c34..0a4ff754 100644 --- a/src/fceu.cpp +++ b/src/fceu.cpp @@ -64,6 +64,8 @@ extern void RefreshThrottleFPS(); #include "fceulua.h" #endif +#include "fceupython.h" + //TODO - we really need some kind of global platform-specific options api #ifdef __WIN_DRIVER__ #include "drivers/win/main.h" @@ -796,6 +798,8 @@ void FCEUI_Emulate(uint8 **pXBuf, int32 **SoundBuf, int32 *SoundBufSize, int ski FCEU_LuaFrameBoundary(); #endif + FCEU_PythonFrameBoundary(); + FCEU_UpdateInput(); lagFlag = 1; diff --git a/src/fceupython.h b/src/fceupython.h new file mode 100644 index 00000000..40674464 --- /dev/null +++ b/src/fceupython.h @@ -0,0 +1,7 @@ +#ifndef _FCEUPYTHON_H +#define _FCEUPYTHON_H + +void FCEU_PythonFrameBoundary(); +void FCEU_LoadPythonCode(const char* filename); + +#endif //_FCEUPYTHON_H \ No newline at end of file diff --git a/src/python-engine.cpp b/src/python-engine.cpp new file mode 100644 index 00000000..5f9082b0 --- /dev/null +++ b/src/python-engine.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + +#include +namespace py = pybind11; + +#include "fceupython.h" + +#define SetCurrentDir chdir + +// Are we running any code right now? +static char* pythonScriptName = NULL; +bool pythonRunning = false; + +// True if there's a thread waiting to run after a run of frame-advance. +static std::atomic_bool frameAdvanceWaiting = false; + +// True if there's a thread waiting to run after a run of frame-advance. +static std::atomic_bool inFrameBoundry = false; + +std::mutex mtx; +std::condition_variable cv; + +// Python thread object +// std::thread python_thread; + + +void FCEU_PythonFrameBoundary() +{ + std::cout << "in FCEU_PythonFrameBoundary" << std::endl; + + if(!pythonRunning) + return; + + // Notify Python thread the main thread is in the frame boundry + inFrameBoundry = true; + cv.notify_all(); + + + std::unique_lock lock(mtx); + cv.wait(lock, [] { return bool(frameAdvanceWaiting); }); + frameAdvanceWaiting = false; +} + +void emu_frameadvance() +{ + // Can't call if a frameAdvance is already waiting + if (frameAdvanceWaiting) + return; + + frameAdvanceWaiting = true; + + // Notify main thread it can advance the frame + cv.notify_all(); + + // Wait until inFrameBoundry is true to continue python script + std::unique_lock lock(mtx); + inFrameBoundry = false; + cv.wait(lock, [] { return bool(inFrameBoundry); }); +} + +PYBIND11_EMBEDDED_MODULE(emu, m) +{ + m.def("frameadvance", &emu_frameadvance); + // m.def("framecount", [] { return emu_framecount; }); +} + +void pythonStart(std::string filename) +{ + // Wait until in_frame_boundry is true to start python script + std::unique_lock lock(mtx); + cv.wait(lock, [] { return bool(inFrameBoundry); }); + lock.unlock(); + + // Start evaluating the python file + py::gil_scoped_acquire acquire; + py::eval_file(filename); +} + +/** + * Loads and runs the given Python script. + * The emulator MUST be paused for this function to be + * called. Otherwise, all frame boundary assumptions go out the window. + * + * Returns true on success, false on failure. + */ +void FCEU_LoadPythonCode(const char* filename) +{ + if (filename != pythonScriptName) + { + if (pythonScriptName) + free(pythonScriptName); + pythonScriptName = strdup(filename); + } + + // Start interpreter + pythonRunning = true; + py::initialize_interpreter(); + + // gil_scoped_release created on heap to not be destroyed on leaving FCEU_LoadPythonCode scope + py::gil_scoped_release* release = new py::gil_scoped_release; + + std::thread(pythonStart, std::string(filename)).detach(); + + FCEU_PythonFrameBoundary(); +} + +/** + * Terminates a running Python scripts by killing the whole Python Interpretor. + */ +void FCEU_PythonStop() +{ + if (!pythonRunning) + return; + + // Stop interpretor + pythonRunning = false; + py::finalize_interpreter(); +} \ No newline at end of file