Scripting: Add `callbacks:oneshot` for single-call callbacks

This commit is contained in:
Vicki Pfau 2023-02-08 19:14:36 -08:00
parent ff449dc66c
commit 123532ed6e
5 changed files with 115 additions and 30 deletions

View File

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

View File

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

View File

@ -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 TableIterator iter;
if (!TableIteratorStart(table, &iter)) {
return;
}
struct UInt32List oneshots;
UInt32ListInit(&oneshots, 0);
do {
struct mScriptFrame frame;
struct mScriptValue* fn = mScriptListGetPointer(list->value.list, i);
if (!fn->type) {
continue;
}
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) {

View File

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

View File

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