(16.09.2024) Now it respects in which order modules was required
Hello everyone. Recently I’ve made my own Module Loader which I using for organization of my games, and now decided to share it.
If you don’t know what is that “Module Loader”, it’s module which helps you to organize you game, because you can write all code in different module scripts and only one server/client script gonna run them all. This makes your game scripts very modular, so you can remove or add them easily, without changing most of code.
local Loader = {}
local required_modules = {}
local get_cache = {}
function Require(modules)
local local_required_modules = {}
for _,module_script in modules do
if not module_script:IsA("ModuleScript") then continue end
if not module_script.Parent:IsA("Folder") then continue end
local module = require(module_script)
local_required_modules[module_script.Name] = module
end
return local_required_modules
end
function Loader.UpdateGetCache() -- Just to optimize get function
get_cache = {}
for _,required_modules_list in ipairs(required_modules) do
for required_module_name, required_module in required_modules_list do
get_cache[required_module_name] = required_module
end
end
end
function Loader.Get(name)
return get_cache[name]
end
function Loader.Require(modules)
if typeof(modules) == "Instance" then
table.insert(required_modules, Require(modules:GetDescendants())) -- if this folder or any other instance
elseif typeof(modules) == "table" then
table.insert(required_modules, Require(modules)) -- if this table with modules
end
Loader.UpdateGetCache()
end
function Loader.Start() -- loading and initing everything from "required_modules" variable
for _,required_modules_list in ipairs(required_modules) do
for required_module_name, required_module in required_modules_list do
if required_module.__load then
print("[LOADING MODULE]", required_module_name)
required_module.__load() -- synchronically one by one loads modules
end
end
end
for _,required_modules_list in ipairs(required_modules) do
for required_module_name, required_module in required_modules_list do
if required_module.__init then
print("[INITIALIZING MODULE]", required_module_name)
task.spawn(required_module.__init) -- asynchronically initializes each module
end
end
end
end
_G.Get = Loader.Get -- global function to get link to required module
return Loader
I tried to keep code clean and understandable as possible, so it can lack some safety checks. But I guess that’s not big problem, because it’s used for advanced scripters, which know what they’re doing.
It’s would be hard to explain how it works in details here, so I just gonna show some examples of code. How I used it.
--Single server script in my currently developed project
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Loader = require(ReplicatedStorage.Loader)
Loader.Require(ReplicatedStorage.Modules)
Loader.Require(script.Modules)
Loader.Start()
Basically what we doing. We getting our Loader module, and requiring in it two folders. One is shared between client and server in ReplicatedStorage, and other one is parented to our script, so all modules in it can be used only by server. Loader.Start()
loads and inits all modules which we required upper.
How organization of server scripts actually look in my project (it’s pretty small for now but gonna grow)
I made it to require childrens only of folders, and skip anything except module scripts.
So it gonna skip any garbarage in your folder, you can even put assets folder or something like that in your folder and it still gonna work normally.
If you gonna put module scripts in module scripts Loader gonna skip them, so you can have some local module scripts to some of your modules if you want. By the way you can easily change it, just a little modyfying Loader.
Same works with client
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Loader = require(ReplicatedStorage:WaitForChild("Loader"))
Loader.Require(ReplicatedStorage:WaitForChild("Modules"))
Loader.Require(script.Modules)
Loader.Start()
I highly recommend you to put Loader in ReplicatedStorage so it can be shared between client and server.
What is that “__init” and “__load” functions?
It’s additional functions of Loader, which is can be very useful. Basically it runs both of them for each module when you calling Module.Start()
Which is difference between “__init” and “__load”?
“__load” is called one by one synchronically for each module before “__init” is called. When “__init” is asynchronous function which called in different threads for each module at same time.
How it can be used?
At least for me, “__load” useful to get links of required modules, and getting them into global variables of my certain module, so I can use them later in it.
function Player:__load()
Health = _G.Get("Health")
Character = _G.Get("Character")
Janitor = _G.Get("Janitor")
TableUtility = _G.Get("TableUtility")
end
One example how I using it in one of my modules. I can use Health/Character/Janitor modules in any piece of my code here now. So let’s say in “new” function of my Player class I can do something like this:
self.character = Character:new(self)
self.character:load()
That’s it. Thanks for reading post. Tell me your throughts under, maybe some things to improve, or questions. I hope someone will find this useful.
P.S: Sorry for some troubles with grammar in this post. English not my first language, but I still hope you got everything I tried to say.