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] 扫描失败:", 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] 加载失败:", 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] 跳过模块:", 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] 模块重名:", 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] 初始化失败:", 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] 启动报告: success=", success, " failed=", failed, " skipped=", skipped) for phase, stat in pairs(ModuleLoader.phaseStats) do _log("[ModuleLoader] 阶段:", 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] 失败模块:", 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] 跳过模块:", item.phase or "", item.path or "", item.name or "", item.reason or "") end end end return ModuleLoader