Server-sided Module Loader

  • This code takes a look at all modules in a folder called “Modules” inside ServerScriptService and runs .init() and .start() functions on them (every .init() function should run before any of the .start() functions run).
  • I have two main concerns with my current code, and I’m not sure if I should be too worried about them or not:
    • Are coroutines outdated because of the task.spawn() system? And if so, how can I replace current portions of my code with it?
    • The code seems very repetitive in certain parts, and I’m not sure if there’s a way to take up less space while maintaining readability.

Thanks for any feedback,
- P_rceptionNull (Noxira)

--!strict

--[[
	SERVER-SIDED MODULE LOADER:
	- requires all modules in Server Script Service's module folder
	- runs init() asynchronously on the modules, expects a returned true value
	- runs run() asynchronously on the modules, expects a returned true value
]]

--------------
-- SERVICES --
--------------
local ServerScriptService = game:GetService("ServerScriptService")

-----------
-- TYPES --
-----------
type RequiredModule = {
	["init"]: () -> boolean, -- runs first, used for connections (DO NOT STORE DEPENDENCIES IN HERE)
	["start"]: () -> boolean, -- runs after .init(), used for starting systems
	[string]: () -> any? -- for any public functions/methods that other modules may want to access
}

---------------
-- CONSTANTS --
---------------
local modulesFolder = ServerScriptService.Modules

---------------
-- VARIABLES --
---------------
local successfullyInitializedModules: { ModuleScript } = {} -- modules that returned true on .init()

---------------
-- FUNCTIONS --
---------------

-- Runs .init() on all modules and saves the ones that returned true on .init() to successfullyInitializedModules array.
local function initModule(requiredModule: RequiredModule, module: ModuleScript)
	if typeof(requiredModule.init) ~= "function" then -- verify init function exists
		error("[SERVER]: Module " .. module.Name .. " missing .init().")
	end

	local thread = coroutine.create(requiredModule.init) -- allow for asynchronous execution
	local success, result = coroutine.resume(thread)
	if not success then -- check for error in resuming coroutine
		error("[SERVER]: Coroutine failed for module: " .. module.Name .. ".")
	end
	
	if result then -- returned true, things are good
		print("[SERVER]: Successfully initialized module: " .. module.Name .. ".")
		table.insert(successfullyInitializedModules, module)
	else -- failed to return true
		warn("[SERVER]: Failed to initialize module: " .. module.Name .. ".")
	end
end

-- Runs .start() on all modules that were saved on successfullyInitializedModules array.
local function runModule(module: ModuleScript)
	local success, requiredModuleToStart: RequiredModule = pcall(require, module) -- make sure no issues when requiring
	if not success then
		error("[SERVER]: Error requiring module: " .. module.Name .. ".")
	end
	
	if typeof(requiredModuleToStart.start) ~= "function" then -- verify start function exists
		error("[SERVER]: Module " .. module.Name .. " missing .start().")
	end

	local thread = coroutine.create(requiredModuleToStart.start) -- allow for asynchronous execution
	local success, result = coroutine.resume(thread)
	if not success then -- check for error in resuming coroutine
		error("[SERVER]: Coroutine failed for module: " .. module.Name .. ".")
	end

	if result then -- returned true, things are good
		print("[SERVER]: Successfully started module: " .. module.Name .. ".")
	else -- failed to return true
		warn("[SERVER]: Failed to start module: " .. module.Name .. ".")
	end
end

-- Runs .init() on all module-scripts and runs .start() after they have all been initialized
local function run()
	for _, moduleToInit: ModuleScript in ipairs(modulesFolder:GetChildren()) do
		if not moduleToInit:IsA("ModuleScript") then -- verify things in module folder are modules
			continue
		end
		
		local success, requiredModule: RequiredModule = pcall(require, moduleToInit) -- make sure no issues when requiring
		if not success then
			error("[SERVER]: Error requiring module: " .. moduleToInit.Name .. ".")
		end
		
		initModule(requiredModule, moduleToInit) -- run the init, shouldnt hold up the thread
	end
	
	for _, moduleToStart: ModuleScript in ipairs(successfullyInitializedModules) do
		if not moduleToStart:IsA("ModuleScript") then -- makes sure array elements are modules
			continue
		end
		
		runModule(moduleToStart) -- run the start function, shouldnt hold up the thread
	end
end

-------------
-- BINDING --
-------------

-------------
-- RUNNING --
-------------
run() -- get things started

ServerSidedModuleLoader.rbxm (3.3 KB)

1 Like

Coroutines are not outdated at all, infact they are still used for Parallel scripting.
One thing i noticed is that you keep initializing variables inside your functions, make sure after you used them you set them to nil when you dont want to use it anymore to prevent memory leaks.

Overall your framework seems fine in my opinion.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.