diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fcb031..5a471c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/mod if(NOT DEFINED DUSK_TARGET_SYSTEM) set(DUSK_TARGET_SYSTEM "linux") - #set(DUSK_TARGET_SYSTEM "psp") + # set(DUSK_TARGET_SYSTEM "psp") endif() # Prep cache diff --git a/assets/script/test.lua b/assets/script/test.lua index 32aca49..9ba9463 100644 --- a/assets/script/test.lua +++ b/assets/script/test.lua @@ -1 +1,7 @@ -print("Test Lua script") \ No newline at end of file +function testFunction() + print("Hello from testFunction!") +end + +function doAdd(a, b) + return a + b +end \ No newline at end of file diff --git a/src/debug/debug.c b/src/debug/debug.c index 52c5b1b..4a9f7e1 100644 --- a/src/debug/debug.c +++ b/src/debug/debug.c @@ -12,9 +12,7 @@ void debugPrint(const char_t *message, ...) { va_start(args, message); vprintf(message, args); va_end(args); - - // For the time being just use standard printing functions. - printf(message, args); + fflush(stdout); #if PSP FILE *file = fopen("ms0:/PSP/GAME/Dusk/debug.log", "a"); diff --git a/src/engine/engine.c b/src/engine/engine.c index 6aef2de..a318591 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -18,6 +18,8 @@ #include "script/scriptmanager.h" #include "debug/debug.h" +#include "script/scriptcontext.h" + engine_t ENGINE; errorret_t engineInit(const int32_t argc, const char_t **argv) { @@ -38,10 +40,20 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(sceneManagerInit()); errorChain(scriptManagerInit()); - // scriptManagerExec( - // "print('Hello from Lua!')\n" - // "luaCallable()\n" - // ); + scriptcontext_t testCtx; + errorChain(scriptContextInit(&testCtx)); + errorChain(scriptContextExecFile(&testCtx, "script/test.dsf")); + errorChain(scriptContextCallFunc(&testCtx, "testFunction", NULL, 0, NULL)); + + scriptvalue_t args[2] = { + { .type = SCRIPT_VALUE_TYPE_INT, .value.intValue = 5 }, + { .type = SCRIPT_VALUE_TYPE_INT, .value.intValue = 7 } + }; + scriptvalue_t ret = { .type = SCRIPT_VALUE_TYPE_INT }; + errorChain(scriptContextCallFunc(&testCtx, "doAdd", args, 2, &ret)); + printf("doAdd returned: %d\n", ret.value.intValue); + + scriptContextDispose(&testCtx); errorOk(); } @@ -55,7 +67,6 @@ errorret_t engineUpdate(void) { sceneManagerUpdate(); errorChain(displayUpdate()); - if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false; errorOk(); diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index 5d90652..1e60d85 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -7,4 +7,5 @@ target_sources(${DUSK_TARGET_NAME} PRIVATE scriptmanager.c + scriptcontext.c ) \ No newline at end of file diff --git a/src/script/scriptcontext.c b/src/script/scriptcontext.c new file mode 100644 index 0000000..ef68d5e --- /dev/null +++ b/src/script/scriptcontext.c @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "scriptcontext.h" +#include "assert/assert.h" +#include "asset/asset.h" +#include "util/memory.h" +#include "debug/debug.h" + +errorret_t scriptContextInit(scriptcontext_t *context) { + assertNotNull(context, "Script context cannot be NULL"); + + memoryZero(context, sizeof(scriptcontext_t)); + + // Create a new Lua state for this context. + context->luaState = luaL_newstate(); + if(context->luaState == NULL) { + errorThrow("Failed to init Lua state"); + } + luaL_openlibs(context->luaState); + + // Register shared functions + scriptContextRegFunc(context, "print", scriptContextPrint); + + errorOk(); +} + +void scriptContextRegFunc( + scriptcontext_t *context, + const char_t *fnName, + lua_CFunction function +) { + assertNotNull(context, "Script context cannot be NULL"); + assertNotNull(fnName, "Function name cannot be NULL"); + assertNotNull(function, "Function cannot be NULL"); + + lua_register(context->luaState, fnName, function); +} + +errorret_t scriptContextCallFunc( + scriptcontext_t *context, + const char_t *fnName, + const scriptvalue_t *args, + const int32_t argCount, + scriptvalue_t *retValue +) { + assertNotNull(context, "Script context cannot be NULL"); + assertNotNull(fnName, "Function name cannot be NULL"); + assertTrue(args == NULL || argCount >= 0, "Invalid arg count"); + + // Get func + lua_getglobal(context->luaState, fnName); + if(!lua_isfunction(context->luaState, -1)) { + errorThrow("Function '%s' not found in script context", fnName); + } + + // Push args + for(int32_t i = 0; i < argCount; i++) { + const scriptvalue_t *arg = &args[i]; + switch(arg->type) { + case SCRIPT_VALUE_TYPE_INT: + lua_pushinteger(context->luaState, arg->value.intValue); + break; + + case SCRIPT_VALUE_TYPE_FLOAT: + lua_pushnumber(context->luaState, arg->value.floatValue); + break; + + case SCRIPT_VALUE_TYPE_STRING: + lua_pushstring(context->luaState, arg->value.strValue); + break; + + default: + errorThrow("Unsupported argument type %d", arg->type); + } + } + + // Call func + if(lua_pcall( + context->luaState, + args ? argCount : 0, + retValue ? 1 : 0, + 0 + ) != LUA_OK) { + const char_t *strErr = lua_tostring(context->luaState, -1); + lua_pop(context->luaState, 1); + errorThrow("Failed to call function '%s': %s", fnName, strErr); + } + + // Was there a ret value? + if(retValue == NULL) { + errorOk(); + } + + // Get ret value + switch(retValue->type) { + case SCRIPT_VALUE_TYPE_INT: + if(!lua_isinteger(context->luaState, -1)) { + errorThrow("Expected integer return value from '%s'", fnName); + } + retValue->value.intValue = (int32_t)lua_tointeger(context->luaState, -1); + break; + + case SCRIPT_VALUE_TYPE_FLOAT: + if(!lua_isnumber(context->luaState, -1)) { + errorThrow("Expected float return value from '%s'", fnName); + } + retValue->value.floatValue = (float)lua_tonumber(context->luaState, -1); + break; + + // case SCRIPT_VALUE_TYPE_STRING: + // if(!lua_isstring(context->luaState, -1)) { + // errorThrow("Expected string return value from '%s'", fnName); + // } + // retValue->value.strValue = lua_tostring(context->luaState, -1); + // break; + + default: + errorThrow("Unsupported return value type %d", retValue->type); + } + + errorOk(); +} + +errorret_t scriptContextExec(scriptcontext_t *context, const char_t *script) { + assertNotNull(context, "Script context cannot be NULL"); + assertNotNull(script, "Script cannot be NULL"); + + if(luaL_dostring(context->luaState, script) != LUA_OK) { + const char_t *strErr = lua_tostring(context->luaState, -1); + lua_pop(context->luaState, 1); + errorThrow("Failed to execute Lua: ", strErr); + } + + errorOk(); +} + +errorret_t scriptContextExecFile(scriptcontext_t *ctx, const char_t *fname) { + assertNotNull(ctx, "Script context cannot be NULL"); + assertNotNull(fname, "Filename cannot be NULL"); + + assetscript_t script; + errorChain(assetLoad(fname, &script)); + + if(lua_load( + ctx->luaState, assetScriptReader, &script, fname, NULL + ) != LUA_OK) { + const char_t *strErr = lua_tostring(ctx->luaState, -1); + lua_pop(ctx->luaState, 1); + errorThrow("Failed to load Lua script: %s", strErr); + } + + if(lua_pcall(ctx->luaState, 0, LUA_MULTRET, 0) != LUA_OK) { + const char_t *strErr = lua_tostring(ctx->luaState, -1); + lua_pop(ctx->luaState, 1); + errorThrow("Failed to execute Lua script: %s", strErr); + } + + errorChain(assetScriptDispose(&script)); + errorOk(); +} + +int32_t scriptContextPrint(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + int n = lua_gettop(L); + luaL_Buffer b; + luaL_buffinit(L, &b); + + for (int i = 1; i <= n; ++i) { + size_t len; + const char *s = luaL_tolstring(L, i, &len); // converts any value to string + luaL_addlstring(&b, s, len); + lua_pop(L, 1); // pop result of luaL_tolstring + if (i < n) luaL_addlstring(&b, "\t", 1); + } + + luaL_pushresult(&b); + const char *msg = lua_tostring(L, -1); + debugPrint("%s\n", msg); + return 0; // no values returned to Lua +} + +void scriptContextDispose(scriptcontext_t *context) { + assertNotNull(context, "Script context cannot be NULL"); + assertNotNull(context->luaState, "Lua state is not initialized"); + + if(context->luaState != NULL) { + lua_close(context->luaState); + context->luaState = NULL; + } +} \ No newline at end of file diff --git a/src/script/scriptcontext.h b/src/script/scriptcontext.h new file mode 100644 index 0000000..97b9568 --- /dev/null +++ b/src/script/scriptcontext.h @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "scriptvalue.h" +#include +#include +#include + +typedef struct scriptcontext_s { + lua_State *luaState; +} scriptcontext_t; + +/** + * Initialize a script context. + * + * @param context The script context to initialize. + * @return The error return value. + */ +errorret_t scriptContextInit(scriptcontext_t *context); + +/** + * Register a C function within a script context. + * + * @param context The script context to use. + * @param fnName The name of the function in Lua. + * @param function The C function to register. + */ +void scriptContextRegFunc( + scriptcontext_t *context, + const char_t *fnName, + lua_CFunction function +); + +/** + * Call a Lua function within a script context. + * + * @param context The script context to use. + * @param fnName The name of the Lua function to call. + * @param args Array of args to pass to the function (or NULL for no args) + * @param argCount The number of arguments in the args array (omitable). + * @param retValue Output to store returned value (or NULL for no return value). + * @return The error return value. + */ + +errorret_t scriptContextCallFunc( + scriptcontext_t *context, + const char_t *fnName, + const scriptvalue_t *args, + const int32_t argCount, + scriptvalue_t *retValue +); + +/** + * Execute a script within a script context. + * + * @param context The script context to use. + * @param script The script to execute. + * @return The error return value. + */ +errorret_t scriptContextExec(scriptcontext_t *context, const char_t *script); + +/** + * Execute a script from a file within a script context. + * + * @param ctx The script context to use. + * @param fname The filename of the script to execute. + * @return The error return value. + */ +errorret_t scriptContextExecFile(scriptcontext_t *ctx, const char_t *fname); + +/** + * Overridden print function for Lua scripts to output to debug. + * + * @param L The Lua state. + * @return The number of return values. + */ +int32_t scriptContextPrint(lua_State *L); + +/** + * Dispose of a script context. + * + * @param context The script context to dispose of. + */ +void scriptContextDispose(scriptcontext_t *context); \ No newline at end of file diff --git a/src/script/scriptmanager.c b/src/script/scriptmanager.c index 3db2623..d773329 100644 --- a/src/script/scriptmanager.c +++ b/src/script/scriptmanager.c @@ -8,74 +8,16 @@ #include "scriptmanager.h" #include "util/memory.h" #include "assert/assert.h" - +#include "debug/debug.h" #include "asset/asset.h" -int luaCallable(lua_State *L) { - printf("This function was called from Lua!\n"); - return 0; -} - scriptmanager_t SCRIPT_MANAGER; errorret_t scriptManagerInit() { memoryZero(&SCRIPT_MANAGER, sizeof(scriptmanager_t)); - - SCRIPT_MANAGER.luaState = luaL_newstate(); - if(SCRIPT_MANAGER.luaState == NULL) { - errorThrow("Failed to init Lua state"); - } - - luaL_openlibs(SCRIPT_MANAGER.luaState); - lua_register(SCRIPT_MANAGER.luaState, "luaCallable", luaCallable); - - errorChain(scriptManagerExecFile("script/test.dsf")); - errorOk(); -} - -errorret_t scriptManagerExecString(const char_t *script) { - assertNotNull(script, "Script cannot be NULL"); - assertNotNull(SCRIPT_MANAGER.luaState, "Lua state is not initialized"); - - if(luaL_dostring(SCRIPT_MANAGER.luaState, script) != LUA_OK) { - const char_t *strErr = lua_tostring(SCRIPT_MANAGER.luaState, -1); - lua_pop(SCRIPT_MANAGER.luaState, 1); - errorThrow("Failed to execute Lua: ", strErr); - } - - errorOk(); -} - -errorret_t scriptManagerExecFile(const char_t *filename) { - assertNotNull(filename, "Filename cannot be NULL"); - assertNotNull(SCRIPT_MANAGER.luaState, "Lua state is not initialized"); - - assetscript_t script; - errorChain(assetLoad(filename, &script)); - - if(lua_load( - SCRIPT_MANAGER.luaState, assetScriptReader, &script, filename, NULL - ) != LUA_OK) { - const char_t *strErr = lua_tostring(SCRIPT_MANAGER.luaState, -1); - lua_pop(SCRIPT_MANAGER.luaState, 1); - errorThrow("Failed to load Lua script: %s", strErr); - } - - if(lua_pcall(SCRIPT_MANAGER.luaState, 0, LUA_MULTRET, 0) != LUA_OK) { - const char_t *strErr = lua_tostring(SCRIPT_MANAGER.luaState, -1); - lua_pop(SCRIPT_MANAGER.luaState, 1); - errorThrow("Failed to execute Lua script: %s", strErr); - } - - errorChain(assetScriptDispose(&script)); errorOk(); } errorret_t scriptManagerDispose() { - if(SCRIPT_MANAGER.luaState != NULL) { - lua_close(SCRIPT_MANAGER.luaState); - SCRIPT_MANAGER.luaState = NULL; - } - errorOk(); } \ No newline at end of file diff --git a/src/script/scriptmanager.h b/src/script/scriptmanager.h index c0749b6..9566095 100644 --- a/src/script/scriptmanager.h +++ b/src/script/scriptmanager.h @@ -7,12 +7,9 @@ #pragma once #include "error/error.h" -#include -#include -#include typedef struct scriptmanager_s { - lua_State *luaState; + void *nothing; } scriptmanager_t; extern scriptmanager_t SCRIPT_MANAGER; @@ -24,22 +21,6 @@ extern scriptmanager_t SCRIPT_MANAGER; */ errorret_t scriptManagerInit(); -/** - * Execute a Lua script. - * - * @param script The script to execute. - * @return The error return value. - */ -errorret_t scriptManagerExecString(const char_t *script); - -/** - * Execute a Lua script from a file. - * - * @param filename The filename of the script to execute. - * @return The error return value. - */ -errorret_t scriptManagerExecFile(const char_t *filename); - /** * Dispose of the script manager. * diff --git a/src/script/scriptvalue.h b/src/script/scriptvalue.h new file mode 100644 index 0000000..3431574 --- /dev/null +++ b/src/script/scriptvalue.h @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +#define SCRIPT_VALUE_TYPE_NIL 0 +#define SCRIPT_VALUE_TYPE_INT 1 +#define SCRIPT_VALUE_TYPE_FLOAT 2 +#define SCRIPT_VALUE_TYPE_STRING 3 +#define SCRIPT_VALUE_TYPE_BOOL 4 + +typedef struct scriptvalue_s { + uint8_t type; + + union { + int32_t intValue; + float floatValue; + const char_t *strValue; + bool boolValue; + } value; +} scriptvalue_t; \ No newline at end of file diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index c46cc1d..eacb921 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -11,4 +11,7 @@ target_sources(${DUSK_TARGET_NAME} uidebug.c uiframe.c uitextbox.c -) \ No newline at end of file +) + +# Subdirs +add_subdirectory(element) \ No newline at end of file diff --git a/src/ui/element/CMakeLists.txt b/src/ui/element/CMakeLists.txt new file mode 100644 index 0000000..15c3bec --- /dev/null +++ b/src/ui/element/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_TARGET_NAME} + PRIVATE +) \ No newline at end of file diff --git a/src/ui/element/uielementtype.h b/src/ui/element/uielementtype.h new file mode 100644 index 0000000..32c2367 --- /dev/null +++ b/src/ui/element/uielementtype.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once + +typedef enum { + UI_ELEMENT_TYPE_NULL, + + UI_ELEMENT_TYPE_TEXT, + + UI_ELEMENT_TYPE_COUNT, +} uielementtype_t; \ No newline at end of file