Fixing this circular dependency

I’m having trouble fixing a circular dependency in my script. I’ve tried looking around for an answer but I can’t seem to get them working (or maybe I’m not doing them right). The issue is both DataManager and KickManager need features from each other. What should I do here?

Here’s the scripts:

-- Server script
local DataManager = require(ServerModules:WaitForChild("DataManager"))
--This starts initializing the DataManager so it runs
-- DataManager
local KickManager = require(script.Parent:WaitForChild("KickManager"))
-- KickManager
local DataManager = require(script.Parent:WaitForChild("DataManager"))
1 Like

This type of strong coupling is usually discouraged within programming. Two-way dependencies can cause you huge headaches in the future. While your problem is solvable, I would first ask: Is it possible for you to redesign your code in such a way that you don’t have to have a two-way dependency?

9 Likes

You could always make a third module that has the shared features and use it in each of your modules, or combine everything into one module. Otherwise you need to find a way to make it work the way you have it - that means having the features you want for both inside both modules themselves, rather than having them rely on each other.

You don’t want to have cyclic dependencies, it should be linear so you don’t get confused lol

And even more so, you probably confuse the computer which is a difficult thing to do

3 Likes

I’ve seen the following approaches taken by games:

Approach 1
  • Make a library module create a table in _G
  • Have the library module require all other modules and put their returns into the table
  • Make the table have a function which yields until the specific module has been put into it
  • Have all other modules use the table

Example:

--Library Module--
local lib = {}
function lib.Preload(name)
    repeat wait() until lib[name] --just an example, please use a better way to do this
end
_G.Library = lib

for i,v in pairs(someModulesList) do
    coroutine.wrap(function()
        lib[v.Name] = require(v)
    end)()
end
--Any module--
local lib = _G.Library
local stuff = lib.Preload("name")
--stuff
Approach 2
  • Make a library module create a table and return it
  • Have the library module use coroutine.wrap to require all other modules and put their returns into the table
  • Have the modules require the library module and use its table in a similar manner to the one in approach 1

Example:

--Library Module--
local lib = {}

coroutine.wrap(function()
    for i,v in pairs(someModulesList) do
        lib[v.Name] = require(v)
    end
end)()

return lib
--Any module--
local lib
coroutine.wrap(function()
    lib = require(pathToLibraryModule)
end)()
--stuff

(I think this one also used a Loaded field but can’t remember how exactly)

3 Likes

This is one of the most annoying things I ever personally came across I didn’t know why was wrong because at the time, and I believe even still to this day, the console/Ouput will not throw up an error or any message whatsoever when it runs into a self to self require situation. . The solution is simple enough, but, crazy man really is right. Eliminating the need entirely is the best way to go.

2 Likes

Quick question: If you were to setup a main library that required all the other modules (mentioned by @tralalah and @Amiaa16), then why would you want to refactor the code? Is it better to do in some way? Because at least in my case, it’s nicer to have everything with to do with kicks managed through the KickManager.

1 Like

Everything to do with kicks should be managed through KickManager, just as you say. If your DataManager is handling kicks, I think this is where the code refactoring comes in. Otherwise why else have the KickManager be a dependency? If they are sharing information between each other, it might be cleaner to write it all as one module in my opinion. It’s not the only option, like I suggested above there are other ways to handle it.

If you wouldn’t mind sharing, what does the KickManager need from the DataManager?

1 Like

Ideally, a good principal is to design modules in such a way that they are not interlocked with others. If two modules are talking to each other, then it kinda defeats the purpose of modularity.

Again, it also can cause you headaches in the future. One change in one of those modules could break them both. Cohesion and Coupling are good computer science topics that might be of interest.

1 Like

The KickManager contains methods like KickUser and BanUser. It’s supposed to be my game’s way of kicking/banning people, but with some extra features that need to be assisted by data (the banning functionality, for example). The DataManager needs KickUser and the KickManager needs the player’s profile data. I would assume based off of my intended use the best option would be creating a central library?

Also, in regards to the code refactoring suggested by Crazyman32, wouldn’t adding a main library count as refactoring them in a way?

1 Like

I know this might not solve your problem but the data hierarchy I use goes like this:

image

Each child module depends on the parent module for information, but otherwise is completely independent. For example, the top-most module might contain information relevant to all modules, like services or a reference to the remote events folder. A child module called ItemVars might hold item information that another child module could use, and so on. I apologize if I explained it weird or if this structure isn’t particularly useful.

1 Like

This was the kind of structure I’m leaning to at the moment, but I’m curious to hear the possible refactoring side of fixing this that wouldn’t deal with a “parent” module, first.

1 Like

Yes, refactoring can be adjusting the code inside your libraries or adjusting your structure entirely (obviously not rewriting as much as possible).

Also, I would say that since the two rely on each other so much, you could probably save a lot of space just combining them into one library. I know I’m just restating my opinion from earlier, but unless you use these libraries for anything else, it probably makes sense to make a so-called “PlayerHandler” that handles data and kicking. The fact that they rely on each other means either you’re mixing too much, or they should be combined into one module.

I’m not an expert, but unless it’s some crazy extreme circumstance, try to decide based on how much the modules have in common. If there’s a lot, then you could merge them. If it’s not a large similarity, it could be a good idea to have a central library with their differences (but that’s also annoying to set up, as you might guess).

If you have some control script instead that manages these libraries, it would be able to give the libraries access to each other without having a circular dependency - you can just pass methods or call methods based on how your game logic is set up.

There’s a lot of options, usually it comes to personal preference + what the specifics are of your code layout. It might be worth playing around with multiple configurations if you have the time for that

Combining them into one sounds like a good idea. I’ll go with that. Thanks for the help!

1 Like

I wouldn’t recommend this. Using BindableFunctions (and by extension BindableEvents) have a plethora of limitations that can potentially cause problems or are otherwise unideal - notably how they copy tables. It’s better to separate shared module logic and/or data into a new module.

1 Like

Looked into that. You are right about the nuances. I have deleted fixed that with this updated response:

As per silencing the circular dependancies, I have figured out a very simple patch that resolves the circular dependancy problem, while providng autofill capabilities.

local function return_exact(...)
	return ...
end

local simplegui = require(return_exact(packages.Client.simplegui))

This silences the error, but you won’t be able to get the autocompletes from the require. But that is the basis of what you can do.

By knowing this, you can create a “header” file that emulates the autocomplete for you:

hello modulescript:

local hello = {}

-- example of cyclic function
function hello.hello_function(stuff_to_send:string):string
 print(stuff_to_send)
 print("Hello")
 require(path.to.bye.modulescript).bye_function("Test")
end

return hello

hello_h modulescript:

local function return_exact(...)
	return ...
end

local hello_h = {}

function hello_h.hello_function(stuff_to_send:string):string
 return(return_exact(path.to.hello.modulescript)).hello_function(stuff_to_send)
end

return hello_h

bye modulescript

local bye = {}

function bye.bye_function(text:string)
 print(text)
 print("Bye")
end

function bye.hello_bye(text:string)
 return require(path.to.hello_h).hello_function(text)
end

return bye

and if I made a script that does

local a = require(path.to.bye.modulescript)
a.hello_bye("Works!")

it would successfully execute and print:

Works!
Hello
Test
Bye

Update:
I have honestly simplified it down to something very simple: dependancy caching.

I simply do:

local module_b_requirement = nil
function module_a.run()
 if not module_b_requirement then module_b_requirement = require(path.to.module_b) end
 -- Code that uses functions from module_b
end

This way, you can use circular dependancies without causing any recursive requirement errors. This also has the benefit of having the function run quickly and efficienctly without having to re-require the module_b every time it is ran. You can have multiple functions that do this so that you can do circular dependancies with ease while respecting the laws of code.