mirror of https://github.com/mgba-emu/mgba.git
Scripting: Start working on mCore bridge
This commit is contained in:
parent
7226b7ee31
commit
ce3710323b
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2022 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
|
||||
|
@ -13,6 +13,10 @@ CXX_GUARD_START
|
|||
#ifdef USE_DEBUGGERS
|
||||
#include <mgba/debugger/debugger.h>
|
||||
#endif
|
||||
#include <mgba/script/types.h>
|
||||
|
||||
struct mCore;
|
||||
mSCRIPT_DECLARE_STRUCT(mCore);
|
||||
|
||||
struct mScriptBridge;
|
||||
struct VFile;
|
||||
|
@ -47,6 +51,11 @@ bool mScriptBridgeLoadScript(struct mScriptBridge*, const char* name);
|
|||
|
||||
bool mScriptBridgeLookupSymbol(struct mScriptBridge*, const char* name, int32_t* out);
|
||||
|
||||
struct mScriptContext;
|
||||
struct mCore;
|
||||
void mScriptContextAttachCore(struct mScriptContext*, struct mCore*);
|
||||
void mScriptContextDetachCore(struct mScriptContext*);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
||||
|
|
|
@ -19,7 +19,7 @@ struct mScriptFunction;
|
|||
struct mScriptEngineContext;
|
||||
|
||||
struct mScriptContext {
|
||||
struct mScriptValue rootScope;
|
||||
struct Table rootScope;
|
||||
struct Table engines;
|
||||
};
|
||||
|
||||
|
@ -46,9 +46,9 @@ struct mScriptEngineContext {
|
|||
void mScriptContextInit(struct mScriptContext*);
|
||||
void mScriptContextDeinit(struct mScriptContext*);
|
||||
|
||||
void mScriptContextRegisterEngine(struct mScriptContext*, struct mScriptEngine2*);
|
||||
struct mScriptEngineContext* mScriptContextRegisterEngine(struct mScriptContext*, struct mScriptEngine2*);
|
||||
|
||||
void mScriptContextAddGlobal(struct mScriptContext*, const char* key, struct mScriptValue* value);
|
||||
void mScriptContextSetGlobal(struct mScriptContext*, const char* key, struct mScriptValue* value);
|
||||
void mScriptContextRemoveGlobal(struct mScriptContext*, const char* key);
|
||||
|
||||
bool mScriptInvoke(const struct mScriptValue* fn, struct mScriptFrame* frame);
|
||||
|
|
|
@ -580,6 +580,7 @@ void mScriptValueDeref(struct mScriptValue* val);
|
|||
|
||||
void mScriptValueWrap(struct mScriptValue* val, struct mScriptValue* out);
|
||||
struct mScriptValue* mScriptValueUnwrap(struct mScriptValue* val);
|
||||
const struct mScriptValue* mScriptValueUnwrapConst(const struct mScriptValue* val);
|
||||
|
||||
struct mScriptValue* mScriptStringCreateFromUTF8(const char* string);
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ set(SOURCE_FILES
|
|||
map-cache.c
|
||||
mem-search.c
|
||||
rewind.c
|
||||
scripting.c
|
||||
serialize.c
|
||||
sync.c
|
||||
thread.c
|
||||
|
@ -24,6 +23,16 @@ set(SOURCE_FILES
|
|||
set(TEST_FILES
|
||||
test/core.c)
|
||||
|
||||
if(ENABLE_SCRIPTING)
|
||||
list(APPEND SOURCE_FILES
|
||||
scripting.c)
|
||||
|
||||
if(USE_LUA)
|
||||
list(APPEND TEST_FILES
|
||||
test/scripting.c)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
source_group("mCore" FILES ${SOURCE_FILES})
|
||||
source_group("mCore tests" FILES ${TEST_FILES})
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2022 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 <mgba/core/scripting.h>
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba-util/table.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
|
@ -144,3 +146,33 @@ bool mScriptBridgeLookupSymbol(struct mScriptBridge* sb, const char* name, int32
|
|||
HashTableEnumerate(&sb->engines, _seLookupSymbol, &info);
|
||||
return info.success;
|
||||
}
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mCore, U32, frameCounter, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mCore, S32, frameCycles, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mCore, S32, frequency, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, runFrame, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, step, 0);
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(mCore)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Get the number of the current frame")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(mCore, currentFrame, frameCounter)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Get the number of cycles per frame")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, frameCycles)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Get the number of cycles per second")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, frequency)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Run until the next frame")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, runFrame)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Run a single instruction")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, step)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
||||
void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core) {
|
||||
struct mScriptValue* coreValue = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mCore));
|
||||
coreValue->value.opaque = core;
|
||||
mScriptContextSetGlobal(context, "emu", coreValue);
|
||||
mScriptValueDeref(coreValue);
|
||||
}
|
||||
|
||||
void mScriptContextDetachCore(struct mScriptContext* context) {
|
||||
mScriptContextRemoveGlobal(context, "emu");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
/* Copyright (c) 2013-2022 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 "util/test/suite.h"
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/scripting.h>
|
||||
#include <mgba/internal/script/lua.h>
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba/script/types.h>
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
#define TEST_PLATFORM mPLATFORM_GBA
|
||||
#elif defined(M_CORE_GB)
|
||||
#define TEST_PLATFORM mPLATFORM_GB
|
||||
#else
|
||||
#error "Need a valid platform for testing"
|
||||
#endif
|
||||
|
||||
static const uint8_t _fakeGBROM[0x4000] = {
|
||||
[0x100] = 0x18, // Loop forever
|
||||
[0x101] = 0xFE, // jr, $-2
|
||||
[0x102] = 0xCE, // Enough of the header to fool the core
|
||||
[0x103] = 0xED,
|
||||
[0x104] = 0x66,
|
||||
[0x105] = 0x66,
|
||||
};
|
||||
|
||||
#define SETUP_LUA \
|
||||
struct mScriptContext context; \
|
||||
mScriptContextInit(&context); \
|
||||
struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA)
|
||||
|
||||
#define CREATE_CORE \
|
||||
struct mCore* core = mCoreCreate(TEST_PLATFORM); \
|
||||
assert_non_null(core); \
|
||||
assert_true(core->init(core)); \
|
||||
switch (core->platform(core)) { \
|
||||
case mPLATFORM_GBA: \
|
||||
core->busWrite32(core, 0x020000C0, 0xEAFFFFFE); \
|
||||
break; \
|
||||
case mPLATFORM_GB: \
|
||||
assert_true(core->loadROM(core, VFileFromConstMemory(_fakeGBROM, sizeof(_fakeGBROM)))); \
|
||||
break; \
|
||||
case mPLATFORM_NONE: \
|
||||
break; \
|
||||
} \
|
||||
mCoreInitConfig(core, NULL); \
|
||||
mScriptContextAttachCore(&context, core)
|
||||
|
||||
#define TEARDOWN_CORE \
|
||||
mCoreConfigDeinit(&core->config); \
|
||||
core->deinit(core)
|
||||
|
||||
#define LOAD_PROGRAM(PROG) \
|
||||
do { \
|
||||
struct VFile* vf = VFileFromConstMemory(PROG, strlen(PROG)); \
|
||||
const char* error = NULL; \
|
||||
assert_true(lua->load(lua, vf, &error)); \
|
||||
assert_null(error); \
|
||||
vf->close(vf); \
|
||||
} while(0)
|
||||
|
||||
#define TEST_VALUE(TYPE, NAME, VALUE) \
|
||||
do { \
|
||||
struct mScriptValue val = mSCRIPT_MAKE(TYPE, VALUE); \
|
||||
struct mScriptValue* global = lua->getGlobal(lua, NAME); \
|
||||
assert_non_null(global); \
|
||||
assert_true(global->type->equal(global, &val)); \
|
||||
mScriptValueDeref(global); \
|
||||
} while(0)
|
||||
|
||||
M_TEST_SUITE_SETUP(mScriptCore) {
|
||||
if (mSCRIPT_ENGINE_LUA->init) {
|
||||
mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
M_TEST_SUITE_TEARDOWN(mScriptCore) {
|
||||
if (mSCRIPT_ENGINE_LUA->deinit) {
|
||||
mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(globals) {
|
||||
SETUP_LUA;
|
||||
CREATE_CORE;
|
||||
core->reset(core);
|
||||
|
||||
LOAD_PROGRAM("assert(emu)");
|
||||
assert_true(lua->run(lua));
|
||||
|
||||
TEARDOWN_CORE;
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(infoFuncs) {
|
||||
SETUP_LUA;
|
||||
CREATE_CORE;
|
||||
core->reset(core);
|
||||
|
||||
LOAD_PROGRAM(
|
||||
"frequency = emu:frequency()\n"
|
||||
"frameCycles = emu:frameCycles()\n"
|
||||
);
|
||||
assert_true(lua->run(lua));
|
||||
|
||||
TEST_VALUE(S32, "frequency", core->frequency(core));
|
||||
TEST_VALUE(S32, "frameCycles", core->frameCycles(core));
|
||||
|
||||
TEARDOWN_CORE;
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(runFrame) {
|
||||
SETUP_LUA;
|
||||
CREATE_CORE;
|
||||
core->reset(core);
|
||||
|
||||
LOAD_PROGRAM(
|
||||
"frame = emu:currentFrame()\n"
|
||||
"emu:runFrame()\n"
|
||||
);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 5; ++i) {
|
||||
assert_true(lua->run(lua));
|
||||
TEST_VALUE(S32, "frame", i);
|
||||
}
|
||||
|
||||
TEARDOWN_CORE;
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore,
|
||||
cmocka_unit_test(globals),
|
||||
cmocka_unit_test(infoFuncs),
|
||||
cmocka_unit_test(runFrame),
|
||||
)
|
|
@ -5,13 +5,64 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/script/context.h>
|
||||
|
||||
struct mScriptKVPair {
|
||||
const char* key;
|
||||
struct mScriptValue* value;
|
||||
};
|
||||
|
||||
static void _engineContextDestroy(void* ctx) {
|
||||
struct mScriptEngineContext* context = ctx;
|
||||
context->destroy(context);
|
||||
}
|
||||
|
||||
static void _contextAddGlobal(const char* key, void* value, void* user) {
|
||||
UNUSED(key);
|
||||
struct mScriptEngineContext* context = value;
|
||||
struct mScriptKVPair* pair = user;
|
||||
context->setGlobal(context, pair->key, pair->value);
|
||||
}
|
||||
|
||||
static void _contextRemoveGlobal(const char* key, void* value, void* user) {
|
||||
UNUSED(key);
|
||||
struct mScriptEngineContext* context = value;
|
||||
context->setGlobal(context, user, NULL);
|
||||
}
|
||||
|
||||
void mScriptContextInit(struct mScriptContext* context) {
|
||||
// TODO: rootScope
|
||||
HashTableInit(&context->engines, 0, NULL);
|
||||
HashTableInit(&context->rootScope, 0, (void (*)(void*)) mScriptValueDeref);
|
||||
HashTableInit(&context->engines, 0, _engineContextDestroy);
|
||||
}
|
||||
|
||||
void mScriptContextDeinit(struct mScriptContext* context) {
|
||||
HashTableDeinit(&context->engines);
|
||||
HashTableDeinit(&context->rootScope);
|
||||
}
|
||||
|
||||
struct mScriptEngineContext* mScriptContextRegisterEngine(struct mScriptContext* context, struct mScriptEngine2* engine) {
|
||||
struct mScriptEngineContext* ectx = engine->create(engine, context);
|
||||
if (ectx) {
|
||||
HashTableInsert(&context->engines, engine->name, ectx);
|
||||
}
|
||||
return ectx;
|
||||
}
|
||||
|
||||
void mScriptContextSetGlobal(struct mScriptContext* context, const char* key, struct mScriptValue* value) {
|
||||
mScriptValueRef(value);
|
||||
HashTableInsert(&context->rootScope, key, value);
|
||||
struct mScriptKVPair pair = {
|
||||
.key = key,
|
||||
.value = value
|
||||
};
|
||||
HashTableEnumerate(&context->engines, _contextAddGlobal, &pair);
|
||||
}
|
||||
|
||||
void mScriptContextRemoveGlobal(struct mScriptContext* context, const char* key) {
|
||||
if (!HashTableLookup(&context->rootScope, key)) {
|
||||
return;
|
||||
}
|
||||
// Since _contextRemoveGlobal doesn't mutate |key|, this cast should be safe
|
||||
HashTableEnumerate(&context->engines, _contextRemoveGlobal, (char*) key);
|
||||
HashTableRemove(&context->rootScope, key);
|
||||
}
|
||||
|
||||
bool mScriptInvoke(const struct mScriptValue* val, struct mScriptFrame* frame) {
|
||||
|
|
|
@ -30,6 +30,7 @@ static void _luaDeref(struct mScriptValue*);
|
|||
static int _luaThunk(lua_State* lua);
|
||||
static int _luaGetObject(lua_State* lua);
|
||||
static int _luaSetObject(lua_State* lua);
|
||||
static int _luaGcObject(lua_State* lua);
|
||||
|
||||
#if LUA_VERSION_NUM < 503
|
||||
#define lua_pushinteger lua_pushnumber
|
||||
|
@ -83,6 +84,7 @@ struct mScriptEngine2* const mSCRIPT_ENGINE_LUA = &_engineLua.d;
|
|||
static const luaL_Reg _mSTStruct[] = {
|
||||
{ "__index", _luaGetObject },
|
||||
{ "__newindex", _luaSetObject },
|
||||
{ "__gc", _luaGcObject },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
@ -483,3 +485,13 @@ int _luaSetObject(lua_State* lua) {
|
|||
mScriptValueDeref(val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _luaGcObject(lua_State* lua) {
|
||||
struct mScriptValue* val = lua_touserdata(lua, -1);
|
||||
val = mScriptValueUnwrap(val);
|
||||
if (!val) {
|
||||
return 0;
|
||||
}
|
||||
mScriptValueDeref(val);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -687,6 +687,13 @@ struct mScriptValue* mScriptValueUnwrap(struct mScriptValue* value) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
const struct mScriptValue* mScriptValueUnwrapConst(const struct mScriptValue* value) {
|
||||
if (value->type == mSCRIPT_TYPE_MS_WRAPPER) {
|
||||
return value->value.copaque;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mScriptValue* mScriptStringCreateFromUTF8(const char* string) {
|
||||
struct mScriptValue* val = mScriptValueAlloc(mSCRIPT_TYPE_MS_STR);
|
||||
struct mScriptString* internal = val->value.opaque;
|
||||
|
@ -823,6 +830,9 @@ void mScriptClassDeinit(struct mScriptTypeClass* cls) {
|
|||
}
|
||||
|
||||
bool mScriptObjectGet(struct mScriptValue* obj, const char* member, struct mScriptValue* val) {
|
||||
if (obj->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
obj = mScriptValueUnwrap(obj);
|
||||
}
|
||||
if (obj->type->base != mSCRIPT_TYPE_OBJECT) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1031,6 +1041,9 @@ bool mScriptPopPointer(struct mScriptList* list, void** out) {
|
|||
}
|
||||
|
||||
bool mScriptCast(const struct mScriptType* type, const struct mScriptValue* input, struct mScriptValue* output) {
|
||||
if (input->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
input = mScriptValueUnwrapConst(input);
|
||||
}
|
||||
if (type->cast && type->cast(input, type, output)) {
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue