Python: Implement Python script backend

This commit is contained in:
Vicki Pfau 2017-07-07 18:04:53 -07:00
parent 1a7a544ba7
commit 25b4faef12
13 changed files with 315 additions and 12 deletions

View File

@ -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 @@ if(DISABLE_FRONTENDS)
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})
@ -840,11 +854,6 @@ if(BUILD_SUITE)
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)
add_executable(${BINARY_NAME}-example-server ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/example/client-server/server.c)
target_link_libraries(${BINARY_NAME}-example-server ${BINARY_NAME})

View File

@ -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

View File

@ -102,7 +102,7 @@ void mDebuggerRun(struct mDebugger* debugger) {
mScriptBridgeRun(debugger->bridge);
#endif
if (debugger->state == DEBUGGER_SCRIPT) {
debugger->state = DEBUGGER_RUNNING;
debugger->state = DEBUGGER_PAUSED;
}
break;
case DEBUGGER_SHUTDOWN:

View File

@ -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 @@ add_custom_command(OUTPUT build/lib/${BINARY_NAME}/__init__.py
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)

View File

@ -58,5 +58,47 @@ for line in preprocessed.splitlines():
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")

View File

@ -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();
}

View File

@ -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

View File

@ -0,0 +1,6 @@
struct mDebugger;
struct VFile;
extern void mPythonSetDebugger(struct mDebugger*);
extern bool mPythonLoadScript(const char*, struct VFile*);
extern void mPythonRunPending();

View File

@ -62,15 +62,19 @@ class Core(object):
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):
return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8')))
@ -125,7 +129,6 @@ class Core(object):
self._core.runLoop(self._core)
@needsReset
@protected
def step(self):
self._core.step(self._core)

View File

@ -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)

View File

@ -108,6 +108,13 @@ class VFile:
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)

View File

@ -86,6 +86,12 @@ else()
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})

View File

@ -15,6 +15,10 @@
#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 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) {
mCoreAutoloadSave(renderer->core);
#ifdef ENABLE_SCRIPTING
struct mScriptBridge* bridge = mScriptBridgeCreate();
#ifdef ENABLE_PYTHON
mPythonSetup(bridge);
#endif
#endif
#ifdef USE_DEBUGGERS