I am a bit confused so I could ask a lot of questions, but to keep things clear:
My main problem is that I am unable to predict if I am creating an unsupported cycle dependency.
more details
I am wondering the following:
Unsupported cyclic dependencies are different from the references related to the ressource loader. But I don’t understand the difference.
if I use the name of a class (I have created with class_name) inside another script, will this script depend on the first one ? or is it noly with preload that scripts depend on other ressources ?
I noticed my lack of knowledege by chating in the issue tracker, so for more details, check the link above.
I skimmed through your Github issue, so I’m not saying this is actually your issue, but covering the part you are asking about cyclic dependencies. This was already mentioned in the issue you linked, but just to reiterate, the cyclic dependency you are seeing is caused by Scenes.
Scripts and Classes shouldn’t cause any circular dependency.
So for your two questions, the answer is yes, you can reference scripts circularly (even with preloads), and the reason the resource loader works is because it’s a script/class, and not a scene.
EDIT: I removed the script block because it was wrong and probably would cause more confusion. My point is well explained in the message bellow (the difference between compile time and runtime time loads)
That’s not really a dependency cycle. A dependency cycle is a situation where file A depends on file B but file B depends on file A. The example above is “file A depends on file A”, which is usually not an issue.
If file A has a hard load-time dependency on file B, then file B must finish loading before file A finishes loading. If file A has a hard load-time dependency on file B and file B has a hard load-time dependency on file B, then both files must finish loading before the other, which clearly cannot happen. Examples of hard load-time dependencies include:
File A is a script that preloads resource B.
File A is script that uses the class_name of script B.
File A is a scene with script B attached to any of its nodes.
File A is a scene that includes an instance of scene B.
In the specific case of “Script A preloads resource B”, you can replace the preload with a regular load, which turns the dependency from a load-time dependency to a runtime dependency, thereby breaking the cycle.
The editor doesn’t stop me from doing it. That’s why I struggle to know if I am doing something unsupported. I created scenes that preload themself in many projects and everything worked like a charm (without even printing errors in most of cases). To get the errors mentioned in the github issue, I had to reload the project and type inside an unrelated script. But even with these errors, the prooject run correctly. In other words, circular dependencies can cause problems later, but the editor accept them.
If the editor was stoping me from doing it as soon as I try, I would be able to see by myself that something is unsupported. But as it doesn’t, I want to predict by myself what is unsupported.
Thank’s ! It is always better to know the right words.
Yes, that’s cyclic dependency. It works because Godot is clever about loading scripts and breaks them into smaller pieces that can be processed individually. However, there are limits to this. This works:
You are right. I tried simplifying it using one class and missed the point a bit. Besides the extra hidden relationship of actual circular dependencies, the underlying issue is the same I described though, it causes the compiler to enter a loop trying to parse the file.
This question is not answered yet. But let me try to answer by myself thanks to what you already explained:
example situation
Let’s say I have a scene a.tsn which has an unique node with a script a.gd. a.gd would be:
class_name A extends Node
@export var infos: Infos
Info would be a custom ressource. info.gd:
class_name Infos extends Ressource
var a_scn = preload("res://a.tsn") # preloads the a.tsn scene
This way, I create cyclic dependencies in the same way of my diagram (a part of the cycle is between scripts).
the question
I want to know if these circular dependencies are supported.
my guess
I guess this is not supported because I don’t see any correct order to finish loading a.tsn.
If I understand correctly, to finish loading a scene, all its scripts have to be parsed entirely as well as there dependencies. So in this case, to load a.tsc, we need to parse a.gd and infos.gd. But infos.gd cannot be parsed entirely because it preloads the scene which is waiting for it to be loaded.
Breaking scripts into parts, doesn’t help here because a.gd depends on the whole info.gd (I guess).
But wait ! This is only a guess, let me know if I am totally or partially wrong… (given the number of suppositions I had to make, this is unlikely to be 100% true)
Yes, preload produces the cyclic dependency at a compile-time (wrong word for it, just trying to describe) level; the script cannot even begin to be understood until the preload is finished loading, and it ends up including itself which is not understood yet.
Generally speaking do not preload, you could use it for small self-contained resources, but it’s almost always better to load later.
Yes, load would not induce a compile-time cyclic dependency. Your class “Infos” would be well-defined.
load is different from preload, when used in the class body load is called when the class is instantiated/.new() while preload is called before the game is run.
I think I can deduce even more things from it.
Are the following following deductions correct ?
As load is a function, it loads the given ressource when it is called (when its line is executed).In the snippet below this would mean that scene_one.tscn is loaded when creating an instance of MyNode. It would also mean that scene_two.tscnis loaded only when the do_something()function is called.
As preload is a keyword, it can load its given ressource before its line is executed.This would mean that scene_three is loaded before the game is run.
Lines of a class which are not inside a function are executed when the class is instantiated.This would mean that variables a, b and c are initialised when creating an instance of the class.
class_name MyNode extends Node
var a := 10
@export var b : PackedScene
var c = load("res://scene_one.tsn")
func do_something() -> void:
var d = load("res://scene_two.tscn")
var e = preload("res://scene_three.tscn")