From cc8598373653453c6dd63de2340d2543fc754baa Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 27 Jan 2026 21:16:53 -0600 Subject: [PATCH] Add struct metafield --- assets/init.lua | 7 +- src/engine/engine.c | 5 +- src/event/event.c | 17 +++- src/event/event.h | 13 ++- src/input/input.c | 6 +- src/script/CMakeLists.txt | 1 + src/script/module/moduleevent.h | 8 +- src/script/module/moduleinput.h | 26 +++++ src/script/scriptstruct.c | 165 ++++++++++++++++++++++++++++++++ src/script/scriptstruct.h | 85 ++++++++++++++++ 10 files changed, 314 insertions(+), 19 deletions(-) create mode 100644 src/script/scriptstruct.c create mode 100644 src/script/scriptstruct.h diff --git a/assets/init.lua b/assets/init.lua index 6402dec..8f7de7f 100644 --- a/assets/init.lua +++ b/assets/init.lua @@ -43,8 +43,9 @@ end -- mapLoad('map/testmap/testmap.dmf') localeSet(DUSK_LOCALE_EN_US) -function eventTest() - print("Event system is working!") +function eventTest(data) + print("Pressed") + data.action = 2 end -eventSubscribe(EVENT_TEST, eventTest) \ No newline at end of file +eventSubscribe(INPUT_EVENT_PRESSED, eventTest) \ No newline at end of file diff --git a/src/engine/engine.c b/src/engine/engine.c index 483fe53..22c3073 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -20,6 +20,7 @@ #include "item/backpack.h" engine_t ENGINE; +scriptcontext_t ctx; errorret_t engineInit(const int32_t argc, const char_t **argv) { memoryZero(&ENGINE, sizeof(engine_t)); @@ -41,10 +42,8 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { backpackInit(); // Run the initial script. - scriptcontext_t ctx; errorChain(scriptContextInit(&ctx)); errorChain(scriptContextExecFile(&ctx, "init.dsf")); - scriptContextDispose(&ctx); errorOk(); } @@ -68,6 +67,8 @@ void engineExit(void) { } errorret_t engineDispose(void) { + scriptContextDispose(&ctx); + localeManagerDispose(); // sceneManagerDispose(); // rpgDispose(); diff --git a/src/event/event.c b/src/event/event.c index 1bce770..0a89b8c 100644 --- a/src/event/event.c +++ b/src/event/event.c @@ -8,6 +8,7 @@ #include "event.h" #include "assert/assert.h" #include "util/memory.h" +#include "script/scriptstruct.h" void eventInit( event_t *event, @@ -196,7 +197,11 @@ void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx) { } while(i < event->listenerCount); } -void eventInvoke(event_t *event, const void *eventParams) { +void eventInvoke( + event_t *event, + const void *eventParams, + const char_t *metatableName +) { assertNotNull(event, "Event cannot be NULL"); if(event->listenerCount == 0) return; @@ -221,8 +226,14 @@ void eventInvoke(event_t *event, const void *eventParams) { // Push function lua_rawgeti(L, LUA_REGISTRYINDEX, listener->user.script.luaFunctionRef); - // Push eventdata as lightuserdata - lua_pushlightuserdata(L, &data); + + if(eventParams != NULL && metatableName != NULL) { + scriptStructPush( + listener->user.script.context, + metatableName, + (void *)eventParams + ); + } // Call function with 1 arg, 0 return values if(lua_pcall(L, 1, 0, 0) != LUA_OK) { diff --git a/src/event/event.h b/src/event/event.h index c89f4fe..13c952a 100644 --- a/src/event/event.h +++ b/src/event/event.h @@ -104,9 +104,18 @@ void eventUnsubscribe(event_t *event, const eventsub_t subscription); void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx); /** - * Invoke an event, calling all subscribed listeners. + * Invoke an event, calling all subscribed listeners. Optionally provide event + * parameters, and if desired pass a metatable name. Event listeners will be + * able to read event params, and if metatable name is provided the script + * listeners will lookup metatable information matching that name to access the + * params as a structure within the script. * * @param event The event to invoke. * @param eventParams Parameters to pass to the event listeners. + * @param metatableName Metatable name. See details. */ -void eventInvoke(event_t *event, const void *eventParams); \ No newline at end of file +void eventInvoke( + event_t *event, + const void *eventParams, + const char_t *metatableName +); \ No newline at end of file diff --git a/src/input/input.c b/src/input/input.c index 6e3d54f..08f8377 100644 --- a/src/input/input.c +++ b/src/input/input.c @@ -98,12 +98,14 @@ void inputUpdate(void) { } while(cur->name); // Do we need to fire off events? + if(TIME.dynamicUpdate) return; + if(INPUT.eventPressed.listenerCount > 0) { action = &INPUT.actions[0]; do { if(inputPressed(action->action)) { inputevent_t inputEvent = { .action = action->action }; - eventInvoke(&INPUT.eventPressed, &inputEvent); + eventInvoke(&INPUT.eventPressed, &inputEvent, "input_mt"); } action++; } while(action < &INPUT.actions[INPUT_ACTION_COUNT]); @@ -114,7 +116,7 @@ void inputUpdate(void) { do { if(inputReleased(action->action)) { inputevent_t inputEvent = { .action = action->action }; - eventInvoke(&INPUT.eventReleased, &inputEvent); + eventInvoke(&INPUT.eventReleased, &inputEvent, "input_mt"); } action++; } while(action < &INPUT.actions[INPUT_ACTION_COUNT]); diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index 57e46d6..259d4b5 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -9,4 +9,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} scriptmanager.c scriptcontext.c scriptmodule.c + scriptstruct.c ) \ No newline at end of file diff --git a/src/script/module/moduleevent.h b/src/script/module/moduleevent.h index 2565524..9512c10 100644 --- a/src/script/module/moduleevent.h +++ b/src/script/module/moduleevent.h @@ -40,12 +40,6 @@ int moduleEventSubscribe(lua_State *L) { } void moduleEvent(scriptcontext_t *context) { + // Reg functions scriptContextRegFunc(context, "eventSubscribe", moduleEventSubscribe); - - // register test event constant - scriptContextRegPointer( - context, - "EVENT_TEST", - (void *)&EVENT_TEST - ); } \ No newline at end of file diff --git a/src/script/module/moduleinput.h b/src/script/module/moduleinput.h index 21537c9..4b27e53 100644 --- a/src/script/module/moduleinput.h +++ b/src/script/module/moduleinput.h @@ -8,6 +8,7 @@ #pragma once #include "script/scriptcontext.h" #include "input/input.h" +#include "script/scriptstruct.h" int moduleInputBind(lua_State *L) { assertNotNull(L, "Lua state cannot be NULL"); @@ -44,6 +45,19 @@ int moduleInputBind(lua_State *L) { return 0; } +void moduleInputEventGetter( + const scriptcontext_t *context, + const char_t *key, + const void *structPtr, + scriptvalue_t *outValue +) { + if(stringCompare(key, "action") == 0) { + outValue->type = SCRIPT_VALUE_TYPE_INT; + outValue->value.intValue = ((const inputevent_t*)structPtr)->action; + return; + } +} + void moduleInput(scriptcontext_t *context) { assertNotNull(context, "Script context cannot be NULL"); @@ -63,6 +77,18 @@ void moduleInput(scriptcontext_t *context) { #endif ); + // Script structure + scriptStructRegister( + context, + "input_mt", + &moduleInputEventGetter, + NULL + ); + + // Events + scriptContextRegPointer(context,"INPUT_EVENT_PRESSED",&INPUT.eventPressed); + scriptContextRegPointer(context,"INPUT_EVENT_RELEASED",&INPUT.eventReleased); + // Bind methods scriptContextRegFunc(context, "inputBind", moduleInputBind); } \ No newline at end of file diff --git a/src/script/scriptstruct.c b/src/script/scriptstruct.c new file mode 100644 index 0000000..39f10a2 --- /dev/null +++ b/src/script/scriptstruct.c @@ -0,0 +1,165 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "scriptstruct.h" +#include "assert/assert.h" + +void scriptStructRegister( + scriptcontext_t *context, + const char_t *metatableName, + const scriptstructgetter_t getter, + const scriptstructsetter_t setter +) { + assertNotNull(context, "Script context cannot be NULL"); + assertNotNull(metatableName, "Metatable name cannot be NULL"); + + // Create metatable + if(!luaL_newmetatable(context->luaState, metatableName)) return; + + // Create a Lua owned structure for holding the metatable context. + structmetatablecontext_t *metaContext = lua_newuserdata( + context->luaState, + sizeof(structmetatablecontext_t) + ); + + metaContext->context = context; + metaContext->getter = getter; + metaContext->setter = setter; + + // Store in the metatable. + lua_setfield(context->luaState, -2, "__structmetatablecontext"); + + // Set __index and __newindex metamethods for Lua. + lua_pushcfunction(context->luaState, scriptStructIndex); + lua_setfield(context->luaState, -2, "__index"); + + lua_pushcfunction(context->luaState, scriptStructNewIndex); + lua_setfield(context->luaState, -2, "__newindex"); +} + +int scriptStructIndex(lua_State *l) { + structmetatablecontext_t *ctx = scriptStructGetMetatableContext(l); + if(!ctx->getter) { + luaL_error(l, "Attempt to read from write-only structure field"); + return 0; + } + + const char_t *key = lua_tostring(l, 2); + + void *structPtr = *(void **)lua_touserdata(l, 1); + assertNotNull(structPtr, "Structure pointer cannot be NULL"); + + scriptvalue_t outValue = { .type = SCRIPT_VALUE_TYPE_NIL }; + ctx->getter(ctx->context, key, structPtr, &outValue); + + switch(outValue.type) { + case SCRIPT_VALUE_TYPE_INT: + lua_pushinteger(l, outValue.value.intValue); + break; + + case SCRIPT_VALUE_TYPE_FLOAT: + lua_pushnumber(l, outValue.value.floatValue); + break; + + case SCRIPT_VALUE_TYPE_STRING: + lua_pushstring(l, outValue.value.strValue); + break; + + case SCRIPT_VALUE_TYPE_BOOL: + lua_pushboolean(l, outValue.value.boolValue); + break; + + default: + lua_pushnil(l); + break; + } + + return 1; +} + +int scriptStructNewIndex(lua_State *l) { + structmetatablecontext_t *ctx = scriptStructGetMetatableContext(l); + if(ctx->setter == NULL) { + luaL_error(l, "Attempt to set read-only structure field"); + return 0; + } + + const char_t *key = lua_tostring(l, 2); + + void *structPtr = *(void **)lua_touserdata(l, 1); + assertNotNull(structPtr, "Structure pointer cannot be NULL"); + + scriptvalue_t inValue; + int t = lua_type(l, 3); + switch(t) { + case LUA_TNUMBER: + if(lua_isinteger(l, 3)) { + inValue.type = SCRIPT_VALUE_TYPE_INT; + inValue.value.intValue = (int32_t)lua_tointeger(l, 3); + } else { + inValue.type = SCRIPT_VALUE_TYPE_FLOAT; + inValue.value.floatValue = (float)lua_tonumber(l, 3); + } + break; + + case LUA_TSTRING: + inValue.type = SCRIPT_VALUE_TYPE_STRING; + inValue.value.strValue = lua_tostring(l, 3); + break; + + case LUA_TBOOLEAN: + inValue.type = SCRIPT_VALUE_TYPE_BOOL; + inValue.value.boolValue = lua_toboolean(l, 3); + break; + case LUA_TNIL: + inValue.type = SCRIPT_VALUE_TYPE_NIL; + break; + + default: + assertUnreachable("Unsupported value type for struct field assignment"); + break; + } + + ctx->setter(ctx->context, key, structPtr, &inValue); + + lua_pushnil(l); + return 1; +} + +structmetatablecontext_t * scriptStructGetMetatableContext(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_getmetatable(L, 1)) { + assertUnreachable("Expected metatable on structure"); + } + + lua_getfield(L, -1, "__structmetatablecontext"); + structmetatablecontext_t *metaContext = ( + (structmetatablecontext_t *)lua_touserdata(L, -1) + ); + assertNotNull(metaContext, "Metatable context userdata cannot be NULL"); + lua_pop(L, 2); + return metaContext; +} + +void scriptStructPush( + scriptcontext_t *context, + const char_t *metatableName, + void *structPtr +) { + assertNotNull(context, "Script context cannot be NULL"); + assertNotNull(metatableName, "Metatable name cannot be NULL"); + assertNotNull(structPtr, "Structure pointer cannot be NULL"); + + // Create userdata + void **ud = (void **)lua_newuserdata(context->luaState, sizeof(void *)); + *ud = structPtr; + + // Set metatable + luaL_getmetatable(context->luaState, metatableName); + lua_setmetatable(context->luaState, -2); +} \ No newline at end of file diff --git a/src/script/scriptstruct.h b/src/script/scriptstruct.h new file mode 100644 index 0000000..4fde009 --- /dev/null +++ b/src/script/scriptstruct.h @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "scriptcontext.h" + +typedef void (*scriptstructgetter_t)( + const scriptcontext_t *context, + const char_t *key, + const void *structPtr, + scriptvalue_t *outValue +); + +typedef void (*scriptstructsetter_t)( + const scriptcontext_t *context, + const char_t *key, + void *structPtr, + const scriptvalue_t *inValue +); + +typedef struct { + scriptcontext_t *context; + scriptstructgetter_t getter; + scriptstructsetter_t setter; +} structmetatablecontext_t; + +/** + * Registers a script structure to allow a script to access its fields. You will + * be creating a new metatable in Lua with the given name, so Ideally use a name + * like Ideally struct_mt e.g. player_t or entity_t. + * + * @param context The script context. + * @param metatableName The name of the metatable to register + * @param getter The getter function for retrieving field values. + * @param setter The setter function for setting field values. + */ +void scriptStructRegister( + scriptcontext_t *context, + const char_t *metatableName, + const scriptstructgetter_t getter, + const scriptstructsetter_t setter +); + +/** + * Callback received from Lua to index (get) a structure field. + * + * @param l The Lua state. + * @return The number of return values. + */ +int scriptStructIndex(lua_State *l); + +/** + * Callback received from Lua to newindex (set) a structure field. + * + * @param l The Lua state. + * @return The number of return values. + */ +int scriptStructNewIndex(lua_State *l); + +/** + * Pops the metatable context from the given Lua state. This is only valid from + * within the index or newindex methods. + * + * @param l The Lua state. + * @return The metatable context. + */ +structmetatablecontext_t *scriptStructGetMetatableContext(lua_State *l); + +/** + * Pushes a structure onto the Lua stack, associating it with the given + * metatable. + * + * @param context The script context. + * @param metatableName The name of the metatable to associate with. + * @param structPtr Pointer to the structure to push. + */ +void scriptStructPush( + scriptcontext_t *context, + const char_t *metatableName, + void *structPtr +); \ No newline at end of file