diff --git a/data-canary/lib/core/load.lua b/data-canary/lib/core/load.lua index f097faa836b..33acae25735 100644 --- a/data-canary/lib/core/load.lua +++ b/data-canary/lib/core/load.lua @@ -1,2 +1,2 @@ dofile(DATA_DIRECTORY .. "/lib/core/storages.lua") -require("data-canary.lib.core.quests") +dofile(DATA_DIRECTORY .. "/lib/core/quests.lua") diff --git a/data-canary/lib/core/quests.lua b/data-canary/lib/core/quests.lua index 611fa26a0ae..c00d869a35a 100644 --- a/data-canary/lib/core/quests.lua +++ b/data-canary/lib/core/quests.lua @@ -1 +1 @@ -return require("data.lib.core.quests.loader").load(DATA_DIRECTORY) +return dofile(CORE_DIRECTORY .. "/lib/core/quests/loader.lua").load(DATA_DIRECTORY) diff --git a/data-canary/lib/core/quests/catalog/init.lua b/data-canary/lib/core/quests/catalog/init.lua index ca0d92fab2d..ed03a78b288 100644 --- a/data-canary/lib/core/quests/catalog/init.lua +++ b/data-canary/lib/core/quests/catalog/init.lua @@ -3,5 +3,7 @@ local questModules = { } local catalog = dofile(CORE_DIRECTORY .. "/lib/core/quests/catalog.lua") +local dirSep = package.config:sub(1, 1) +local catalogDirectory = table.concat({ DATA_DIRECTORY, "lib", "core", "quests", "catalog" }, dirSep) -return catalog.build(DATA_DIRECTORY .. ".lib.core.quests.catalog", questModules) +return catalog.build(DATA_DIRECTORY .. ".lib.core.quests.catalog", questModules, catalogDirectory) diff --git a/data-otservbr-global/lib/core/load.lua b/data-otservbr-global/lib/core/load.lua index 3a346828c6d..bf9b37a52f6 100644 --- a/data-otservbr-global/lib/core/load.lua +++ b/data-otservbr-global/lib/core/load.lua @@ -1,3 +1,3 @@ dofile(DATA_DIRECTORY .. "/lib/core/storages.lua") dofile(DATA_DIRECTORY .. "/lib/core/constants.lua") -require("data-otservbr-global.lib.core.quests") +dofile(DATA_DIRECTORY .. "/lib/core/quests.lua") diff --git a/data-otservbr-global/lib/core/quests.lua b/data-otservbr-global/lib/core/quests.lua index 611fa26a0ae..c00d869a35a 100644 --- a/data-otservbr-global/lib/core/quests.lua +++ b/data-otservbr-global/lib/core/quests.lua @@ -1 +1 @@ -return require("data.lib.core.quests.loader").load(DATA_DIRECTORY) +return dofile(CORE_DIRECTORY .. "/lib/core/quests/loader.lua").load(DATA_DIRECTORY) diff --git a/data-otservbr-global/lib/core/quests/catalog/init.lua b/data-otservbr-global/lib/core/quests/catalog/init.lua index 7c5dc5bea12..0d6b6558ac1 100644 --- a/data-otservbr-global/lib/core/quests/catalog/init.lua +++ b/data-otservbr-global/lib/core/quests/catalog/init.lua @@ -53,5 +53,7 @@ local questModules = { } local catalog = dofile(CORE_DIRECTORY .. "/lib/core/quests/catalog.lua") +local dirSep = package.config:sub(1, 1) +local catalogDirectory = table.concat({ DATA_DIRECTORY, "lib", "core", "quests", "catalog" }, dirSep) -return catalog.build(DATA_DIRECTORY .. ".lib.core.quests.catalog", questModules) +return catalog.build(DATA_DIRECTORY .. ".lib.core.quests.catalog", questModules, catalogDirectory) diff --git a/data/core.lua b/data/core.lua index 7c2ed0f5a3b..319425f5bd9 100644 --- a/data/core.lua +++ b/data/core.lua @@ -1,6 +1,14 @@ DATA_DIRECTORY = configManager.getString(configKeys.DATA_DIRECTORY) CORE_DIRECTORY = configManager.getString(configKeys.CORE_DIRECTORY) +-- Extend package.path so that require() can find modules under the libs +-- directory (e.g. gamestore.*). The base path is kept minimal (set in C++) +-- to avoid the heavy string processing that LuaJIT's package.searchpath +-- performs on every require() call when running in interpreter-only mode +-- (macOS ARM64). +local sep = package.config:sub(1, 1) +package.path = package.path .. ";" .. CORE_DIRECTORY .. sep .. "libs" .. sep .. "?.lua" .. ";" .. CORE_DIRECTORY .. sep .. "libs" .. sep .. "?" .. sep .. "init.lua" + dofile(CORE_DIRECTORY .. "/global.lua") dofile(CORE_DIRECTORY .. "/libs/libs.lua") dofile(CORE_DIRECTORY .. "/stages.lua") diff --git a/data/lib/core/quests/catalog.lua b/data/lib/core/quests/catalog.lua index 4c27447e606..51f73a0e703 100644 --- a/data/lib/core/quests/catalog.lua +++ b/data/lib/core/quests/catalog.lua @@ -25,13 +25,24 @@ local function validateStartStorage(quest, questName, owners) owners[storage] = questName end -local function buildCatalog(namespace, questModules) +local function buildCatalog(namespace, questModules, catalogDirectory) local quests = {} local missionOwners = {} local storageOwners = {} + local dirSep = package.config:sub(1, 1) for index, moduleName in ipairs(questModules) do - local quest = require(namespace .. "." .. moduleName) + local quest + if catalogDirectory then + local filePath = catalogDirectory .. dirSep .. moduleName .. ".lua" + local loader, errMsg = loadfile(filePath) + if not loader then + error(string.format("Quest module %s failed to load: %s", moduleName, errMsg)) + end + quest = loader() + else + quest = require(namespace .. "." .. moduleName) + end if type(quest) ~= "table" then error(string.format("Quest module %s did not return a table", moduleName)) end diff --git a/data/lib/core/quests/loader.lua b/data/lib/core/quests/loader.lua index 268cde3b21c..2d2c75fa246 100644 --- a/data/lib/core/quests/loader.lua +++ b/data/lib/core/quests/loader.lua @@ -1,49 +1,21 @@ -local currentNamespace - -local function ensureQuestCatalogLoader(namespace, catalogDirectory) - local searchers = package.searchers or package.loaders - local loaderRegistryName = namespace .. ".loader" - if package.loaded[loaderRegistryName] then - return - end - - local dirSeparator = package.config:sub(1, 1) - local prefix = namespace .. "." - - local function questCatalogLoader(moduleName) - if moduleName ~= namespace and moduleName:sub(1, #prefix) ~= prefix then - return nil - end - local filePath - if moduleName == namespace then - filePath = catalogDirectory .. dirSeparator .. "init.lua" - else - local relative = moduleName:sub(#prefix + 1):gsub("%.", dirSeparator) - filePath = catalogDirectory .. dirSeparator .. relative .. ".lua" - end - local loader, errorMessage = loadfile(filePath) - if not loader then - return "\n\t" .. errorMessage - end - return loader, filePath - end - - table.insert(searchers, 1, questCatalogLoader) - package.loaded[loaderRegistryName] = true -end - local function loadQuestCatalog(dataDirectory) local dirSeparator = package.config:sub(1, 1) local namespace = dataDirectory .. ".lib.core.quests.catalog" - if Quests and currentNamespace == namespace then + -- Guard against redundant reloads. Since this file is loaded via dofile + -- (no require caching), use a global to track whether quests are already + -- loaded for the given namespace. + if Quests and _G._questCatalogNamespace == namespace then return Quests end local catalogDirectory = table.concat({ dataDirectory, "lib", "core", "quests", "catalog" }, dirSeparator) - ensureQuestCatalogLoader(namespace, catalogDirectory) - - Quests = require(namespace) - currentNamespace = namespace + -- Load init.lua directly via dofile instead of going through require's + -- package.searchpath machinery. On macOS ARM64 where LuaJIT runs in + -- interpreter-only mode, the string operations in searchpath across many + -- nested require calls cause severe startup delays. + local initPath = catalogDirectory .. dirSeparator .. "init.lua" + Quests = dofile(initPath) + _G._questCatalogNamespace = namespace return Quests end diff --git a/data/libs/functions/quests.lua b/data/libs/functions/quests.lua index 71f4c594a49..6f4e8bcc4ed 100644 --- a/data/libs/functions/quests.lua +++ b/data/libs/functions/quests.lua @@ -1,4 +1,6 @@ -require("data-otservbr-global.lib.core.quests") +-- Load quest catalog via dofile instead of require to avoid package.searchpath +-- overhead in LuaJIT interpreter mode (macOS ARM64). +dofile(DATA_DIRECTORY .. "/lib/core/quests.lua") if not LastQuestlogUpdate then LastQuestlogUpdate = {} diff --git a/src/lua/functions/lua_functions_loader.cpp b/src/lua/functions/lua_functions_loader.cpp index f086a48ca69..fd1313f3142 100644 --- a/src/lua/functions/lua_functions_loader.cpp +++ b/src/lua/functions/lua_functions_loader.cpp @@ -37,6 +37,22 @@ void Lua::load(lua_State* L) { luaL_openlibs(L); + // Set a minimal package.path so that require() only searches the working + // directory. The default path compiled into LuaJIT (plus whatever LUA_PATH + // adds) can contain many entries, and package.searchpath iterates through + // every one of them doing string operations for each require() call. + // On macOS ARM64 where LuaJIT runs in interpreter-only mode (JIT disabled), + // the cumulative cost of those string operations across 50+ nested require + // calls during startup causes the server to hang for minutes. + lua_getglobal(L, "package"); + if (lua_istable(L, -1)) { + lua_pushliteral(L, "./?.lua;./?/init.lua"); + lua_setfield(L, -2, "path"); + lua_pushliteral(L, ""); + lua_setfield(L, -2, "cpath"); + } + lua_pop(L, 1); + CoreFunctions::init(L); CreatureFunctions::init(L); EventFunctions::init(L); diff --git a/vcpkg.json b/vcpkg.json index f92a5f8b902..b2e5dc6ae86 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -39,5 +39,5 @@ ] } }, - "builtin-baseline": "66c0373dc7fca549e5803087b9487edfe3aca0a1" + "builtin-baseline": "c3867e714dd3a51c272826eea77267876517ed99" }