---@class ModuleLoader local ModuleLoader = {} -- Module registry to track loaded vs. stub modules ModuleLoader._registry = {} ModuleLoader._ErrorHandler = nil --- Initialize ModuleLoader with dependencies ---@param deps table function ModuleLoader.init(deps) ModuleLoader._ErrorHandler = deps.ErrorHandler end --- Create a null-object stub for a missing optional module --- Provides safe defaults that won't cause runtime errors ---@param moduleName string ---@return table local function createNullObject(moduleName) local stub = { _isStub = true, _moduleName = moduleName, } -- Common method stubs that return safe defaults local metatable = { __index = function(_, key) -- Common initialization method if key == "init" then return function() return stub end end -- Common constructor method if key == "new" then return function() return stub end end -- Common draw method if key == "draw" then return function() end end -- Common update method if key == "update" then return function() end end -- Common render method if key == "render" then return function() end end -- Common cleanup method if key == "destroy" then return function() end end -- Common cleanup method if key == "cleanup" then return function() end end -- Common clear method if key == "clear" then return function() end end -- Common reset method if key == "reset" then return function() end end -- Common get method if key == "get" then return function() return nil end end -- Common set method if key == "set" then return function() end end -- Common load method if key == "load" then return function() return stub end end -- Common cache-related methods if key == "cache" or key == "getCache" or key == "clearCache" then return function() return {} end end -- For any unknown method, return a no-op function that accepts any arguments -- This allows safe method calls on stub objects (e.g., Performance:startFrame()) return function() return stub end end, -- Make function calls safe (in case the stub itself is called) __call = function() return stub end, } setmetatable(stub, metatable) return stub end --- Safely require a module with graceful fallback for optional modules --- Returns the module if it exists, or a null-object stub if it's optional and missing --- Throws an error if a required module is missing ---@param modulePath string Full path to the module (e.g., "modules.Performance") ---@param isOptional boolean If true, returns null-object on failure; if false, throws error ---@return table module The loaded module or a null-object stub function ModuleLoader.safeRequire(modulePath, isOptional) -- Check if already loaded if ModuleLoader._registry[modulePath] then return ModuleLoader._registry[modulePath] end -- Attempt to load the module local success, result = pcall(require, modulePath) if success then -- Module loaded successfully ModuleLoader._registry[modulePath] = result return result else -- Module failed to load if isOptional then -- Create null-object stub for optional module local stub = createNullObject(modulePath) ModuleLoader._registry[modulePath] = stub -- Log warning about missing optional module if ModuleLoader._ErrorHandler then ModuleLoader._ErrorHandler:warn( "ModuleLoader", string.format("Optional module '%s' not found, using stub implementation", modulePath) ) end return stub else -- Required module is missing - throw error error(string.format("Required module '%s' not found: %s", modulePath, tostring(result))) end end end --- Check if a module is actually loaded (not a stub) ---@param modulePath string Full path to the module ---@return boolean isLoaded True if module is loaded, false if it's a stub or not loaded function ModuleLoader.isModuleLoaded(modulePath) local module = ModuleLoader._registry[modulePath] if not module then return false end -- Check if it's a stub return not module._isStub end --- Get list of all loaded modules ---@return table modules List of module paths that are actually loaded (not stubs) function ModuleLoader.getLoadedModules() local loaded = {} for path, module in pairs(ModuleLoader._registry) do if not module._isStub then table.insert(loaded, path) end end return loaded end --- Get list of all stub modules ---@return table stubs List of module paths that are stubs function ModuleLoader.getStubModules() local stubs = {} for path, module in pairs(ModuleLoader._registry) do if module._isStub then table.insert(stubs, path) end end return stubs end --- Clear the module registry (useful for testing) function ModuleLoader._clearRegistry() ModuleLoader._registry = {} end return ModuleLoader