diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index bb3b98b78..b808872e1 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -21,6 +21,8 @@ struct mScriptEngineContext; struct mScriptContext { struct Table rootScope; struct Table engines; + struct Table weakrefs; + uint32_t nextWeakref; }; struct mScriptEngine2 { @@ -55,6 +57,11 @@ void mScriptContextRegisterEngines(struct mScriptContext*); void mScriptContextSetGlobal(struct mScriptContext*, const char* key, struct mScriptValue* value); void mScriptContextRemoveGlobal(struct mScriptContext*, const char* key); +uint32_t mScriptContextSetWeakref(struct mScriptContext*, struct mScriptValue* value); +struct mScriptValue* mScriptContextMakeWeakref(struct mScriptContext*, struct mScriptValue* value); +struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext*, struct mScriptValue* value); +void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref); + struct VFile; bool mScriptContextLoadVF(struct mScriptContext*, const char* name, struct VFile* vf); bool mScriptContextLoadFile(struct mScriptContext*, const char* path); diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index e5ca34b82..e55ce4057 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -32,6 +32,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_PTR void* #define mSCRIPT_TYPE_C_TABLE Table* #define mSCRIPT_TYPE_C_WRAPPER struct mScriptValue* +#define mSCRIPT_TYPE_C_WEAKREF uint32_t #define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT* #define mSCRIPT_TYPE_C_CS(STRUCT) const struct STRUCT* #define mSCRIPT_TYPE_C_S_METHOD(STRUCT, NAME) _mSTStructFunctionType_ ## STRUCT ## _ ## NAME @@ -51,6 +52,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_FIELD_PTR opaque #define mSCRIPT_TYPE_FIELD_TABLE opaque #define mSCRIPT_TYPE_FIELD_WRAPPER opaque +#define mSCRIPT_TYPE_FIELD_WEAKREF u32 #define mSCRIPT_TYPE_FIELD_S(STRUCT) opaque #define mSCRIPT_TYPE_FIELD_CS(STRUCT) copaque #define mSCRIPT_TYPE_FIELD_S_METHOD(STRUCT, NAME) copaque @@ -69,6 +71,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_MS_CHARP (&mSTCharPtr) #define mSCRIPT_TYPE_MS_TABLE (&mSTTable) #define mSCRIPT_TYPE_MS_WRAPPER (&mSTWrapper) +#define mSCRIPT_TYPE_MS_WEAKREF (&mSTWeakref) #define mSCRIPT_TYPE_MS_S(STRUCT) (&mSTStruct_ ## STRUCT) #define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT) #define mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME) (&_mSTStructBindingType_ ## STRUCT ## _ ## NAME) @@ -435,7 +438,8 @@ enum mScriptTypeBase { mSCRIPT_TYPE_TUPLE, mSCRIPT_TYPE_LIST, mSCRIPT_TYPE_TABLE, - mSCRIPT_TYPE_WRAPPER + mSCRIPT_TYPE_WRAPPER, + mSCRIPT_TYPE_WEAKREF, }; enum mScriptClassInitType { @@ -462,6 +466,7 @@ extern const struct mScriptType mSTString; extern const struct mScriptType mSTCharPtr; extern const struct mScriptType mSTTable; extern const struct mScriptType mSTWrapper; +extern const struct mScriptType mSTWeakref; struct mScriptTypeTuple { size_t count; diff --git a/src/core/test/scripting.c b/src/core/test/scripting.c index b3af97742..63d2ef61e 100644 --- a/src/core/test/scripting.c +++ b/src/core/test/scripting.c @@ -120,6 +120,33 @@ M_TEST_DEFINE(infoFuncs) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(detach) { + SETUP_LUA; + CREATE_CORE; + core->reset(core); + + LOAD_PROGRAM( + "assert(emu)\n" + "a = emu\n" + ); + assert_true(lua->run(lua)); + + mScriptContextDetachCore(&context); + + LOAD_PROGRAM( + "assert(not emu)\n" + ); + assert_true(lua->run(lua)); + + LOAD_PROGRAM( + "a:frequency()\n" + ); + assert_false(lua->run(lua)); + + TEARDOWN_CORE; + mScriptContextDeinit(&context); +} + M_TEST_DEFINE(runFrame) { SETUP_LUA; CREATE_CORE; @@ -206,6 +233,7 @@ M_TEST_DEFINE(memoryWrite) { M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore, cmocka_unit_test(globals), cmocka_unit_test(infoFuncs), + cmocka_unit_test(detach), cmocka_unit_test(runFrame), cmocka_unit_test(memoryRead), cmocka_unit_test(memoryWrite), diff --git a/src/script/context.c b/src/script/context.c index 44d0fdca6..13fd262fb 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -52,11 +52,14 @@ static void _contextFindForFile(const char* key, void* value, void* user) { void mScriptContextInit(struct mScriptContext* context) { HashTableInit(&context->rootScope, 0, (void (*)(void*)) mScriptValueDeref); HashTableInit(&context->engines, 0, _engineContextDestroy); + TableInit(&context->weakrefs, 0, (void (*)(void*)) mScriptValueDeref); + context->nextWeakref = 0; } void mScriptContextDeinit(struct mScriptContext* context) { HashTableDeinit(&context->engines); HashTableDeinit(&context->rootScope); + HashTableDeinit(&context->weakrefs); } struct mScriptEngineContext* mScriptContextRegisterEngine(struct mScriptContext* context, struct mScriptEngine2* engine) { @@ -75,7 +78,13 @@ void mScriptContextRegisterEngines(struct mScriptContext* context) { } void mScriptContextSetGlobal(struct mScriptContext* context, const char* key, struct mScriptValue* value) { - mScriptValueRef(value); + struct mScriptValue* oldValue = HashTableLookup(&context->rootScope, key); + if (oldValue) { + mScriptContextClearWeakref(context, oldValue->value.u32); + } + uint32_t weakref = mScriptContextSetWeakref(context, value); + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_WEAKREF); + value->value.u32 = weakref; HashTableInsert(&context->rootScope, key, value); struct mScriptKVPair pair = { .key = key, @@ -90,7 +99,42 @@ void mScriptContextRemoveGlobal(struct mScriptContext* context, const char* key) } // Since _contextRemoveGlobal doesn't mutate |key|, this cast should be safe HashTableEnumerate(&context->engines, _contextRemoveGlobal, (char*) key); - HashTableRemove(&context->rootScope, key); + struct mScriptValue* oldValue = HashTableLookup(&context->rootScope, key); + if (oldValue) { + mScriptContextClearWeakref(context, oldValue->value.u32); + HashTableRemove(&context->rootScope, key); + } +} + +uint32_t mScriptContextSetWeakref(struct mScriptContext* context, struct mScriptValue* value) { + mScriptValueRef(value); + TableInsert(&context->weakrefs, context->nextWeakref, value); + + uint32_t nextWeakref = context->nextWeakref; + ++context->nextWeakref; + while (TableLookup(&context->weakrefs, context->nextWeakref)) { + ++context->nextWeakref; + } + return nextWeakref; +} + +struct mScriptValue* mScriptContextMakeWeakref(struct mScriptContext* context, struct mScriptValue* value) { + uint32_t weakref = mScriptContextSetWeakref(context, value); + mScriptValueDeref(value); + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_WEAKREF); + value->value.u32 = weakref; + return value; +} + +struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext* context, struct mScriptValue* value) { + if (value->type != mSCRIPT_TYPE_MS_WEAKREF) { + return value; + } + return TableLookup(&context->weakrefs, value->value.u32); +} + +void mScriptContextClearWeakref(struct mScriptContext* context, uint32_t weakref) { + TableRemove(&context->weakrefs, weakref); } bool mScriptContextLoadVF(struct mScriptContext* context, const char* name, struct VFile* vf) { diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index c002dafc6..35d7f7c45 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -212,15 +212,23 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext) { } lua_pop(luaContext->lua, 2); value = lua_touserdata(luaContext->lua, -1); + value = mScriptContextAccessWeakref(luaContext->d.context, value); } lua_pop(luaContext->lua, 1); return value; } bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* value) { + uint32_t weakref; + bool needsWeakref = false; if (value->type == mSCRIPT_TYPE_MS_WRAPPER) { value = mScriptValueUnwrap(value); } + if (value->type == mSCRIPT_TYPE_MS_WEAKREF) { + weakref = value->value.u32; + value = mScriptContextAccessWeakref(luaContext->d.context, value); + needsWeakref = true; + } bool ok = true; struct mScriptValue* newValue; switch (value->type->base) { @@ -260,7 +268,11 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v break; case mSCRIPT_TYPE_OBJECT: newValue = lua_newuserdata(luaContext->lua, sizeof(*newValue)); - mScriptValueWrap(value, newValue); + if (needsWeakref) { + *newValue = mSCRIPT_MAKE(WEAKREF, weakref); + } else { + mScriptValueWrap(value, newValue); + } luaL_setmetatable(luaContext->lua, "mSTStruct"); break; default: @@ -484,6 +496,13 @@ int _luaGetObject(lua_State* lua) { struct mScriptValue* obj = lua_touserdata(lua, -2); struct mScriptValue val; + obj = mScriptContextAccessWeakref(luaContext->d.context, obj); + if (!obj) { + lua_pop(lua, 2); + lua_pushliteral(lua, "Invalid object"); + lua_error(lua); + } + if (!mScriptObjectGet(obj, key, &val)) { lua_pop(lua, 2); lua_pushliteral(lua, "Invalid key"); @@ -505,6 +524,12 @@ int _luaSetObject(lua_State* lua) { struct mScriptValue* obj = lua_touserdata(lua, -3); struct mScriptValue* val = _luaCoerce(luaContext); + obj = mScriptContextAccessWeakref(luaContext->d.context, obj); + if (!obj) { + lua_pushliteral(lua, "Invalid object"); + lua_error(lua); + } + lua_pop(lua, 2); if (!val) { lua_pushliteral(lua, "Invalid value"); diff --git a/src/script/types.c b/src/script/types.c index bed27625e..5a12ef2a9 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -195,6 +195,15 @@ const struct mScriptType mSTWrapper = { .hash = NULL, }; +const struct mScriptType mSTWeakref = { + .base = mSCRIPT_TYPE_WEAKREF, + .size = sizeof(uint32_t), + .name = "weakref", + .alloc = NULL, + .free = NULL, + .hash = NULL, +}; + DEFINE_VECTOR(mScriptList, struct mScriptValue) void _allocTable(struct mScriptValue* val) {