bayuMIR/Mirserver/Mir200/Envir/QuestDiary/util/ModuleLoader.lua

256 lines
8 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

ModuleLoader = ModuleLoader or {}
ModuleLoader.loaded = ModuleLoader.loaded or {}
ModuleLoader.failed = ModuleLoader.failed or {}
ModuleLoader.skipped = ModuleLoader.skipped or {}
ModuleLoader.pending = ModuleLoader.pending or {}
ModuleLoader.modules = ModuleLoader.modules or {}
ModuleLoader.modulePaths = ModuleLoader.modulePaths or {}
ModuleLoader.phaseStats = ModuleLoader.phaseStats or {}
local function _log(...)
if release_print then
release_print(...)
elseif LOGPrint then
LOGPrint(...)
else
print(...)
end
end
local function _traceback(err)
return tostring(err) .. "\n" .. debug.traceback("", 2)
end
local function _shortError(err)
local msg = tostring(err or "")
local firstLine = msg:match("([^\n\r]+)") or msg
local lineInfo = msg:match("([^\n\r]-%.lua:%d+:%s*[^\n\r]+)")
return lineInfo or firstLine
end
local function _push(list, item)
list[#list + 1] = item
end
local function _recordPhase(phase, key)
local stat = ModuleLoader.phaseStats[phase]
if not stat then
stat = { success = 0, failed = 0, skipped = 0 }
ModuleLoader.phaseStats[phase] = stat
end
stat[key] = (stat[key] or 0) + 1
end
local function _basename(path)
local name = tostring(path or "")
name = name:gsub("\\", "/")
name = name:match("([^/]+)$") or name
name = name:gsub("%.lua$", "")
return name
end
local function _safeName(module, path)
if type(module) == "table" and module._name and module._name ~= "" then
return tostring(module._name)
end
return _basename(path)
end
local function _dependsReady(depends)
if not depends then
return true
end
if type(depends) ~= "table" then
return false, "_depends must be table"
end
for _, name in ipairs(depends) do
if not ModuleLoader.modules[name] then
return false, "missing dependency: " .. tostring(name)
end
end
return true
end
function ModuleLoader.reset()
ModuleLoader.loaded = {}
ModuleLoader.failed = {}
ModuleLoader.skipped = {}
ModuleLoader.pending = {}
ModuleLoader.modules = {}
ModuleLoader.modulePaths = {}
ModuleLoader.phaseStats = {}
end
function ModuleLoader.getFiles(dir)
local ok, files = xpcall(function()
return GetFileList(dir) or {}
end, _traceback)
if not ok then
_push(ModuleLoader.failed, { phase = "scan", path = dir, error = files })
_log("[ModuleLoader] ɨ<><C9A8>ʧ<EFBFBD><CAA7>:", dir, files)
return {}
end
table.sort(files, function(a, b)
return tostring(a) < tostring(b)
end)
return files
end
function ModuleLoader.safeRequire(path, phase)
phase = phase or "default"
local ok, module = xpcall(function()
return require(path)
end, _traceback)
if not ok then
_push(ModuleLoader.failed, { phase = phase, path = path, error = module, short = _shortError(module) })
_recordPhase(phase, "failed")
_log("[ModuleLoader] <20><><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>:", path, _shortError(module))
return nil, module
end
_push(ModuleLoader.loaded, { phase = phase, path = path, module = module })
_recordPhase(phase, "success")
return module
end
function ModuleLoader.registerModule(module, path, phase, opts)
opts = opts or {}
phase = phase or "default"
if type(module) ~= "table" then
return true
end
if module._enabled == false then
_push(ModuleLoader.skipped, { phase = phase, path = path, reason = "disabled" })
_recordPhase(phase, "skipped")
return false, "disabled"
end
local name = _safeName(module, path)
local ready, reason = _dependsReady(module._depends)
if not ready then
if opts.deferMissing then
return false, reason, "defer"
end
_push(ModuleLoader.skipped, { phase = phase, path = path, name = name, reason = reason })
_recordPhase(phase, "skipped")
_log("[ModuleLoader] <20><><EFBFBD><EFBFBD>ģ<EFBFBD><C4A3>:", name, reason)
return false, reason
end
if ModuleLoader.modules[name] then
local err = "duplicate module name: " .. name .. ", old=" .. tostring(ModuleLoader.modulePaths[name]) .. ", new=" .. tostring(path)
_push(ModuleLoader.failed, { phase = phase, path = path, name = name, error = err })
_recordPhase(phase, "failed")
_log("[ModuleLoader] ģ<><C4A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:", err)
return false, err
end
if type(module.init) == "function" then
local ok, err = xpcall(function()
module:init()
end, _traceback)
if not ok then
_push(ModuleLoader.failed, { phase = phase, path = path, name = name, error = err, short = _shortError(err) })
_recordPhase(phase, "failed")
_log("[ModuleLoader] <20><>ʼ<EFBFBD><CABC>ʧ<EFBFBD><CAA7>:", name, _shortError(err))
return false, err
end
end
ModuleLoader.modules[name] = module
ModuleLoader.modulePaths[name] = path
if Message and Message.dispatch_handler and module._name and module._name ~= "" then
Message.dispatch_handler[module._name] = module
end
return true
end
function ModuleLoader.loadModule(path, phase, opts)
opts = opts or {}
local module, err = ModuleLoader.safeRequire(path, phase)
if not module then
return nil, err
end
if opts.register ~= false then
local ok, reason, code = ModuleLoader.registerModule(module, path, phase, { deferMissing = true })
if not ok and code == "defer" then
_push(ModuleLoader.pending, { phase = phase, path = path, module = module, reason = reason })
end
end
return module
end
function ModuleLoader.resolvePending()
local changed = true
while changed do
changed = false
for i = #ModuleLoader.pending, 1, -1 do
local item = ModuleLoader.pending[i]
local ok = ModuleLoader.registerModule(item.module, item.path, item.phase, { deferMissing = true })
if ok then
table.remove(ModuleLoader.pending, i)
changed = true
end
end
end
for i = #ModuleLoader.pending, 1, -1 do
local item = ModuleLoader.pending[i]
ModuleLoader.registerModule(item.module, item.path, item.phase, { deferMissing = false })
table.remove(ModuleLoader.pending, i)
end
end
function ModuleLoader.loadSystemDir(dir, requirePrefix, phase)
local files = ModuleLoader.getFiles(dir)
for _, file in ipairs(files) do
if tostring(file):match("%.lua$") then
local name = _basename(file)
ModuleLoader.loadModule(requirePrefix .. "/" .. name, phase, { register = true })
end
end
end
function ModuleLoader.loadGameDirs(rootDir, requirePrefix, phase)
local dirs = ModuleLoader.getFiles(rootDir)
for _, dir in ipairs(dirs) do
local subDir = rootDir .. "/" .. dir
local files = ModuleLoader.getFiles(subDir)
for _, file in ipairs(files) do
if tostring(file):match("%.lua$") then
local name = _basename(file)
ModuleLoader.loadModule(requirePrefix .. "/" .. dir .. "/" .. name, phase, { register = true })
end
end
end
end
function ModuleLoader.report()
local success = #ModuleLoader.loaded
local failed = #ModuleLoader.failed
local skipped = #ModuleLoader.skipped
_log("[ModuleLoader] <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: success=", success, " failed=", failed, " skipped=", skipped)
for phase, stat in pairs(ModuleLoader.phaseStats) do
_log("[ModuleLoader] <20>׶<EFBFBD>:", phase, " success=", stat.success or 0, " failed=", stat.failed or 0, " skipped=", stat.skipped or 0)
end
if failed > 0 then
for _, item in ipairs(ModuleLoader.failed) do
_log("[ModuleLoader] ʧ<><CAA7>ģ<EFBFBD><C4A3>:", item.phase or "", item.path or "", item.name or "", item.short or _shortError(item.error))
end
end
if skipped > 0 then
for _, item in ipairs(ModuleLoader.skipped) do
_log("[ModuleLoader] <20><><EFBFBD><EFBFBD>ģ<EFBFBD><C4A3>:", item.phase or "", item.path or "", item.name or "", item.reason or "")
end
end
end
return ModuleLoader