Best approach for modules to interact with eachother?

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)
1 Like

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.

Bumping - the previous solution I originally updated ended up not working due to the table being cyclical as well.

what are you trying to do, just needed a reference for it since the topic is just not understandable for me

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.

Have you tried looking into this topic?

2 Likes

so on the topic you are trying to make ProfileService interact with the Daily Reward Module?

1 Like

I have a few modules that need to interact with the Profiles module, but yes, that’s the general idea.

from @IdiomicLanguage’s Solution

you should:

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)
1 Like

I tried this already and still got a warning for cyclical requiring multiple modules.

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

Also look at Aero’s API here!

1 Like

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!

1 Like
Quire module
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

1 Like

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

1 Like

After testing some things I feel like this is most likely the easiest solution to implement specifically to my problem.

Is :Start() a required method, or can I just get away with using :Init()? If not, what is the intended purpose of Start?

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.

1 Like

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

1 Like