hypixel skyblock
Lock
Page Protected from Editing
This page is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, it is subject to Page Protection.

Usage

The loader require and loadData are used to load a list of modules. These functions accept relative paths.

What is lazy load? When to use lazy load?

The term "lazy loading" is a strategy for loading resources.

Simply put, the lazy loading here is a layer between a required module and the code that accesses it. When running local string = loader.lazy.require('String'), the variable string is loaded with the layer, not the loaded module - in fact, the module is not loaded. The layer is responsible for loading the module when it is first accessed (string.trim()) or called (yesno()).

It will take up a little more memory and a tiny bit more access time. The benefit is that when a large amount of other modules are required for different parts of a module, only the used ones are required, while preserving the loading-on-top semantic and flexible package grouping that some programmers like. This untangles parts of the cobweb of modules that are required on a page, and reduces the impact to other modules during exceptions to one or a few modules.

If a module requires many other modules, but only a small subset will be used in most calls (when loading pages) to the module, it is beneficial to lazy load over normal load. If the other modules required are usually used every time it is called (when loading pages), then the benefit of lazy load is minimal to none compared to normal load.


Load Normally

The results work the same way as require() and mw.loadData().

local loader = require('Module:Loader')
local string, table, yesno = loader.require('String', 'Table', 'Yesno')
local myData, myData2 = loader.loadData('MyData', 'MyData2')

Lazy Load

The lazy loader returns a proxy (a special table) that loads the module when it is accessed the first time.
If used on table-like exports (packages or data tables) or function exports, the proxy can be used the same way as a table/function.
If used on other exports types, the loaded data will be loaded in the 'value' field in the resulting table.

local loader = require('Module:Loader')
local string, table, yesno = loader.lazy.require('String', 'Table', 'Yesno')
local myData, myData2 = loader.lazy.loadData('MyData', 'MyData2')

Submodules

No submodules found for this page. Try purging the page or viewing all subpages.


See Also

Hypixel SkyBlock Wiki Standard Lua Libraries (hsw/stdll) v · d · e
Type Libraries
Module Loading Utilities
  • Module:Loader (Current standard for normal/lazy loading)
  • Module:LoadLib (Auto loads some modules; customizable method loading)
  • Module:MultiRequire (Also pushes all exports to _G)
General Utilities
Meta Modules
Object-oriented
Caching
Module:Loader/doc

Module Code

local table, makeClass, libU = require('Module:Table'), require('Module:MakeClass'), require('Module:LibU')
local require, pcall, type, _G, debug, mw = require, pcall, type, _G, debug, mw
local checkType, checkArgs, assertTrue, assertFalse, makeCheckSelfFunction =
	libU.checkType, libU.checkArgs, libU.assertTrue, libU.assertFalse, libU.makeCheckSelfFunction
local makeCheckSelfFunction, forEachArgs = libU.makeCheckSelfFunction, libU.forEachArgs

local dpl, json

local p = {}
_G.loader = p
p.registry = _G.loader.registry or {}
p.proxyregistry = _G.loader.proxyregistry or {}
local loadable = table.Set{
	'bit32',
	'libraryUtil',
	'ustring',
	'luabit.hex',
	'luabit.bit',
}
local stack, moduleName = _G.loader.stack, _G.loader.moduleName
p.stack = stack
if not _G.loader.stack then
	_G.loader.stack = table.slice(mw.text.split(debug.traceback(''), '\n\t'), 2)
	_G.loader.moduleName = (not loader.stack[2]:match('mw.lua:487:') and loader.stack[3] or loader.stack[1]):match('^(.+):%d+:.+')
	stack, moduleName = _G.loader.stack, _G.loader.moduleName
end

local function formatError(e, module, path)
	if type(e) ~= 'string' then return nil end
	
	if e:match('not found') then return ('Module %q was not found (in path %q)'):format(module, path)
	elseif e:match('loop') then return ('Loop or previous error loading module %q'):format(module)
	elseif e:match('^[Mm]odule:') then return ('Exception in loading module %q at line %s: %s'):format(module,  e:match('^%w+:%w+:(%d+)'), e:gsub('^%w+:%w+:%d+:%s*', ''))
	else return e
	end
end
local function formatDataLoadError(e, module, path)
	if type(e) ~= 'string' then return nil end
	
	if e:match('not found') then 
		return ('Module %q was not found (in path %q)'):format(module, path)
	elseif e:match('unsupported') then
		return ('The data from module %q contains an unsupported data type %q'):format(module, e:match('unsupported data type [\"\'](.-)[\"\']'))
	elseif e:match('metatable') then
		return ('The data from module %q contains a table with a metatable'):format(module, e:match('unsupported data type [\"\'](.-)[\"\']'))
	elseif e:match('loop') then 
		return ('Loop or previous error loading module %q'):format(module)
	end
end

---------------------------------------------------------------------------------
-- For lazy loading modules
-- Thanks to Pythians for helping figure this out. Code referenced:
-- https://pythians.github.io/blogs/2020-12-28-lua-lazy-loader/
---------------------------------------------------------------------------------
local proxyId = {}
local makeProxy
do -- scope for proxy function
	local checkIndex = function(t)
		local pIndex = rawget(t, proxyId)
		if type(pIndex) == 'table' and pIndex.__need_load__ then
			local chunk
			if p.registry[pIndex.__path__] then
				chunk = p.registry[pIndex.__path__]
			else
				chunk = pIndex.__method__(pIndex.__path__)
				p.registry[pIndex.__path__] = chunk
			end
			if (type(chunk) == 'table' or type(chunk) == 'function') then
				rawset(t, proxyId, chunk)
			else
				rawset(t, proxyId, { value = chunk })
			end
		end
	end
	local function for_iter(a, i)
		i = i + 1
		local v = a[i]
		if v then return i, v end
	end
	local mt = {
		__index = function(t, k)
			checkIndex(t)
			local imt = getmetatable(rawget(t, proxyId))
			if imt and imt.__index then
				return imt.__index(rawget(t, proxyId), k)
			else
				return rawget(t, proxyId)[k]
			end
		end,
		__newindex = function(t, k, v)
			checkIndex(t)
			local imt = getmetatable(rawget(t, proxyId))
			if imt and imt.__newindex then
				imt.__newindex(rawget(t, proxyId), k, v)
			else
				rawget(t, proxyId)[k] = v
			end
		end,
		__pairs = function(t)
			checkIndex(t)
			local imt = getmetatable(rawget(t, proxyId))
			if imt and imt.__pairs then
				return imt.__pairs(rawget(t, proxyId))
			else
				return next, rawget(t, proxyId), nil
			end
		end,
		__ipairs = function(t)
			checkIndex(t)
			local imt = getmetatable(rawget(t, proxyId))
			if imt and imt.__ipairs then
				return imt.__ipairs(rawget(t, proxyId))
			else
				return for_iter, rawget(t, proxyId), 0
			end
		end,
		__call = function(t, ...)
			checkIndex(t)
			local imt = getmetatable(rawget(t, proxyId))
			if imt and imt.__call then
				return imt.__call(rawget(t, proxyId), ...)
			else
				return rawget(t, proxyId)(...)
			end
		end,
	}
	-- function export from scope
	makeProxy = function(path, method)
		return setmetatable({
			[proxyId] = {
				__need_load__ = true,
				__path__ = path,
				__method__ = method,
			}
		}, mt)
	end
end

---------------------------------------------------------------------------------
-- Helper function to create a require function
---------------------------------------------------------------------------------
local function createRequireFunc(basePath, isData, lazy)
	local function requireSingle(i, path, args)
		local oldPath = path
		assertTrue(path ~= '', 'Path may not be empty (in argument #%d)', 4, i)
		path = p.resolvePath(path, basePath or moduleName)
		if lazy then
			if p.proxyregistry[path] then
				args[i] = p.proxyregistry[path]
			else
				args[i] = makeProxy(path, isData and mw.loadData or require)
				p.proxyregistry[path] = args[i]
			end
		else
			if p.registry[path] then
				args[i] = p.registry[path]
			else
				args[i] = (isData and mw.loadData or require)(path)
				p.registry[path] = args[i]
			end
		end
	end
	return function(...)
		local args = { ... }
		local ret = {}
		local options
		local doUnpack = true
		if type(args[1]) == 'table' then 
			args = args[1] 
			doUnpack = false
		end
		if type(args[2]) == 'table' then options = args[2] end
		options = options or {}
		
		if i ~= 1 then
			for i, path in forEachArgs({ 'string', 'table', required=1 }, unpack(args)) do
				requireSingle(i, path, args)
			end
		else
			requireSingle(1, ..., args)
		end
		if doUnpack then
			return unpack(args)
		else
			return args
		end
	end
end

---------------------------------------------------------------------------------
-- Loading Functions
--
-- Loads a list of modules. Accepts relative paths
---------------------------------------------------------------------------------
-- function: .require(...modules: table<string>|string[])
p.require = createRequireFunc(nil, false, false)
-- function: .loadData(...dataModules: table<string>|string[])
p.loadData = createRequireFunc(nil, true, false)
-- Lazyloader:
-- If used on table-like exports (packages or data tables) or function exports, the usage is the same as .require and .loadData.
-- If used on other exports types, the loaded data will be loaded in the "value" field.
p.lazy = {
	-- function: .lazy.require(...modules: table<string>|string[])
	['require'] = createRequireFunc(nil, false, true),
	-- function: .lazy.loadData(...dataModules: table<string>|string[])
	['loadData'] = createRequireFunc(nil, true, true)
}

-- p.load = p.require
-- _G.loadData = p.loadData
---------------------------------------------------------------------------------
-- function: .resolveRelativePath(relativePath: string, basePath: string)
-- 
-- Resolves a relative path based on `basePath`.
---------------------------------------------------------------------------------
function p.resolvePath(relativePath, basePath)
	checkType(1, relativePath, 'string')
	checkType(2, basePath, 'string', true)
	basePath = basePath or moduleName:gsub('^[Mm]odule:', '')
	assertTrue(relativePath ~= '', 'Path may not be empty', 3)
	
	local _ = (basePath:match('^(%w+:)') or relativePath:match('^(%w+:)'))
	local isOtherPrefix = _ ~= 'Module:'
	-- local isOther = loadable[relativePath] or loadable[basePath] or isOtherPrefix
	local prefix = 
		(loadable[relativePath] or loadable[basePath])
			and ''
		or isOtherPrefix 
			and _
		or 'Module:'
	
	relativePath = relativePath:gsub('^'..prefix, '')
	basePath = basePath:gsub('^'..prefix, '')
	
	local stack = mw.text.split(basePath, "/")
	local parts = mw.text.split(relativePath, "/")
	local base = table.deepCopy(stack)
	
	if parts[1]:match"~" or (not table.some(parts, function(_, v) return v == '.' or v == '..' end) and parts[1] ~= '') then 
		parts[1] = parts[1]:gsub("~", '')
		return prefix..table.concat(parts, '/')
	end
	
	for i = 1, #parts, 1 do
		local v = parts[i]:gsub('^%$$', base[1])
		if v ~= "." then
			assertFalse((i > 1 and i < #parts) and v == "", "Invalid path %q: Path level/name must not be empty", 2, relativePath)
			if v == ".." then
				table.pop(stack)
			elseif v ~= "" then
				table.push(stack, v) 
			end
		elseif #base > 1 then
			table.pop(stack)
		end
	end
	
	return prefix..table.concat(table.map(stack, function(v, i)
		return v:gsub('^#$', stack[i-1] or stack[1])
	end), '/');
end

---------------------------------------------------------------------------------
-- function: getModuleNames(namespace?: string)
-- 
-- Lists all the availble modules to load.
---------------------------------------------------------------------------------
function p.getModuleNames(namespace)
	if not dpl then dpl = p.require('Module:DPL') end -- lazy-load
	
	return dpl.list{
		namespace=namespace or 'Module',
	}
end

---------------------------------------------------------------------------------
-- function: getRegistry(lazy: boolean)
-- 
-- Gets the loader's registry
---------------------------------------------------------------------------------
function p.getRegistry(lazy)
	return p.registry
end

---------------------------------------------------------------------------------
-- Helper class for manually registered modules
---------------------------------------------------------------------------------
do
	local Module = {}
	local checkSelf = makeCheckSelfFunction(Module, namespace or 'module')
	
	function Module:addExport(...)
		checkSelf(self)
		local name, payload = checkArgs({ 'string', { 'any' } }, ...)
		self.exports[name] = payload
		return payload
	end
	
	function Module:addExports(...)
		checkSelf(self)
		local exports = checkArgs({ 'table' }, ...)
		
		for name, value in pairs(exports) do
			self:addExport(name, value)
		end
		
		return exports
	end
	
	function Module:constructor(path)
		self.path = path
		self.exports = {}
	end
	
	p.Module = makeClass.makeClass(Module)
end

---------------------------------------------------------------------------------
-- function: register(path: string, payload: function)
-- 
-- Registers a module to the registry.
---------------------------------------------------------------------------------
function p.register(...)
	local path, payload, namespace = checkArgs({ 'string', 'function', { 'string', nilOk=true } }, ...)
	
	path = path:gsub('^[Mm]odule:', '')
	local oldPath = path
	path = p.resolvePath(path)
	local module = p.Module(path)
	
	local success, res = pcall(payload, createRequireFunc(path), module)
	if res == nil then res = 'unknown error' else res = tostring(res) end
	
	assertTrue(success, 'Exception in registring module %q%s: %s', 2, path, res:match('^%w:%d+:') and (' in %s at line %s'):format(res:match('^(.-):%d+:'), res:match('^.-:(%d+):')) or '', res:gsub('^(.-):%d+:%s*', ''))
	p.registry[path] = module.exports
	return module.exports
end

loader.titleCache = {}

---------------------------------------------------------------------------------
-- function: getSubfiles(dir: string, recurse?: boolean)
-- 
-- Reads a directory.
---------------------------------------------------------------------------------
function p.getSubfiles(...)
	if not dpl then dpl = p.require('Module:DPL') end -- Lazy-load
	
	local dir, recurse = checkArgs({ 'string', { 'boolean', nilOk=true } }, ...)
	assertTrue(dir ~= '', 'path may not be empty', 2)
	
	local oldDir = dir
	dir = p.resolvePath(dir)
	local results = table.filter(dpl.getSubpages(dir, recurse), function(v) return v ~= '' end)
	local titleObj = loader.titleCache[dir] or mw.title.new(dir)
	loader.titleCache[dir] = titleObj
	
	assertTrue(#results ~= 0 or titleObj.exists, 'Directory "%s/" not found (in path %q)', 2, dir, oldDir)
	
	return results
end

---------------------------------------------------------------------------------
-- function: readDir(dir: string, options?: table)
-- 
-- Get's the directories subfiles and reads all of them.
---------------------------------------------------------------------------------
function p.readDir(...)
	local dir, recurse, includeFilenames = checkArgs({ 'string', { 'table', nilOk=true } }, ...)
	local ret = {}
	dir = p.resolvePath(dir)
	
	for _, fname in ipairs(p.getSubfiles(dir, recurse)) do
		ret[fname] = p.readFile(fname)
	end
	
	return ret
end

---------------------------------------------------------------------------------
-- function: readFile(...directories: string)
-- 
-- Gets the contents of a file (Already existing on-wiki).
---------------------------------------------------------------------------------
function p.readFile(...)
	local doUnpack = true
	local args = { ... }
	
	if type(args[1]) == 'table' then 
		args = args[1] 
		doUnpack = false
	end
	
	for i, path in forEachArgs({ 'string', required=1 }, unpack(args)) do
		path = p.resolvePath(path)
		local title = loader.titleCache[path] or mw.title.new(path)
		loader.titleCache[path] = title
		
		assertTrue(title.exists, 'Module %q not found', 2, path)
		
		args[i] = title:getContent()
	end
	
	if doUnpack then
		return unpack(args)
	else
		return args
	end
end

---------------------------------------------------------------------------------
-- function: loadFile(...directories: string)
-- 
-- Loads a number of files on wiki. 
--  *If the file is a .json file, it parses it.
--  *if the file is a lua module, it runs it.
---------------------------------------------------------------------------------
function p.loadFile(...)
	local args = { ... }
	local oldPaths = { ... }
	local doUnpack = true
	local t = table.mapWith({ ... }, function(_, v) return p.resolvePath(v) end)
	
	if type(args[1]) == 'table' then 
		args = args[1] 
		doUnpack = false
	end
	
	for i, path in forEachArgs({ 'string', required=1 }, unpack(t)) do
		if path:match('%.json$') then
			if not json then json = p.require('Module:JSON') end -- Lazy-load
			
			local success, res = pcall(json.decode, p.readFile(path))
			
			assertTrue(success, 'Invalid file json from file %q: %s', 2, path, tostring(res):gsub('^Module:%w+:%d+:%s*', ''))
			args[i] = res
		elseif path:match('^Module:') then
			local success, res = pcall(p.require, oldPaths[i])
			
			assertTrue(success, res, 2)
		else
			local title = loader.titleCache[path] or mw.title.new(path)
			loader.titleCache[path] = title
			
			assertTrue(title.exists, 'File %q not found', 2, path)
			args[i] = title:getContent()
		end
		p.registry[path] = args[i]
	end
	
	if doUnpack then
		return unpack(args)
	else
		return args
	end
end

---------------------------------------------------------------------------------
-- function: loadDir(dir: string, options?: table)
-- 
-- Loads and evaluates a directory.
---------------------------------------------------------------------------------
function p.loadDir(...)
	local dir, recurse, includeFilenames = checkArgs({ 'string', { 'table', nilOk=true } }, ...)
	local ret = {}
	dir = p.resolvePath(dir)
	
	for _, fname in ipairs(p.getSubfiles(dir, recurse)) do
		ret[fname] = p.loadFile(fname)
	end
	
	return ret
end

---------------------------------------------------------------------------------
-- function: removeModule(...paths: string)
-- 
-- Removes modules from the registry.
---------------------------------------------------------------------------------
function p.removeModule(...)
	local removed = {}
	for _, path in forEachArgs({ 'string', required=1 }, ...) do
		path = p.resolvePath(path)
		
		assertTrue(p.registry[path], 'Module %q not found in module registry', 2, path)
		
		removed[path] = p.registry[path]
		p.registry[path] = nil
	end
	return removed
end

---------------------------------------------------------------------------------
-- function: requireDir(dir: string, recurse?: boolean)
-- 
-- Loads a directory.
---------------------------------------------------------------------------------
function p.requireDir(...)
	local dir, recurse = checkArgs({ 'string', { 'boolean', nilOk=true } }, ...)
	
	assertTrue(dir ~= '', 'path may not be empty', 2)
	local results = p.getSubfiles(dir, recurse)
	return p.require(unpack(results))
end

return p