mirror of https://github.com/mgba-emu/mgba.git
Python: Implement Python script backend
This commit is contained in:
parent
1a7a544ba7
commit
25b4faef12
|
@ -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})
|
||||
|
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
struct mDebugger;
|
||||
struct VFile;
|
||||
|
||||
extern void mPythonSetDebugger(struct mDebugger*);
|
||||
extern bool mPythonLoadScript(const char*, struct VFile*);
|
||||
extern void mPythonRunPending();
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue