Add ModuleLoader for conditional module loading with graceful fallbacks
- Create ModuleLoader.lua with safeRequire() for optional module loading - Implement null-object pattern for missing optional modules - Update FlexLove.lua to use ModuleLoader for Performance, Animation, Blur, Theme, ImageRenderer, ImageScaler, ImageCache, NinePatch, and GestureRecognizer - Add comprehensive test suite for ModuleLoader (18 tests) - Validate FlexLove works correctly when optional modules are missing - All tests pass (1253/1254 successes)
This commit is contained in:
190
modules/ModuleLoader.lua
Normal file
190
modules/ModuleLoader.lua
Normal file
@@ -0,0 +1,190 @@
|
||||
---@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
|
||||
|
||||
-- Return nil for unknown properties (allows safe property access)
|
||||
return nil
|
||||
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
|
||||
Reference in New Issue
Block a user