all repos — mgba @ 25b4faef1262e37154e955d21d0fe1befa07e23b

mGBA Game Boy Advance Emulator

Python: Implement Python script backend
Vicki Pfau vi@endrift.com
Fri, 07 Jul 2017 18:04:53 -0700
commit

25b4faef1262e37154e955d21d0fe1befa07e23b

parent

1a7a544ba7891471fd94f2626a1c1af2c3427228

M CMakeLists.txtCMakeLists.txt

@@ -397,6 +397,7 @@ find_feature(USE_MAGICK "MagickWand")

find_feature(USE_EPOXY "epoxy") find_feature(USE_CMOCKA "cmocka") find_feature(USE_SQLITE3 "sqlite3") +find_feature(ENABLE_PYTHON "PythonLibs") # Features set(DEBUGGER_SRC

@@ -603,6 +604,14 @@ endif()

if(ENABLE_SCRIPTING) list(APPEND ENABLES SCRIPTING) + + if(BUILD_PYTHON) + find_program(PYTHON python) + include(FindPythonLibs) + list(APPEND DEPENDENCY_LIB ${PYTHON_LIBRARIES}) + include_directories(AFTER ${PYTHON_INCLUDE_DIRS}) + list(APPEND ENABLES PYTHON) + endif() endif() set(TEST_SRC ${CORE_TEST_SRC})

@@ -768,6 +777,11 @@ set(BUILD_SDL OFF)

set(BUILD_QT OFF) endif() +if(BUILD_PYTHON) + enable_testing() + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/python ${CMAKE_CURRENT_BINARY_DIR}/python) +endif() + if(BUILD_LIBRETRO) file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c) add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})

@@ -838,11 +852,6 @@ add_executable(${BINARY_NAME}-suite ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/test/suite-main.c ${TEST_SRC})

target_link_libraries(${BINARY_NAME}-suite ${BINARY_NAME} ${PLATFORM_LIBRARY} cmocka) set_target_properties(${BINARY_NAME}-suite PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") add_test(${BINARY_NAME}-suite ${BINARY_NAME}-suite) -endif() - -if(BUILD_PYTHON) - enable_testing() - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/python ${CMAKE_CURRENT_BINARY_DIR}/python) endif() if(BUILD_EXAMPLE)
A include/mgba/core/scripting.h

@@ -0,0 +1,39 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef M_SCRIPTING_H +#define M_SCRIPTING_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +struct mScriptBridge; +struct VFile; +struct mDebugger; +struct mScriptEngine { + const char* (*name)(struct mScriptEngine*); + + bool (*init)(struct mScriptEngine*, struct mScriptBridge*); + void (*deinit)(struct mScriptEngine*); + bool (*isScript)(struct mScriptEngine*, const char* name, struct VFile* vf); + bool (*loadScript)(struct mScriptEngine*, const char* name, struct VFile* vf); + void (*run)(struct mScriptEngine*); +}; + +struct mScriptBridge* mScriptBridgeCreate(void); +void mScriptBridgeDestroy(struct mScriptBridge*); + +void mScriptBridgeInstallEngine(struct mScriptBridge*, struct mScriptEngine*); + +void mScriptBridgeSetDebugger(struct mScriptBridge*, struct mDebugger*); +struct mDebugger* mScriptBridgeGetDebugger(struct mScriptBridge*); + +void mScriptBridgeRun(struct mScriptBridge*); +bool mScriptBridgeLoadScript(struct mScriptBridge*, const char* name); + +CXX_GUARD_END + +#endif
M src/debugger/debugger.csrc/debugger/debugger.c

@@ -102,7 +102,7 @@ #ifdef ENABLE_SCRIPTING

mScriptBridgeRun(debugger->bridge); #endif if (debugger->state == DEBUGGER_SCRIPT) { - debugger->state = DEBUGGER_RUNNING; + debugger->state = DEBUGGER_PAUSED; } break; case DEBUGGER_SHUTDOWN:
M src/platform/python/CMakeLists.txtsrc/platform/python/CMakeLists.txt

@@ -6,6 +6,10 @@ foreach(DIR IN LISTS INCLUDE_DIRECTORIES)

list(APPEND INCLUDE_FLAGS "-I${DIR}") endforeach() +include(FindPythonLibs) +list(APPEND DEPENDENCY_LIB ${PYTHON_LIBRARIES}) +include_directories(AFTER ${PYTHON_INCLUDE_DIRS}) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) add_custom_command(OUTPUT build/lib/${BINARY_NAME}/__init__.py COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build --build-base ${CMAKE_CURRENT_BINARY_DIR}

@@ -14,4 +18,17 @@ DEPENDS ${BINARY_NAME}

DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/setup.py DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py) -add_custom_target(${BINARY_NAME}-py ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/build/lib/${BINARY_NAME}/__init__.py) +add_custom_command(OUTPUT lib.c + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON} ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/lib.h + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py) + +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/lib.c PROPERTIES GENERATED ON) + +file(GLOB PYTHON_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.c) +add_library(${BINARY_NAME}-pylib STATIC ${CMAKE_CURRENT_BINARY_DIR}/lib.c ${PYTHON_SRC}) +add_dependencies(${BINARY_NAME}-pylib ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py) +set_target_properties(${BINARY_NAME}-pylib PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_BINARY_DIR};${INCLUDE_DIRECTORIES}") +set(PYTHON_LIBRARY ${BINARY_NAME}-pylib PARENT_SCOPE) + +add_custom_target(${BINARY_NAME}-py ALL DEPENDS ${BINARY_NAME}-pylib ${CMAKE_CURRENT_BINARY_DIR}/build/lib/${BINARY_NAME}/__init__.py)
M src/platform/python/_builder.pysrc/platform/python/_builder.py

@@ -58,5 +58,47 @@ continue

lines.append(line) ffi.cdef('\n'.join(lines)) +preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "lib.h")], universal_newlines=True) + +lines = [] +for line in preprocessed.splitlines(): + line = line.strip() + if line.startswith('#'): + continue + lines.append(line) +ffi.embedding_api('\n'.join(lines)) + +ffi.embedding_init_code(""" + from mgba._pylib import ffi + debugger = None + pendingCode = [] + + @ffi.def_extern() + def mPythonSetDebugger(_debugger): + from mgba.debugger import NativeDebugger + global debugger + debugger = _debugger and NativeDebugger(_debugger) + + @ffi.def_extern() + def mPythonLoadScript(name, vf): + from mgba.vfs import VFile + vf = VFile(vf) + name = ffi.string(name) + source = vf.readAll().decode('utf-8') + try: + code = compile(source, name, 'exec') + pendingCode.append(code) + except: + return False + return True + + @ffi.def_extern() + def mPythonRunPending(): + global pendingCode + for code in pendingCode: + exec(code) + pendingCode = [] +""") + if __name__ == "__main__": - ffi.compile() + ffi.emit_c_code("lib.c")
A src/platform/python/engine.c

@@ -0,0 +1,80 @@

+/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "engine.h" + +#include <mgba/core/scripting.h> +#include <mgba/debugger/debugger.h> +#include <mgba-util/string.h> +#include <mgba-util/vfs.h> + +#include "lib.h" + +static const char* mPythonScriptEngineName(struct mScriptEngine*); +static bool mPythonScriptEngineInit(struct mScriptEngine*, struct mScriptBridge*); +static void mPythonScriptEngineDeinit(struct mScriptEngine*); +static bool mPythonScriptEngineIsScript(struct mScriptEngine*, const char* name, struct VFile* vf); +static bool mPythonScriptEngineLoadScript(struct mScriptEngine*, const char* name, struct VFile* vf); +static void mPythonScriptEngineRun(struct mScriptEngine*); + +struct mPythonScriptEngine { + struct mScriptEngine d; + struct mScriptBridge* sb; +}; + +struct mPythonScriptEngine* mPythonCreateScriptEngine(void) { + struct mPythonScriptEngine* engine = malloc(sizeof(*engine)); + engine->d.name = mPythonScriptEngineName; + engine->d.init = mPythonScriptEngineInit; + engine->d.deinit = mPythonScriptEngineDeinit; + engine->d.isScript = mPythonScriptEngineIsScript; + engine->d.loadScript = mPythonScriptEngineLoadScript; + engine->d.run = mPythonScriptEngineRun; + engine->sb = NULL; + return engine; +} + +void mPythonSetup(struct mScriptBridge* sb) { + struct mPythonScriptEngine* se = mPythonCreateScriptEngine(); + mScriptBridgeInstallEngine(sb, &se->d); +} + +const char* mPythonScriptEngineName(struct mScriptEngine* se) { + UNUSED(se); + return "python"; +} + +bool mPythonScriptEngineInit(struct mScriptEngine* se, struct mScriptBridge* sb) { + struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se; + engine->sb = sb; + return true; +} + +void mPythonScriptEngineDeinit(struct mScriptEngine* se) { + struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se; + free(se); +} + +bool mPythonScriptEngineIsScript(struct mScriptEngine* se, const char* name, struct VFile* vf) { + UNUSED(se); + UNUSED(vf); + return endswith(name, ".py"); +} + +bool mPythonScriptEngineLoadScript(struct mScriptEngine* se, const char* name, struct VFile* vf) { + struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se; + return mPythonLoadScript(name, vf); +} + +void mPythonScriptEngineRun(struct mScriptEngine* se) { + struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se; + + struct mDebugger* debugger = mScriptBridgeGetDebugger(engine->sb); + if (debugger) { + mPythonSetDebugger(debugger); + } + + mPythonRunPending(); +}
A src/platform/python/engine.h

@@ -0,0 +1,20 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef PYTHON_ENGINE_H +#define PYTHON_ENGINE_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +struct mScriptBridge; +struct mPythonScriptEngine; +struct mPythonScriptEngine* mPythonCreateScriptEngine(void); +void mPythonSetup(struct mScriptBridge* sb); + +CXX_GUARD_END + +#endif
A src/platform/python/lib.h

@@ -0,0 +1,6 @@

+struct mDebugger; +struct VFile; + +extern void mPythonSetDebugger(struct mDebugger*); +extern bool mPythonLoadScript(const char*, struct VFile*); +extern void mPythonRunPending();
M src/platform/python/mgba/core.pysrc/platform/python/mgba/core.py

@@ -62,14 +62,18 @@ success = bool(core.init(core))

lib.mCoreInitConfig(core, ffi.NULL) if not success: raise RuntimeError("Failed to initialize core") + return cls._detect(core) + + def _deinit(self): + self._core.deinit(self._core) + + @classmethod + def _detect(cls, core): if hasattr(cls, 'PLATFORM_GBA') and core.platform(core) == cls.PLATFORM_GBA: return GBA(core) if hasattr(cls, 'PLATFORM_GB') and core.platform(core) == cls.PLATFORM_GB: return GB(core) return Core(core) - - def _deinit(self): - self._core.deinit(self._core) @protected def loadFile(self, path):

@@ -125,7 +129,6 @@ def runLoop(self):

self._core.runLoop(self._core) @needsReset - @protected def step(self): self._core.step(self._core)
A src/platform/python/mgba/debugger.py

@@ -0,0 +1,67 @@

+# Copyright (c) 2013-2017 Jeffrey Pfau +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +from ._pylib import ffi, lib +from .core import IRunner, ICoreOwner, Core + +class DebuggerCoreOwner(ICoreOwner): + def __init__(self, debugger): + self.debugger = debugger + self.wasPaused = False + + def claim(self): + if self.debugger.isRunning(): + self.wasPaused = True + self.debugger.pause() + return self.debugger._core + + def release(self): + if self.wasPaused: + self.debugger.unpause() + +class NativeDebugger(IRunner): + WATCHPOINT_WRITE = lib.WATCHPOINT_WRITE + WATCHPOINT_READ = lib.WATCHPOINT_READ + WATCHPOINT_RW = lib.WATCHPOINT_RW + + def __init__(self, native): + self._native = native + self._core = Core._detect(native.core) + self._core._wasReset = True + + def pause(self): + lib.mDebuggerEnter(self._native, lib.DEBUGGER_ENTER_MANUAL, ffi.NULL) + + def unpause(self): + self._native.state = lib.DEBUGGER_RUNNING + + def isRunning(self): + return self._native.state == lib.DEBUGGER_RUNNING + + def isPaused(self): + return self._native.state in (lib.DEBUGGER_PAUSED, lib.DEBUGGER_CUSTOM) + + def useCore(self): + return DebuggerCoreOwner(self) + + def setBreakpoint(self, address): + if not self._native.platform.setBreakpoint: + raise RuntimeError("Platform does not support breakpoints") + self._native.platform.setBreakpoint(self._native.platform, address) + + def clearBreakpoint(self, address): + if not self._native.platform.setBreakpoint: + raise RuntimeError("Platform does not support breakpoints") + self._native.platform.clearBreakpoint(self._native.platform, address) + + def setWatchpoint(self, address): + if not self._native.platform.setWatchpoint: + raise RuntimeError("Platform does not support watchpoints") + self._native.platform.setWatchpoint(self._native.platform, address) + + def clearWatchpoint(self, address): + if not self._native.platform.clearWatchpoint: + raise RuntimeError("Platform does not support watchpoints") + self._native.platform.clearWatchpoint(self._native.platform, address)
M src/platform/python/mgba/vfs.pysrc/platform/python/mgba/vfs.py

@@ -108,6 +108,13 @@

def read(self, buffer, size): return self.handle.read(self.handle, buffer, size) + def readAll(self, size=0): + if not size: + size = self.size() + buffer = ffi.new("char[%i]" % size) + size = self.handle.read(self.handle, buffer, size) + return ffi.unpack(buffer, size) + def readline(self, buffer, size): return self.handle.readline(self.handle, buffer, size)
M src/platform/sdl/CMakeLists.txtsrc/platform/sdl/CMakeLists.txt

@@ -86,6 +86,12 @@ list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c)

endif() endif() +if(ENABLE_SCRIPTING) + if(BUILD_PYTHON) + list(APPEND PLATFORM_LIBRARY "${PYTHON_LIBRARY}") + endif() +endif() + add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
M src/platform/sdl/main.csrc/platform/sdl/main.c

@@ -15,6 +15,10 @@ #include "feature/editline/cli-el-backend.h"

#endif #ifdef ENABLE_SCRIPTING #include <mgba/core/scripting.h> + +#ifdef ENABLE_PYTHON +#include "platform/python/engine.h" +#endif #endif #include <mgba/core/core.h>

@@ -164,6 +168,9 @@ }

mCoreAutoloadSave(renderer->core); #ifdef ENABLE_SCRIPTING struct mScriptBridge* bridge = mScriptBridgeCreate(); +#ifdef ENABLE_PYTHON + mPythonSetup(bridge); +#endif #endif #ifdef USE_DEBUGGERS