So, I have a class called FirstPersonCamera and a class called ClientMain,
the issue is that to get the local player’s character object (my custom one, not the regular Players.LocalPlayer.Character one), it needs reference to the singleton instance of MainClient that can be obtained from a get method inside MainClient by doing: MainClient.GetInstance().
Now, when the main client initiates, it creates an instance of the FirstPersonCamera class.
Both the classes need to require each other.
In simple words, both the module scripts need to require each other.
But in doing so, I am getting the error: Requested module was required recursively.
What can I do to bypass this?
My current solution includes requiring the module only when needed instead of at the top of the script.
Note: This is slightly bad for performance, so I’d recommend you set the module equal to module object itself. Then in the functions that need the module run this code: if not module then module = require(module) end
My code is actually very similar in pattern to this one,
One solution I was thinking of was that as I have a ClassLoader module script that is itself not a class, I could add a class importing function like: ClassLoader.import("client.abc.defg")
will this work?
If it is, than I think I should use this one, as Roblox Studio had already left me to give autocomplete info a long time ago due it being overwhelmed and I index my code manually by looking into the class I am requiring
I want my game’s code to be structured as in any other static languages like java.
Is it really bad to do static typing in this language?
Most of my code is structured as a Java code.
I’ve made everything except the class loader a class.
And as in java we use imports to import other classes, I have done the same except of import keyword, I use require function to import other classes defined in other module scripts.
If I were to follow this design pattern, how would I do it so that I dont get the cyclic dependency error?
Is my only way is to use the class loader to import other classes?
Also, When we use Instance.new() function, it shows the list of available instances’ string name in auto complete, I believe it is because that the Instance.new() might be defined as:
function Instance.new(instanceName: "Part" | "MeshPart" | "RemoteEvent" | "BindableEvent" ...)
--code
end
Can I use the same concept to preserve autocomplete/intellisense?
I was trying to experiment and came up with this conclusion to preserve autocomplete/intellisense:
local import = {}
import.NetworkReplicator = nil :: typeof(require(Classes.Networking.NetworkReplicator))
In theory, this should have worked because the require function returns a table of a type that is present in the module script that was passed to it, but still the autocomplete shows NetworkReplicator variable as any.
Is there any reason it didnt work as expected and should I post it into bug reports?
I made it like this so that I could pass the reference of the import table to the class loader and after all the classes have been loaded, class loader then could throw in the classes into that import table so that the code/class/object could use that in its functions.
Luau’s static analysis is trying to follow the rules of on-the-spot cyclic dependency, which under normal circumstances would cause an exception to be thrown, so the type system think its undefined behavior. You are not going to win this fight, you need to just avoid cyclic dependency.
Instead of making two systems co-depend on each other, you should create some middle man layer that connects the two together indirectly. That way it creates a sort of spanning tree branch and respects that constraint.
Creating a middle layer is possible, but will look odd in this OOP coding pattern.
Lets just assume that there are no cyclic dependencies error occurring, I just have a module script called ImportTest and a NetworkReplicator module script/class in this case.
Now, when in ImportTest, I do : local NetworkReplicator = nil :: typeof(require(Classes.Networking.NetworkReplicator)),
the type checker shows NetworkReplicator variable as any, but when the code is: local NetworkReplicator = require(Classes.Networking.NetworkReplicator)
If luau’s type checker does static analysis, i.e. doesnt execute any code, then how it is able to import types from other module script via the require function?
Does Roblox implement some sort of static/dynamic type checker?
The require function has to do some “magic function” hacks under the hood to handle the behavior of the require function. Normally the return type of require is any, but they have specialized code in the static analysis layer that resolves the type of what you are requiring.
The inline require typecast you are trying to do is not influenced by this magic behavior, so it’s falling back to the built-in definition:
This means that the require function has dynamic return type as it holds an exceptional position directly in the type resolver’s code, right?
I was also doing some more experiments, found out that I can do lazy require after all the classes have been loaded by this:
local Classes = game.ReplicatedStorage.Classes
local class = {}
class.__index = class
local anotherClass
function class.import()
anotherClass = require(Classes.AnotherClass)
end
--Rest of the class construction code, new function, return class line etc.
Now, the autocomplete does show anotherClass variable of type AnotherClass or other exported types in this code.
But as soon as this line if found inside the anotherClass: require(Classes.Class) (Class is the module script representing the above class code). No matter if its in a function thats never called, it will make the auto complete show anotherClass variable as any in Class module script. and the TheClass variable also as any in the AnotherClass module script.
Here’s the AnotherClass module script:
local Classes = game.ReplicatedStorage.Classes
local AnotherClass = {}
AnotherClass .__index = AnotherClass
local TheClass
function class.import()
TheClass = require(Classes.Class)
end
--Rest of the class construction code, new function, return class line etc.
Is there specific reason for this behaviour?
Yes, at runtime it doesnt give out Requested module was required recursively error, but still does effect auto complete.
Cyclic dependencies are illegal under the Luau type system. It may work at runtime, but the type system only supports a subset of code that works at runtime, or stated otherwise, not all code that works passes the type checker, and that is by design.