There is currently no way to resolve valid cyclic dependencies.
There are certain times when cyclic dependencies are unavoidable without some clunky workaround. For example, take something like this:
--|| Module Bar
local Foo = require(Foo) --> cyclic dependency with module "Foo"
Foo.someFunction()
--|| Module Foo
local Bar = require(Bar) --> cyclic dependency with module "Bar"
task.delay(2, function()
Bar.someFunction()
end)
We know that the module “Bar” requires immediate access to module “Foo”, but module “Foo” does not need immediate access. Thus, we can resolve runtime conflicts by doing something like this:
--|| Module Foo
local Bar do
task.defer(function()
Bar = require(Bar) --> cyclic dependency with module "Bar"
end)
end
task.delay(2, function()
Bar.someFunction()
end)
Now there are no runtime errors, as the “Foo” module only requires “Bar” after it has loaded. Unfortunately, the warnings still persist and Intellisense will not work on all affected modules.
A proposed solution can be some sort of promise-like method. This can be in the form of something like task.register, which sets the variable to nil until the passed content returns a value:
--|| Module foo
local Bar = task.register(Bar)
print(Bar) --> nil
print(task.registered(Bar)) --> false
task.delay(1, function()
print(Bar) --> the contents of "Bar"
print(task.registered(Bar)) --> true
end)
A possible internal implementation could be that a spot in memory is reserved and a pointer is created in place of that variable, in which it can be filled once a value is successfully created.
This is an architectural problem. You shouldn’t have two modules that rely on each other. Redesign your system so that you don’t have a two-way dependency.
We shouldn’t have to add bloat or dependency injection to our systems for something that Roblox is perfectly capable of managing for us. They can create an underlying intermediary, something as simple as a middle-man table within require’s runtime that just returns any previously required module.
It’s not like requiring the module returns a unique form of that module beyond it’s first requiring.
If anything, Roblox is more likely to develop a more useful solution for cyclical requires than something we could do.
This specifically has become a problem for me with exported types; my module structure has a parent-child relationship where the deeper you go, the more low-level the functions are. The issues come in when I share a type between both the parent and child module; right now, the process is to store the type under the child module, this means regularly needing to restructure the types any time I add a newer ‘deeper level’ to the module chain. This has to be done because a child can never access its parent’s type since its parent relies on its functions.
An ideal solution for me would be to be able to retrieve the type information without needing to actually require the module. Obviously issues come in when we need to determine what the required module’s return is (since it hasn’t returned yet) however a cache and clever referencinh cpuld possibly be able to work, at least only for the typechecking system.
There are also cases where I want to use a function from the parent module in the child function; this is actually possible to acheive by requiring the module inside the function (albeit it looks bad and breaks do-not-repeat-yourself principles). You can’t have type information here either iirc, effectively needing to cast the module to any. For these cases, supporting a way to get that type information would be most ideal since I could register all the actual requires under a task.spawn at the start of the module.
Obviously this is a very complex issue to deal with since there are technically multiple ways to approach it, each with different caveats and behaviour that may be difficult to describe to developers albeit this is still a massive issue we come into and, in my case, forces me to drop typechecking for certain modules.
To my knowledge, this wouldn’t work on Roblox since the mediator would need to require each module; making it unable to be required by those modules? Unless maybe I’m misunderstanding something?
Normally under the circumstances that we do not control, we have to design and work around issues with clever solutions
Not necessarily, the problem with Cyclical dependencies is it’s a two way require
So design it as a one way require instead where:
ModuleOne requires Mediator
ModuleTwo requires Mediator
Mediator never ever requires ModuleOne nor ModuleTwo
Mediator has methods for communication and sharing memory between ModuleOne and ModuleTwo
You can borrow designs and principles from Actor paradigm as well:
+1 on the typing problem. I have found myself putting parent types into separate locations that aren’t very ideal. I would like to define a class type where the class is, not somewhere else. All that does is complicate things.
I have used mediators, but mediators do not resolve every problem and can become quite impractical, especially when two or more cyclic classes rely heavily on each other and other classes outside of the cyclic classes rely on methods from the cyclic classes:
“CollectorUtil” is just one function that cannot be split apart. This module requires access to the “Hive”. But, unfortunately there’s a cyclic dependency between “CollectorUtil”, “Hive”, and “Bee”.
perhaps it’s time to redesign and use a different approach, i recommend Functional programming, Procedural programming, Event-driven programming, using Tags, Attributes and Test Driven Development
seems like the problem you have is a tightly coupled spaghetti monster code base
can I ask if you’re using a Framework? if so what Framework is it?
are you using a lot of Object Orientated Programming for your code base as well?
I don’t use any framework. I use object oriented programming that is event-driven and classes are composed, not inherited. I have functional programming sprinked in a localized area of the code but that is all isolated from the rest of the codebase.
Maybe the current layout is spaghetti, however, I have not found any grounds for a better layout. Everything in the game is interconnected and can affect each other (it gets more complicated than this diagram) but all of these things are different enough that you can’t really split them up, because then it wouldn’t make any sense. I consider it less of a structural issue but more of a game idea-specific problem.
Now I’ve tried splitting up the code but that simply leads to a massive dependency list for each class which is unsustainable in the future.
I would have to see your code base though from my experience using OOP usually causes these problems the more complex a game becomes because the object contains pretty much everything and it does way too much
To solve your original problem of a 3 way require can you try implementing a Signal module for those 3 modules to communicate and share memory instead of directly requiring each other? let me know if that fixes the issue
So have those 3 modules require the Signal module instead of requiring each other, the Signal module requires nothing
The Signal module shares memory and enables communication between the 3 modules
I recommend using GoodSignal
Why?
Before ModuleScripts become a thing in Roblox we only had Scripts and there was no way to require or communicate across Scripts so they added BindableEvents
BindableEvents aren’t needed anymore because we have ModuleScripts and especially LuauSignals are more superior to Bindables because LuauSignals don’t lose reference to the value you pass like a Bindable would nor have any limitations
Now I do understand the Feature request, my opinion is we shouldn’t be allowed to shoot ourselves in the foot so easily (C++), so the best solution is a better design
Though I do understand where you are coming from and I don’t think there’s anything wrong with wanting to simplify this pain point
I use a custom signal implementation very, VERY extensively. It’s the most used class in the entire codebase:
I think the best example I can think of with unavoidable cyclic dependency that describes my situation is Minecraft.
In Minecraft, there are so many blocks that can interact with each other. For example, the world has access to all the blocks, and TNT requires a query of neighboring blocks… which requires access to the world. Water can be poured on lava to make obsidian, and lava can be poured over water to make cobblestone. Observers are literally required to read the state of the block in front of it. Items are affected by blocks, and some blocks (such as pressure plates) are affected by items. Cyclic dependency is unavoidable and there really isn’t a way around it.
Now, Minecraft does have the advantage of being a compiled language, and so cyclic dependencies are naturally available and only comes with the cost of slower compilation and maybe performance loss. I’m totally fine with a loss of performance as I believe it’s quite negligable. Although I understand that you should avoid cyclic dependicies, it’s simply unavoidable in certain situations.
Side note:
I do believe that the feature I’m requesting is valid. It’s literally just a built in promise but better.
To explain my personal scenario further, I’m in the process of making a module that will be used by multiple developers on a team. I need to expose all of the important methods on the parent module since child modules are effectively just ‘internal backend stuff’ that doesn’t really need to be touched by any other developer on the team. The child modules still need to be able to invoke methods on the parent module when certain actions are performed since the parent modules implement functions both helpful for internal and non-internal purposes. Using a ‘mediator module’ isn’t ideal here since it means I effectively need to implement the parent functions twice or use a hacky solution to enforce the types; still effectively writing the type definition twice.
To make it clear, Roblox already supports my use-case from a functionality perspective. I’m requiring the parent module inside of a function in the child module, which in itself isn’t a cyclic reference. However, I have to cast that required module to any, killing any form of typechecking because the type-solver triggers a cyclic reference. Roblox already technically supports what I’m trying to do, albeit it makes your code-base really messy with duplicate type definitions.
Maybe this isn’t the best structure for code. However, there, unfortunately, isn’t really any other better way for me to structure my code without repeating myself.