From 25838f13bc8f74dfd03c78cfabfcfb02346caba5 Mon Sep 17 00:00:00 2001 From: Beckowl <68874587+Beckowl@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:04:48 -0300 Subject: [PATCH] fix require (#908) * fix require * Refactor smlua_find_mod_file * formatting * initialize arrays for consistency * normalize relative path in mod_allocate_file instead (fix mod dev mode) * do cleanup * Remove useless branch + add comments * fix mod_file_exists * WIP: cache mod files at root (LIVE RELOAD IS BROKEN) * Fix live reload * remove useless declaration (??) * fix incorrect top handling * cleanup x2 * stupid * loading sentinel + mark top level files as loading * address dj's comment --- src/pc/lua/smlua.c | 14 ++- src/pc/lua/smlua_hooks.c | 1 + src/pc/lua/smlua_live_reload.c | 11 +- src/pc/lua/smlua_require.c | 183 ++++++++++++++-------------- src/pc/lua/smlua_require.h | 6 +- src/pc/lua/utils/smlua_misc_utils.c | 10 +- src/pc/mods/mod.c | 13 +- src/pc/mods/mods_utils.c | 41 +++++++ src/pc/mods/mods_utils.h | 1 + 9 files changed, 171 insertions(+), 109 deletions(-) diff --git a/src/pc/lua/smlua.c b/src/pc/lua/smlua.c index b93b5679a..762e502da 100644 --- a/src/pc/lua/smlua.c +++ b/src/pc/lua/smlua.c @@ -362,7 +362,19 @@ void smlua_init(void) { } gLuaActiveModFile = file; - smlua_load_script(mod, file, i, true); + + // file has been required by some module before this + if (!smlua_get_cached_module_result(L, mod, file)) { + smlua_mark_module_as_loading(L, mod, file); + + s32 prevTop = lua_gettop(L); + int rc = smlua_load_script(mod, file, i, true); + + if (rc == LUA_OK) { + smlua_cache_module_result(L, mod, file, prevTop); + } + } + lua_settop(L, 0); } gLuaActiveMod = NULL; diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c index 0149e1711..6ec341c05 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -100,6 +100,7 @@ int smlua_hook_event(lua_State* L) { hook->reference[hook->count] = ref; hook->mod[hook->count] = gLuaActiveMod; + hook->modFile[hook->count] = gLuaActiveModFile; hook->count++; return 1; diff --git a/src/pc/lua/smlua_live_reload.c b/src/pc/lua/smlua_live_reload.c index 809572f4d..2b967bfdd 100644 --- a/src/pc/lua/smlua_live_reload.c +++ b/src/pc/lua/smlua_live_reload.c @@ -1,5 +1,6 @@ #include #include "smlua.h" +#include "smlua_require.h" #include "pc/mods/mods.h" #include "pc/mods/mods_utils.h" @@ -343,16 +344,8 @@ static void smlua_reload_module(lua_State *L, struct Mod* mod, struct ModFile *f // only handle loaded Lua modules if (!file->isLoadedLuaModule) { return; } - // build registry key for this mod's loaded table - char registryKey[SYS_MAX_PATH + 16]; - snprintf(registryKey, sizeof(registryKey), "mod_loaded_%s", mod->relativePath); - // get per-mod "loaded" table - lua_getfield(L, LUA_REGISTRYINDEX, registryKey); // ..., loadedTable - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - return; - } + smlua_get_or_create_mod_loaded_table(L, mod); // get the old module table: loadedTable[file->relativePath] lua_getfield(L, -1, file->relativePath); // ..., loadedTable, oldMod diff --git a/src/pc/lua/smlua_require.c b/src/pc/lua/smlua_require.c index 196f8cb76..b1f1f636a 100644 --- a/src/pc/lua/smlua_require.c +++ b/src/pc/lua/smlua_require.c @@ -4,47 +4,89 @@ #include "pc/mods/mods_utils.h" #include "pc/fs/fmem.h" +#define LOADING_SENTINEL ((void*)-1) + // table to track loaded modules per mod -static void smlua_init_mod_loaded_table(lua_State* L, const char* modPath) { - // Create a unique registry key for this mod's loaded table - char registryKey[SYS_MAX_PATH + 16]; - snprintf(registryKey, sizeof(registryKey), "mod_loaded_%s", modPath); +void smlua_get_or_create_mod_loaded_table(lua_State* L, struct Mod* mod) { + char registryKey[SYS_MAX_PATH + 16] = ""; + snprintf(registryKey, sizeof(registryKey), "mod_loaded_%s", mod->relativePath); lua_getfield(L, LUA_REGISTRYINDEX, registryKey); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); + lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, registryKey); - } else { - lua_pop(L, 1); } } +bool smlua_get_cached_module_result(lua_State* L, struct Mod* mod, struct ModFile* file) { + smlua_get_or_create_mod_loaded_table(L, mod); + lua_getfield(L, -1, file->relativePath); + + if (lua_touserdata(L, -1) == LOADING_SENTINEL) { + LOG_LUA_LINE("loop or previous error loading module '%s'", file->relativePath); + lua_pop(L, 1); // pop sentinel + lua_pushnil(L); + + return true; + } + + if (lua_isnil(L, -1)) { + // not cached + lua_pop(L, 2); // pop nil and loaded table + return false; + } + + // cached, remove loaded table and leave value on top + lua_remove(L, -2); + return true; +} + +void smlua_mark_module_as_loading(lua_State* L, struct Mod* mod, struct ModFile* file) { + smlua_get_or_create_mod_loaded_table(L, mod); + lua_pushlightuserdata(L, LOADING_SENTINEL); + lua_setfield(L, -2, file->relativePath); + lua_pop(L, 1); // pop loaded table +} + +void smlua_cache_module_result(lua_State* L, struct Mod* mod, struct ModFile* file, s32 prevTop) { + if (lua_gettop(L) == prevTop) { + lua_pushboolean(L, 1); + } else if (lua_isnil(L, -1)) { + lua_pop(L, 1); + lua_pushboolean(L, 1); + } + + smlua_get_or_create_mod_loaded_table(L, mod); + + lua_pushvalue(L, -2); // duplicate result + lua_setfield(L, -2, file->relativePath); // loaded[file->relativePath] = result + lua_pop(L, 1); // pop loaded table +} + static struct ModFile* smlua_find_mod_file(const char* moduleName) { - char relativeDir[SYS_MAX_PATH] = ""; + char basePath[SYS_MAX_PATH] = ""; + char absolutePath[SYS_MAX_PATH] = ""; if (!gLuaActiveMod) { return NULL; } + // get the directory of the current file if (gLuaActiveModFile) { - path_get_folder(gLuaActiveModFile->relativePath, relativeDir); + path_get_folder(gLuaActiveModFile->relativePath, basePath); } - bool hasRelativeDir = strlen(relativeDir) > 0; + // resolve moduleName to a path relative to mod root + resolve_relative_path(basePath, moduleName, absolutePath); - struct ModFile* bestPick = NULL; - int bestRelativeDepth = INT_MAX; - int bestTotalDepth = INT_MAX; - bool foundRelativeFile = false; - - char rawName[SYS_MAX_PATH] = ""; char luaName[SYS_MAX_PATH] = ""; char luacName[SYS_MAX_PATH] = ""; - snprintf(rawName, SYS_MAX_PATH, "/%s", moduleName); - snprintf(luaName, SYS_MAX_PATH, "/%s.lua", moduleName); - snprintf(luacName, SYS_MAX_PATH, "/%s.luac", moduleName); + snprintf(luaName, SYS_MAX_PATH, "%s.lua", absolutePath); + snprintf(luacName, SYS_MAX_PATH, "%s.luac", absolutePath); + // since mods' relativePaths are relative to the mod's root, we can do a direct comparison for (int i = 0; i < gLuaActiveMod->fileCount; i++) { struct ModFile* file = &gLuaActiveMod->files[i]; @@ -59,36 +101,12 @@ static struct ModFile* smlua_find_mod_file(const char* moduleName) { } // check for match - if (!str_ends_with(file->relativePath, rawName) && !str_ends_with(file->relativePath, luaName) && !str_ends_with(file->relativePath, luacName)) { - continue; - } - - // get total path depth - int totalDepth = path_depth(file->relativePath); - - // make sure we never load the old-style lua files with require() - if (totalDepth < 1) { - continue; - } - - // get relative path depth - int relativeDepth = INT_MAX; - if (hasRelativeDir && path_is_relative_to(file->relativePath, relativeDir)) { - relativeDepth = path_depth(file->relativePath + strlen(relativeDir)); - foundRelativeFile = true; - } - - // pick new best - // relative files will always win against absolute files - // other than that, shallower files will win - if (relativeDepth < bestRelativeDepth || (!foundRelativeFile && totalDepth < bestTotalDepth)) { - bestPick = file; - bestRelativeDepth = relativeDepth; - bestTotalDepth = totalDepth; + if (!strcmp(file->relativePath, luaName) || !strcmp(file->relativePath, luacName)) { + return file; } } - return bestPick; + return NULL; } static int smlua_custom_require(lua_State* L) { @@ -96,69 +114,47 @@ static int smlua_custom_require(lua_State* L) { struct Mod* activeMod = gLuaActiveMod; if (!activeMod) { - LOG_LUA("require() called outside of mod context"); + LOG_LUA_LINE("require() called outside of mod context"); return 0; } - // create registry key for this mod's loaded table - char registryKey[SYS_MAX_PATH + 16] = ""; - snprintf(registryKey, sizeof(registryKey), "mod_loaded_%s", activeMod->relativePath); - - // get or create the mod's loaded table - lua_getfield(L, LUA_REGISTRYINDEX, registryKey); - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - lua_newtable(L); - lua_pushvalue(L, -1); - lua_setfield(L, LUA_REGISTRYINDEX, registryKey); + if (str_ends_with(moduleName, "/") || str_ends_with(moduleName, "\\")) { + LOG_LUA_LINE("cannot require a directory"); + return 0; } // find the file in mod files struct ModFile* file = smlua_find_mod_file(moduleName); if (!file) { - LOG_LUA("module '%s' not found in mod files", moduleName); - lua_pop(L, 1); // pop table + LOG_LUA_LINE("module '%s' not found in mod files", moduleName); return 0; } - // check if module is already loaded - lua_getfield(L, -1, file->relativePath); - if (!lua_isnil(L, -1)) { - // module already loaded, return it - return 1; - } - lua_pop(L, 1); // pop nil value - - // mark module as "loading" to prevent recursion - lua_pushboolean(L, 1); - lua_setfield(L, -2, file->relativePath); - - // cache the previous mod file - struct ModFile* prevModFile = gLuaActiveModFile; - s32 prevTop = lua_gettop(L); - // tag it as a loaded lua module file->isLoadedLuaModule = true; - // load and execute - gLuaActiveModFile = file; - smlua_load_script(activeMod, file, activeMod->index, false); - gLuaActiveModFile = prevModFile; - - // if the module didn't return anything, use true - if (prevTop == lua_gettop(L)) { - lua_pushboolean(L, 1); - } else if (lua_isnil(L, -1)) { - lua_pop(L, 1); - lua_pushboolean(L, 1); + // check cache first + if (smlua_get_cached_module_result(L, activeMod, file)) { + return 1; } - // store in loaded table - lua_pushvalue(L, -1); // duplicate return value - lua_setfield(L, -3, file->relativePath); // loaded[file->relativePath] = return_value + // mark module as "loading" to prevent recursion + smlua_mark_module_as_loading(L, activeMod, file); - // clean up stack - lua_remove(L, -2); + // cache the previous mod file + struct ModFile* prevModFile = gLuaActiveModFile; + + // load and execute + gLuaActiveModFile = file; + s32 prevTop = lua_gettop(L); + + int rc = smlua_load_script(activeMod, file, activeMod->index, false); + + if (rc == LUA_OK) { + smlua_cache_module_result(L, activeMod, file, prevTop); + } + + gLuaActiveModFile = prevModFile; return 1; // return the module value } @@ -179,6 +175,7 @@ void smlua_init_require_system(void) { // initialize loaded tables for each mod for (int i = 0; i < gActiveMods.entryCount; i++) { struct Mod* mod = gActiveMods.entries[i]; - smlua_init_mod_loaded_table(L, mod->relativePath); + smlua_get_or_create_mod_loaded_table(L, mod); + lua_pop(L, 1); // pop loaded table } -} \ No newline at end of file +} diff --git a/src/pc/lua/smlua_require.h b/src/pc/lua/smlua_require.h index dcb8891cb..bb1b38df1 100644 --- a/src/pc/lua/smlua_require.h +++ b/src/pc/lua/smlua_require.h @@ -3,9 +3,11 @@ #include "smlua.h" -void smlua_require_update(lua_State* L); void smlua_bind_custom_require(lua_State* L); -void smlua_reload_module(lua_State *L, struct Mod* mod, struct ModFile *file); +void smlua_get_or_create_mod_loaded_table(lua_State* L, struct Mod* mod); +bool smlua_get_cached_module_result(lua_State* L, struct Mod* mod, struct ModFile* file); +void smlua_mark_module_as_loading(lua_State* L, struct Mod* mod, struct ModFile* file); +void smlua_cache_module_result(lua_State* L, struct Mod* mod, struct ModFile* file, s32 prevTop); void smlua_init_require_system(void); #endif \ No newline at end of file diff --git a/src/pc/lua/utils/smlua_misc_utils.c b/src/pc/lua/utils/smlua_misc_utils.c index f953c458a..7a469db0a 100644 --- a/src/pc/lua/utils/smlua_misc_utils.c +++ b/src/pc/lua/utils/smlua_misc_utils.c @@ -539,9 +539,17 @@ void set_environment_region(u8 index, s16 value) { bool mod_file_exists(const char* filename) { if (gLuaActiveMod == NULL) { return false; } + char normPath[SYS_MAX_PATH] = { 0 }; + + if (snprintf(normPath, sizeof(normPath), "%s", filename) < 0) { + LOG_ERROR("Failed to copy filename for normalization: %s", filename); + } + + normalize_path(normPath); + for (s32 i = 0; i < gLuaActiveMod->fileCount; i++) { struct ModFile* file = &gLuaActiveMod->files[i]; - if (!strcmp(file->relativePath, filename)) { + if (!strcmp(file->relativePath, normPath)) { return true; } } diff --git a/src/pc/mods/mod.c b/src/pc/mods/mod.c index a0e5c817b..3b163cae6 100644 --- a/src/pc/mods/mod.c +++ b/src/pc/mods/mod.c @@ -245,15 +245,22 @@ static struct ModFile* mod_allocate_file(struct Mod* mod, char* relativePath) { memset(file, 0, sizeof(struct ModFile)); // set relative path - if (snprintf(file->relativePath, SYS_MAX_PATH - 1, "%s", relativePath) < 0) { - LOG_ERROR("Failed to remember relative path '%s'", relativePath); + char normPath[SYS_MAX_PATH] = { 0 }; + if (snprintf(normPath, sizeof(normPath), "%s", relativePath) < 0) { + LOG_ERROR("Failed to copy relative path for normalization: %s", relativePath); + } + + normalize_path(normPath); + + if (snprintf(file->relativePath, SYS_MAX_PATH - 1, "%s", normPath) < 0) { + LOG_ERROR("Failed to remember relative path '%s'", normPath); return NULL; } // figure out full path char fullPath[SYS_MAX_PATH] = { 0 }; if (!mod_file_full_path(fullPath, mod, file)) { - LOG_ERROR("Failed to concat path: '%s' + '%s'", mod->basePath, relativePath); + LOG_ERROR("Failed to concat path: '%s' + '%s'", mod->basePath, normPath); return NULL; } diff --git a/src/pc/mods/mods_utils.c b/src/pc/mods/mods_utils.c index 429a22628..df9987704 100644 --- a/src/pc/mods/mods_utils.c +++ b/src/pc/mods/mods_utils.c @@ -244,6 +244,47 @@ int path_depth(const char* path) { return depth; } +void resolve_relative_path(const char* base, const char* path, char* output) { + char combined[SYS_MAX_PATH] = ""; + + // If path is absolute, copy as is. Otherwise, combine base and relative path + if (path[0] == '/' || path[0] == '\\') { + snprintf(combined, sizeof(combined), "%s", path); + } else { + snprintf(combined, sizeof(combined), "%s/%s", base, path); + } + + char* tokens[64]; + int tokenCount = 0; + + // Tokenize path by separators + char* token = strtok(combined, "/\\"); + while (token && tokenCount < 64) { + if (strcmp(token, "..") == 0) { + // Pop last token to go up a directory + if (tokenCount > 0) { tokenCount--; } + + // Ignore "." (current directory) or empty tokens + } else if (strcmp(token, ".") != 0 && token[0] != '\0') { + tokens[tokenCount++] = token; + } + + token = strtok(NULL, "/\\"); + } + + output[0] = '\0'; + + // Build output path from tokens + for (int i = 0; i < tokenCount; i++) { + if (i > 0) { + strncat(output, "/", SYS_MAX_PATH - strlen(output) - 1); + } + strncat(output, tokens[i], SYS_MAX_PATH - strlen(output) - 1); + } + + normalize_path(output); +} + bool path_is_relative_to(const char* fullPath, const char* baseDir) { return strncmp(fullPath, baseDir, strlen(baseDir)) == 0; } diff --git a/src/pc/mods/mods_utils.h b/src/pc/mods/mods_utils.h index ad07e8216..c375a7efa 100644 --- a/src/pc/mods/mods_utils.h +++ b/src/pc/mods/mods_utils.h @@ -21,6 +21,7 @@ bool concat_path(char* destination, char* path, char* fname); char* path_basename(char* path); void path_get_folder(char* path, char* outpath); int path_depth(const char* path); +void resolve_relative_path(const char* base, const char* path, char* output); bool path_is_relative_to(const char* fullPath, const char* baseDir); bool directory_sanity_check(struct dirent* dir, char* dirPath, char* outPath);