Scripting: Start working on mCore bridge

This commit is contained in:
Vicki Pfau 2022-05-03 21:50:53 -07:00
parent 7226b7ee31
commit ce3710323b
9 changed files with 278 additions and 8 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

143
src/core/test/scripting.c Normal file
View File

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

View File

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

View File

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

View File

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