Server Module Loader Design Review

Hello, recently I built a module loader (server-side) and would like to hear some opinions on it, as well as suggestions on what could be improved or where I might be doing things wrong.

Also, I’m wondering if it’s worth using strict type checking and annotating types everywhere.

I don’t want to overcomplicate it, but I want to make it safe.

local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local AttributeUtils = require(ReplicatedStorage.Shared.Modules.Core.AttributeUtils)
local DataManager = require(ServerScriptService.Data.DataManager)

local moduleFolders = {
	ServerScriptService:WaitForChild("Controllers"),
	ServerScriptService:WaitForChild("Services")
}

local modules = {}
local playerHandlers = {}
local characterHandlers = {}

local initializedModules = {}
local handledPlayers = {}
local connections = {}

local function sortByPriority(list)
	table.sort(list, function(a, b)
		return (a.Priority or 0) > (b.Priority or 0)
	end)
end

local function registerModule(moduleScript)
	table.insert(modules, moduleScript)

	if moduleScript.SetupPlayer then
		table.insert(playerHandlers, moduleScript)
	end

	if moduleScript.SetupCharacter then
		table.insert(characterHandlers, moduleScript)
	end
end

local function gatherModules()
	for _, folder in ipairs(moduleFolders) do
		for _, obj in ipairs(folder:GetDescendants()) do
			if not obj:IsA("ModuleScript") then
				continue
			end

			local success, moduleScript = pcall(require, obj)
			if not success or type(moduleScript) ~= "table" or not moduleScript.Init then
				continue
			end

			moduleScript.Priority = moduleScript.Priority or 0
			registerModule(moduleScript)
		end
	end

	sortByPriority(modules)
	sortByPriority(playerHandlers)
	sortByPriority(characterHandlers)
end

local function initModules()
	for _, moduleScript in ipairs(modules) do
		if not initializedModules[moduleScript] then
			initializedModules[moduleScript] = true
			moduleScript.Init()
		end
	end
end

local function runCharacterModules(player, character)
	if character:GetAttribute("Initialized") then
		return
	end

	character:SetAttribute("Initialized", true)

	for _, moduleScript in ipairs(characterHandlers) do
		moduleScript:SetupCharacter(player, character)
	end
end

local function cleanupPlayer(player)
	if connections[player] then
		for _, conn in ipairs(connections[player]) do
			conn:Disconnect()
		end
		connections[player] = nil
	end

	handledPlayers[player] = nil
end

local function track(player, conn)
	connections[player] = connections[player] or {}
	table.insert(connections[player], conn)
end

local function handlePlayer(player)
	if handledPlayers[player] then return end
	handledPlayers[player] = true

	local loaded = AttributeUtils.WaitForAttributes(player, {"DataLoaded"}, 10)
	if not loaded and not DataManager.Profiles[player] then
		warn("Data not loaded for " .. player.Name .. " (timeout)")
		return
	end

	for _, moduleScript in ipairs(playerHandlers) do
		moduleScript:SetupPlayer(player)
	end

	track(player, player.CharacterAdded:Connect(function(character)
		runCharacterModules(player, character)
	end))

	if player.Character then
		runCharacterModules(player, player.Character)
	end
end

gatherModules()
initModules()

Players.PlayerAdded:Connect(handlePlayer)
Players.PlayerRemoving:Connect(cleanupPlayer)

for _, player in ipairs(Players:GetPlayers()) do
	task.spawn(handlePlayer, player)
end

Don’t use ipairs and try to get rid of the priority order and focus on eliminating race conditions in each module

1 Like

Why remove priority? Because potentially I might want certain modules to run first.

Frankly speaking, what you are trying to solve here people call “dependency hell” and specifically “circular dependencies”.
Designing systems with this flaw is OK and that happens with best of us.
Obviously “module loader” do not address the problem and merely works around it.
As @Wynsage said, fix the root cause so you do not need this scrap.
Learn to design good systems.
Good luck!

Wow…
How is this scrap, s/he worked hard on this

read my post in the same topic you posted this previously on Help and Feedback > Scripting Support

1 Like

well, “I am sorry” then.
Post must be at least 3O characters. And I don’t think that :heart: button suits here.