mirror of https://github.com/mgba-emu/mgba.git
Scripting: Add `callbacks:oneshot` for single-call callbacks
This commit is contained in:
parent
ff449dc66c
commit
123532ed6e
1
CHANGES
1
CHANGES
|
@ -22,6 +22,7 @@ Misc:
|
|||
- GBA: Improve detection of valid ELF ROMs
|
||||
- Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796)
|
||||
- Qt: Stop eating boolean action key events (fixes mgba.io/i/2636)
|
||||
- Scripting: Add `callbacks:oneshot` for single-call callbacks
|
||||
|
||||
0.10.1: (2023-01-10)
|
||||
Emulation fixes:
|
||||
|
|
|
@ -98,6 +98,7 @@ void mScriptContextExportNamespace(struct mScriptContext* context, const char* n
|
|||
|
||||
void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback, struct mScriptList* args);
|
||||
uint32_t mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value);
|
||||
uint32_t mScriptContextAddOneshot(struct mScriptContext*, const char* callback, struct mScriptValue* value);
|
||||
void mScriptContextRemoveCallback(struct mScriptContext*, uint32_t cbid);
|
||||
|
||||
void mScriptContextSetDocstring(struct mScriptContext*, const char* key, const char* docstring);
|
||||
|
|
|
@ -17,8 +17,10 @@ struct mScriptFileInfo {
|
|||
};
|
||||
|
||||
struct mScriptCallbackInfo {
|
||||
struct mScriptValue* fn;
|
||||
const char* callback;
|
||||
size_t id;
|
||||
uint32_t id;
|
||||
bool oneshot;
|
||||
};
|
||||
|
||||
static void _engineContextDestroy(void* ctx) {
|
||||
|
@ -56,13 +58,28 @@ static void _contextFindForFile(const char* key, void* value, void* user) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _freeTable(void* data) {
|
||||
struct Table* table = data;
|
||||
|
||||
struct TableIterator iter;
|
||||
if (TableIteratorStart(table, &iter)) {
|
||||
do {
|
||||
struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter);
|
||||
mScriptValueDeref(info->fn);
|
||||
} while (TableIteratorNext(table, &iter));
|
||||
}
|
||||
|
||||
TableDeinit(table);
|
||||
free(table);
|
||||
}
|
||||
|
||||
void mScriptContextInit(struct mScriptContext* context) {
|
||||
HashTableInit(&context->rootScope, 0, (void (*)(void*)) mScriptValueDeref);
|
||||
HashTableInit(&context->engines, 0, _engineContextDestroy);
|
||||
mScriptListInit(&context->refPool, 0);
|
||||
TableInit(&context->weakrefs, 0, (void (*)(void*)) mScriptValueDeref);
|
||||
context->nextWeakref = 1;
|
||||
HashTableInit(&context->callbacks, 0, (void (*)(void*)) mScriptValueDeref);
|
||||
HashTableInit(&context->callbacks, 0, _freeTable);
|
||||
TableInit(&context->callbackId, 0, free);
|
||||
context->nextCallbackId = 1;
|
||||
context->constants = NULL;
|
||||
|
@ -204,46 +221,60 @@ void mScriptContextDisownWeakref(struct mScriptContext* context, uint32_t weakre
|
|||
}
|
||||
|
||||
void mScriptContextTriggerCallback(struct mScriptContext* context, const char* callback, struct mScriptList* args) {
|
||||
struct mScriptValue* list = HashTableLookup(&context->callbacks, callback);
|
||||
if (!list) {
|
||||
struct Table* table = HashTableLookup(&context->callbacks, callback);
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
size_t i;
|
||||
for (i = 0; i < mScriptListSize(list->value.list); ++i) {
|
||||
struct mScriptFrame frame;
|
||||
struct mScriptValue* fn = mScriptListGetPointer(list->value.list, i);
|
||||
if (!fn->type) {
|
||||
continue;
|
||||
struct TableIterator iter;
|
||||
if (!TableIteratorStart(table, &iter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct UInt32List oneshots;
|
||||
UInt32ListInit(&oneshots, 0);
|
||||
do {
|
||||
struct mScriptFrame frame;
|
||||
struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter);
|
||||
mScriptFrameInit(&frame);
|
||||
if (args) {
|
||||
mScriptListCopy(&frame.arguments, args);
|
||||
}
|
||||
if (fn->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
fn = mScriptValueUnwrap(fn);
|
||||
}
|
||||
mScriptInvoke(fn, &frame);
|
||||
mScriptInvoke(info->fn, &frame);
|
||||
mScriptFrameDeinit(&frame);
|
||||
|
||||
if (info->oneshot) {
|
||||
*UInt32ListAppend(&oneshots) = info->id;
|
||||
}
|
||||
} while (TableIteratorNext(table, &iter));
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < UInt32ListSize(&oneshots); ++i) {
|
||||
mScriptContextRemoveCallback(context, *UInt32ListGetPointer(&oneshots, i));
|
||||
}
|
||||
UInt32ListDeinit(&oneshots);
|
||||
}
|
||||
|
||||
uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) {
|
||||
static uint32_t mScriptContextAddCallbackInternal(struct mScriptContext* context, const char* callback, struct mScriptValue* fn, bool oneshot) {
|
||||
if (fn->type->base != mSCRIPT_TYPE_FUNCTION) {
|
||||
return 0;
|
||||
}
|
||||
struct mScriptValue* list = HashTableLookup(&context->callbacks, callback);
|
||||
if (!list) {
|
||||
list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST);
|
||||
HashTableInsert(&context->callbacks, callback, list);
|
||||
struct Table* table = HashTableLookup(&context->callbacks, callback);
|
||||
if (!table) {
|
||||
table = calloc(1, sizeof(*table));
|
||||
TableInit(table, 0, NULL);
|
||||
HashTableInsert(&context->callbacks, callback, table);
|
||||
}
|
||||
struct mScriptCallbackInfo* info = malloc(sizeof(*info));
|
||||
// Steal the string from the table key, since it's guaranteed to outlive this struct
|
||||
struct TableIterator iter;
|
||||
HashTableIteratorLookup(&context->callbacks, &iter, callback);
|
||||
info->callback = HashTableIteratorGetKey(&context->callbacks, &iter);
|
||||
info->id = mScriptListSize(list->value.list);
|
||||
info->oneshot = oneshot;
|
||||
if (fn->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
fn = mScriptValueUnwrap(fn);
|
||||
}
|
||||
info->fn = fn;
|
||||
mScriptValueRef(fn);
|
||||
mScriptValueWrap(fn, mScriptListAppend(list->value.list));
|
||||
while (true) {
|
||||
uint32_t id = context->nextCallbackId;
|
||||
++context->nextCallbackId;
|
||||
|
@ -251,8 +282,19 @@ uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* c
|
|||
continue;
|
||||
}
|
||||
TableInsert(&context->callbackId, id, info);
|
||||
return id;
|
||||
info->id = id;
|
||||
break;
|
||||
}
|
||||
TableInsert(table, info->id, info);
|
||||
return info->id;
|
||||
}
|
||||
|
||||
uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) {
|
||||
return mScriptContextAddCallbackInternal(context, callback, fn, false);
|
||||
}
|
||||
|
||||
uint32_t mScriptContextAddOneshot(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) {
|
||||
return mScriptContextAddCallbackInternal(context, callback, fn, true);
|
||||
}
|
||||
|
||||
void mScriptContextRemoveCallback(struct mScriptContext* context, uint32_t cbid) {
|
||||
|
@ -260,16 +302,13 @@ void mScriptContextRemoveCallback(struct mScriptContext* context, uint32_t cbid)
|
|||
if (!info) {
|
||||
return;
|
||||
}
|
||||
struct mScriptValue* list = HashTableLookup(&context->callbacks, info->callback);
|
||||
if (!list) {
|
||||
struct Table* table = HashTableLookup(&context->callbacks, info->callback);
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
if (info->id >= mScriptListSize(list->value.list)) {
|
||||
return;
|
||||
}
|
||||
struct mScriptValue* fn = mScriptValueUnwrap(mScriptListGetPointer(list->value.list, info->id));
|
||||
mScriptValueDeref(fn);
|
||||
mScriptListGetPointer(list->value.list, info->id)->type = NULL;
|
||||
mScriptValueDeref(info->fn);
|
||||
TableRemove(table, cbid);
|
||||
TableRemove(&context->callbackId, cbid);
|
||||
}
|
||||
|
||||
void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants) {
|
||||
|
|
|
@ -27,12 +27,21 @@ static uint32_t _mScriptCallbackAdd(struct mScriptCallbackManager* adapter, stru
|
|||
return id;
|
||||
}
|
||||
|
||||
static uint32_t _mScriptCallbackOneshot(struct mScriptCallbackManager* adapter, struct mScriptString* name, struct mScriptValue* fn) {
|
||||
if (fn->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
fn = mScriptValueUnwrap(fn);
|
||||
}
|
||||
uint32_t id = mScriptContextAddOneshot(adapter->context, name->buffer, fn);
|
||||
return id;
|
||||
}
|
||||
|
||||
static void _mScriptCallbackRemove(struct mScriptCallbackManager* adapter, uint32_t id) {
|
||||
mScriptContextRemoveCallback(adapter->context, id);
|
||||
}
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT(mScriptCallbackManager);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCallbackManager, U32, add, _mScriptCallbackAdd, 2, STR, callback, WRAPPER, function);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCallbackManager, U32, oneshot, _mScriptCallbackOneshot, 2, STR, callback, WRAPPER, function);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCallbackManager, remove, _mScriptCallbackRemove, 1, U32, cbid);
|
||||
|
||||
static uint64_t mScriptMakeBitmask(struct mScriptList* list) {
|
||||
|
@ -83,6 +92,8 @@ mSCRIPT_DEFINE_STRUCT(mScriptCallbackManager)
|
|||
)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Add a callback of the named type. The returned id can be used to remove it later")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, add)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Add a one-shot callback of the named type that will be automatically removed after called. The returned id can be used to remove it early")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, oneshot)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Remove a callback with the previously retuned id")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, remove)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
|
|
@ -96,8 +96,41 @@ M_TEST_DEFINE(callbacks) {
|
|||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(oneshot) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM(
|
||||
"val = 0\n"
|
||||
"function cb()\n"
|
||||
" val = val + 1\n"
|
||||
"end\n"
|
||||
"id = callbacks:oneshot('test', cb)\n"
|
||||
"assert(id)"
|
||||
);
|
||||
|
||||
TEST_VALUE(S32, "val", 0);
|
||||
|
||||
mScriptContextTriggerCallback(&context, "test", NULL);
|
||||
TEST_VALUE(S32, "val", 1);
|
||||
|
||||
mScriptContextTriggerCallback(&context, "test", NULL);
|
||||
TEST_VALUE(S32, "val", 1);
|
||||
|
||||
TEST_PROGRAM(
|
||||
"id = callbacks:oneshot('test', cb)\n"
|
||||
"assert(id)\n"
|
||||
"callbacks:remove(id)"
|
||||
);
|
||||
|
||||
mScriptContextTriggerCallback(&context, "test", NULL);
|
||||
TEST_VALUE(S32, "val", 1);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStdlib,
|
||||
cmocka_unit_test(bitMask),
|
||||
cmocka_unit_test(bitUnmask),
|
||||
cmocka_unit_test(callbacks),
|
||||
cmocka_unit_test(oneshot),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue