256 lines
8 KiB
Lua
256 lines
8 KiB
Lua
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
|