diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index a6b478b17..bc9233025 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -24,6 +24,7 @@ struct mScriptContext { struct mScriptList refPool; struct Table weakrefs; uint32_t nextWeakref; + struct Table callbacks; }; struct mScriptEngine2 { @@ -66,6 +67,11 @@ struct mScriptValue* mScriptContextMakeWeakref(struct mScriptContext*, struct mS struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext*, struct mScriptValue* value); void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref); +void mScriptContextAttachStdlib(struct mScriptContext* context); + +void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback); +void mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value); + 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 bc81d3105..6c6b0c47d 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -102,6 +102,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_STR(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) #define mSCRIPT_TYPE_CMP_CHARP(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE) #define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE) +#define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_S_METHOD(STRUCT, NAME) mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME)->name == _mSCRIPT_FIELD_NAME diff --git a/src/core/thread.c b/src/core/thread.c index 2375aea4c..fb773e8ba 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -7,6 +7,9 @@ #include #include +#ifdef ENABLE_SCRIPTING +#include +#endif #include #include #include @@ -183,6 +186,42 @@ void _coreShutdown(void* context) { _changeState(thread->impl, mTHREAD_EXITING, true); } +#ifdef ENABLE_SCRIPTING +#define ADD_CALLBACK(NAME) \ +void _script_ ## NAME(void* context) { \ + struct mCoreThread* threadContext = context; \ + if (!threadContext->scriptContext) { \ + return; \ + } \ + mScriptContextTriggerCallback(threadContext->scriptContext, #NAME); \ +} + +ADD_CALLBACK(frame) +ADD_CALLBACK(crashed) +ADD_CALLBACK(sleep) +ADD_CALLBACK(stop) +ADD_CALLBACK(keysRead) +ADD_CALLBACK(savedataUpdated) +ADD_CALLBACK(alarm) + +#undef ADD_CALLBACK +#define CALLBACK(NAME) _script_ ## NAME + +static void _mCoreThreadAddCallbacks(struct mCoreThread* threadContext) { + struct mCoreCallbacks callbacks = { + .videoFrameEnded = CALLBACK(frame), + .coreCrashed = CALLBACK(crashed), + .sleep = CALLBACK(sleep), + .shutdown = CALLBACK(stop), + .keysRead = CALLBACK(keysRead), + .savedataUpdated = CALLBACK(savedataUpdated), + .alarm = CALLBACK(alarm), + .context = threadContext + }; + threadContext->core->addCoreCallbacks(threadContext->core, &callbacks); +} +#endif + static THREAD_ENTRY _mCoreThreadRun(void* context) { struct mCoreThread* threadContext = context; #ifdef USE_PTHREADS @@ -220,8 +259,10 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { } #ifdef ENABLE_SCRIPTING - if (threadContext->scriptContext) { - mScriptContextAttachCore(threadContext->scriptContext, core); + struct mScriptContext* scriptContext = threadContext->scriptContext; + if (scriptContext) { + mScriptContextAttachCore(scriptContext, core); + _mCoreThreadAddCallbacks(threadContext); } #endif @@ -229,6 +270,18 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { if (threadContext->startCallback) { threadContext->startCallback(threadContext); } +#ifdef ENABLE_SCRIPTING + // startCallback could add a script context + if (!scriptContext) { + scriptContext = threadContext->scriptContext; + if (scriptContext) { + _mCoreThreadAddCallbacks(threadContext); + } + } + if (scriptContext) { + mScriptContextTriggerCallback(scriptContext, "start"); + } +#endif core->reset(core); threadContext->impl->core = core; @@ -238,6 +291,19 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { threadContext->resetCallback(threadContext); } +#ifdef ENABLE_SCRIPTING + // resetCallback could add a script context + if (!scriptContext) { + scriptContext = threadContext->scriptContext; + if (scriptContext) { + _mCoreThreadAddCallbacks(threadContext); + } + } + if (scriptContext) { + mScriptContextTriggerCallback(scriptContext, "reset"); + } +#endif + struct mCoreThreadInternal* impl = threadContext->impl; bool wasPaused = false; int pendingRequests = 0; @@ -283,6 +349,14 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { MutexLock(&impl->stateMutex); } } +#ifdef ENABLE_SCRIPTING + if (!scriptContext) { + scriptContext = threadContext->scriptContext; + if (scriptContext) { + _mCoreThreadAddCallbacks(threadContext); + } + } +#endif if (wasPaused && !(impl->requested & mTHREAD_REQ_PAUSE)) { break; } @@ -324,6 +398,11 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { if (threadContext->resetCallback) { threadContext->resetCallback(threadContext); } +#ifdef ENABLE_SCRIPTING + if (scriptContext) { + mScriptContextTriggerCallback(scriptContext, "reset"); + } +#endif } if (pendingRequests & mTHREAD_REQ_RUN_ON) { if (threadContext->run) { @@ -344,8 +423,9 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { threadContext->cleanCallback(threadContext); } #ifdef ENABLE_SCRIPTING - if (threadContext->scriptContext) { - mScriptContextDetachCore(threadContext->scriptContext); + if (scriptContext) { + mScriptContextTriggerCallback(scriptContext, "shutdown"); + mScriptContextDetachCore(scriptContext); } #endif core->clearCoreCallbacks(core); diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index aa7cc1562..c579ccb9f 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -1,6 +1,7 @@ include(ExportDirectory) set(SOURCE_FILES context.c + stdlib.c types.c) set(TEST_FILES diff --git a/src/script/context.c b/src/script/context.c index ba552be41..9778a6ebe 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -60,6 +60,7 @@ void mScriptContextInit(struct mScriptContext* context) { mScriptListInit(&context->refPool, 0); TableInit(&context->weakrefs, 0, (void (*)(void*)) mScriptValueDeref); context->nextWeakref = 0; + HashTableInit(&context->callbacks, 0, (void (*)(void*)) mScriptValueDeref); } void mScriptContextDeinit(struct mScriptContext* context) { @@ -68,6 +69,7 @@ void mScriptContextDeinit(struct mScriptContext* context) { HashTableDeinit(&context->weakrefs); mScriptContextDrainPool(context); mScriptListDeinit(&context->refPool); + HashTableDeinit(&context->callbacks); } void mScriptContextFillPool(struct mScriptContext* context, struct mScriptValue* value) { @@ -177,6 +179,36 @@ void mScriptContextClearWeakref(struct mScriptContext* context, uint32_t weakref TableRemove(&context->weakrefs, weakref); } +void mScriptContextTriggerCallback(struct mScriptContext* context, const char* callback) { + struct mScriptValue* list = HashTableLookup(&context->callbacks, callback); + if (!list) { + return; + } + size_t i; + for (i = 0; i < mScriptListSize(list->value.opaque); ++i) { + struct mScriptFrame frame; + mScriptFrameInit(&frame); + struct mScriptValue* fn = mScriptListGetPointer(list->value.opaque, i); + if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { + fn = mScriptValueUnwrap(fn); + } + mScriptInvoke(fn, &frame); + mScriptFrameDeinit(&frame); + } +} + +void mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) { + if (fn->type->base != mSCRIPT_TYPE_FUNCTION) { + return; + } + struct mScriptValue* list = HashTableLookup(&context->callbacks, callback); + if (!list) { + list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + HashTableInsert(&context->callbacks, callback, list); + } + mScriptValueWrap(fn, mScriptListAppend(list->value.opaque)); +} + bool mScriptContextLoadVF(struct mScriptContext* context, const char* name, struct VFile* vf) { struct mScriptFileInfo info = { .name = name, diff --git a/src/script/stdlib.c b/src/script/stdlib.c new file mode 100644 index 000000000..08871fb49 --- /dev/null +++ b/src/script/stdlib.c @@ -0,0 +1,37 @@ +/* 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 + +struct mScriptCallbackAdapter { + struct mScriptContext* context; +}; + +static void _mScriptCallbackAdd(struct mScriptCallbackAdapter* adapter, struct mScriptString* name, struct mScriptValue* fn) { + if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { + fn = mScriptValueUnwrap(fn); + } + mScriptContextAddCallback(adapter->context, name->buffer, fn); +} + +mSCRIPT_DECLARE_STRUCT(mScriptCallbackAdapter); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCallbackAdapter, add, _mScriptCallbackAdd, 2, STR, callback, WRAPPER, function); + +mSCRIPT_DEFINE_STRUCT(mScriptCallbackAdapter) +mSCRIPT_DEFINE_DOCSTRING("Add a callback of the named type") +mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackAdapter, add) +mSCRIPT_DEFINE_END; + +void mScriptContextAttachStdlib(struct mScriptContext* context) { + struct mScriptValue* lib; + + lib = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCallbackAdapter)); + lib->value.opaque = calloc(1, sizeof(struct mScriptCallbackAdapter)); + *(struct mScriptCallbackAdapter*) lib->value.opaque = (struct mScriptCallbackAdapter) { + .context = context + }; + lib->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER; + mScriptContextSetGlobal(context, "callbacks", lib); +}