192 lines
5.1 KiB
Lua
192 lines
5.1 KiB
Lua
---@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
|