Created
July 6, 2021 12:19
-
-
Save SamMousa/ece11e30aaaf9088d65aa5ceb9a16d01 to your computer and use it in GitHub Desktop.
AMD Style module loader for LUA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ BEGIN LIBRARY CODE ]]-- | |
local libraries = {} | |
local queue = {} | |
local function assertString(param) | |
if type(param) ~= 'string' then | |
error(string.format("Expected string got %s", type(param))) | |
end | |
end | |
local function coalesce(...) | |
for i = 1, select('#', ...) do | |
local item = select(i, ...) | |
if (item ~= nil) then | |
return item | |
end | |
end | |
end | |
local function assertBoolean(param) | |
if type(param) ~= 'boolean' then | |
error(string.format("Expected boolean got %s", type(param))) | |
end | |
end | |
-- @return bool | |
local function allLoaded(dependencies) | |
for _, dep in ipairs(dependencies) do | |
if not libraries[dep] then | |
return false | |
end | |
end | |
return true | |
end | |
-- @return list | |
local function retrieve(dependencies, requester) | |
local result = {} | |
for _, dep in ipairs(dependencies) do | |
if not libraries[dep] then | |
error(string.format("Library %s not loaded", dep)); | |
end | |
local d = libraries[dep] | |
if type(d) == 'function' then | |
print("calling constructor") | |
table.insert(result, libraries[dep](requester)) | |
else | |
table.insert(result, libraries[dep]) | |
end | |
end | |
return result | |
end | |
local function loadLibrary(name, dependencies, callback) | |
print(string.format("Loading %s", name)) | |
libraries[name] = callback(unpack(retrieve(dependencies, name))) | |
end | |
local function checkQueue() | |
local loadedAny = false | |
local newQueue = {} | |
local unloaded = 1 | |
for i, definition in ipairs(queue) do | |
if allLoaded(definition.dependencies) then | |
loadLibrary(definition.name, definition.dependencies, definition.callback) | |
loadedAny = true | |
else | |
newQueue[unloaded] = definition | |
unloaded = unloaded + 1 | |
end | |
end | |
if loadedAny then | |
queue = newQueue | |
-- use return for efficient tail recursion | |
return checkQueue() | |
end | |
end | |
function define(name, dependencies, callback) | |
print(string.format("Defining %s with %d dependencies", name, #dependencies)) | |
-- Shortcut check if loaded | |
if allLoaded(dependencies) then | |
loadLibrary(name, dependencies, callback) | |
checkQueue() | |
else | |
table.insert(queue, {name = name, dependencies = dependencies, callback = callback}) | |
end | |
end | |
--[[ END LIBRARY CODE ]]-- | |
--[[ MODULE myModule ]]-- | |
define('myModule', {'dep1', 'dep2'}, function(dep1, dep2) | |
return {} | |
end) | |
--[[ MODULE database ]]-- | |
define('database', {'storage'}, function(storage) | |
storage.initialize(); | |
-- must return a constructor function | |
return function(module) | |
assertString(module) | |
local prefix = module .. '.' | |
return { | |
setBoolean = function(name, value) | |
assertBoolean(value) | |
storage.set(prefix .. name, value) | |
end, | |
getBoolean = function(name, default) | |
local result = storage.get(prefix .. name) | |
return coalesce(result, default) | |
end | |
} | |
end | |
end) | |
--[[ MODULE storage ]]-- | |
define('storage', {}, function() | |
local data = {} | |
local api = { | |
initialize = function() print("Initializing storage") end, | |
set = function(key, value) | |
print (string.format("Storing key %s, value %s", key, tostring(value))) | |
data[key] = value | |
end, | |
get = function(key) | |
print (string.format("Retrieving key %s", key)) | |
return data[key] | |
end, | |
} | |
return api | |
end) | |
--[[ MODULE dep1 ]]-- | |
define('dep1', {'dep2'}, function(dep2) | |
return {} | |
end) | |
--[[ MODULE dep2 ]]-- | |
define('dep2', {'database'}, function(database) | |
database.setBoolean('isEnabled', false) | |
print(database.getBoolean('isEnabled', true)) | |
print(database.getBoolean('unknownSetting', true)) | |
return {} | |
end) | |
--[[ TEST CODE | |
Since modules are loaded out of order, we never know when we are done. | |
In real world scenario we could set a timeout and throw an exception if not all dependencies are available after it has expired. | |
Alternatively we start a ticker that just prints the status of the queue ever 5 seconds, if not empty. | |
]]-- | |
if #queue > 0 then | |
for i, definition in ipairs(queue) do | |
print(string.format("Module: %s was not loaded", definition.name)) | |
end | |
error("Not all modules could be loaded") | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment