I’m currently using a simplified version of Quenty’s Nevermore Engine to require modules that may need to use the same information multiple times throughout the game.
At this moment I’m getting warnings for cyclical requiring (I know what this means, I’m just confused as of how I can get around this because I still need to access these modules.) It’s 100% possible I’m just not using Nevermore as it’s supposed to be used, but I would appreciate pointers in the right direction to support fully modular code moving forward.
Warning - originates from one of Nevermore’s default modules:
Warning: Cyclical require on "ServerScriptService.Eos.Server.Data.Profiles".
Cycle: Profiles -> Profiles - Server - ModuleScriptUtils:36
Main Server Script References
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local Network = ReplicatedStorage.Network
local Functions = Network.Functions
local Remotes = Network.Remotes
local require = require(game.ReplicatedStorage.Eos)
local Profiles = require("Profiles") -- this is being cyclically required
local DailyRewards = require("DailyRewards")
Profiles - Module being referenced in the script above
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ProfileService = require(script.Parent.ProfileService)
After a very confusing conversation with @EmeraldSlash we decided I could just use a BindableFunction and invoke it whenever I want to get the most up-to-date profile for a player.
This ended up not working because profiles are cyclical as well.
I made the mistake of designing my modules to depend on each other by requiring each other (which results in cyclical requiring.)
I’m trying to find a solution that allows one module to interact with the others. I tried searching the DevForums for something similar to this to no avail.
A common solution is to make a single module that requires all others and puts their results into a table which is shared with all of the modules. This can be done by having each module return a callback which takes in the shared table or a global variable.
Anyways, this sort of cyclic dependency and tight state relationships is common in object oriented programming. If you search in my posts, you’ll find a wealth of anti-OOP material. Unfortunately the powers that be have pushed OOP beyond where it should have stopped. Do yourself a favor and learn to live without it: it yields little value beyond empty promises and ignorant friends.
from this it said that you should make 1 module that requires all the modules Eg. Aero here is Aero’s Interaction
local THIS_MODULE_NAME = self.Modules.THIS_MODULE_NAME
THIS_MODULE_NAME.Function(parameters)
I’m going to try to implement something similar to this in the morning. How would this work for client modules though? Granted I probably wouldn’t need to use client modules to interact with each other.
In Aero its automatically set-up for client-server in 1 Script, you should look into AeroServer and AeroClient, I personally use Aero and it’s great plus with VS Code things but if you want to make them interact without requiring then I suggest looking at Aero Core Scripts
Right, but i’d prefer not having to use Aero unless I really have to.
I’m going to try something you elaborated on later on in the day as it’s 2:00 AM and I need to sleep. I’ll mark it as a solution after I get something working — thanks again!
local LOAD_SUB_MODULES = false -- load modules that are a child of another module
local started = false
local services = {}
local modules = {}
local Quire = {}
function Quire.GetService(serviceName)
return assert(services[serviceName], "Service does not exist")
end
function Quire.LoadService(serviceModule)
services[serviceModule.Name] = require(serviceModule)
end
function Quire.GetModule(moduleName)
local module = assert(modules[moduleName], "Module does not exist")
return require(module)
end
function Quire.AddModule(module)
modules[module.Name] = module
end
function Quire.AddServiceDirectory(directory) -- called before Quire.Start() to load a directory of services
assert(not started, "Must be called before Quire.Start()")
assert(directory:IsA("Folder"), "Directory must be a folder")
for _, v in ipairs(directory:GetDescendants()) do
if v:IsA("ModuleScript") and not (LOAD_SUB_MODULES and v.Parent:IsA("ModuleScript")) then
Quire.LoadService(v)
end
end
end
function Quire.AddModuleDirectory(directory)
assert(directory:IsA("Folder"), "Directory must be a folder")
for _, v in ipairs(directory:GetDescendants()) do
if v:IsA("ModuleScript") and not (LOAD_SUB_MODULES and v.Parent:IsA("ModuleScript")) then
Quire.AddModule(v)
end
end
end
function Quire.Start()
assert(not started, "Quire is already started")
started = true
for i, v in pairs(services) do
if v.Init then v:Init() end
end
for i, v in pairs(services) do
if v.Start then v:Start() end
end
end
return Quire
Runtime script that loads the Quire module (One on server and one on client)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Quire = require(ReplicatedStorage.Quire)
Quire.AddModuleDirectory(ReplicatedStorage.SharedScripts.Modules)
Quire.AddModuleDirectory(script.Parent.Modules)
Quire.AddServiceDirectory(ReplicatedStorage.SharedScripts.Services)
Quire.AddServiceDirectory(script.Parent.Services)
Quire.Start()
Example Service
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Quire = require(ReplicatedStorage.Quire)
local AnotherService
local Service = {}
function Service:Init()
AnotherService = Quire.GetService("AnotherService")
end
function Service:Start()
AnotherService.Test()
end
return Service
I use my own lightweight module/service loader, the general idea is to have Start and Init Methods in a service and reference other services in the Init method, and only use them after the Start method
I don’t really recommend using my module though as it wasn’t intended to be used by anyone else so it’s still a wip
I have a system that loads a bunch of modules based on a “manifest” file to determine init order. All modules are required at the start and stored in a table, and they’re initialized randomly unless the manifest says to prioritize it
I keep seeing references to creating some sort of manifest as you’ve described, but I keep end up running into a similar cyclical issues. Maybe I’m just over-thinking this? Do you have any code you would be willing to provide as an example?
Not familiar with the workings of Nevermore, and this is a pretty unusual problem to run into in well structured scripting enviroments, but a dirty way of doing cyclical requiring is requiring inside the function.
For example
Script A
local ScriptB = Loader:Get("ScriptB")
local function DoStuff()
ScriptB.Foo()
end
Script B:
local ScriptB = {}
function ScriptB.Foo()
local ScriptA = Loader:Get("ScriptA")
--do stuff
end
This isn’t great, but occasionally it makes sense to do this rather than restructure a large swarth of code.
That’s what I figured looking into Nevermore and never finding anyone report cyclical requiring modules. I’ll just leave it up rouser error on my behalf I suppose.
The start method is called on all services after the init method is called on all services, this is to make sure all services are initialized before they start using each other