Godot Version
v4.5.stable.official [876b29033]
Question / preface
First, I want to apologise for this thread being a pretty broad topic without a super clear and straightforward question. I hope it’s still okay to post a topic like this!
We have now been using Godot for a handful of months and have really liked the engine. However, the one thing that we have had the most problems is circular dependencies.
After adding new functionality, our game can break in seemingly an unrelated place, due to added new dependencies. It doesn’t help that the editor seems to be pretty bad at detecting this. We end up wasting time hunting down the culprit, as Godot shows an error in the last place, instead of in the place where the original ‘cyclic branch’ takes place.
A different loading order can cause the compilation of certain custom classes to fail, resulting in them being treated as their closest native type (such as Node2D, GDScript, or Resource). We discovered that adding standalone references to custom class names can help enforce a more reliable loading order and temporarily bypass the issue. However, this approach is not a stable or a long-term solution.
Dependencies in autoloads seem to trigger this more often, and their compile order is actually not the one defined in the globals list, because dependencies can shift their actual load order. Preloads and static initialisers seem to also take place before the first autoload, so we’ve really struggled on how to fix some of the issues popping up.
Example problematic use case
We allow managers to call functions on scene tree nodes and vice versa. For example, we let a CardManager act on CardUI nodes. But we also allow CardUI nodes to call functions of the CardManager. Cyclic dependencies like these have been working fine so far. But it seems like as soon as static initialisers and/or preloads get involved, they can become a vulnerability.
Signals?
While we do use signals a lot, maybe we should use them even more? One concern about them is, again, debugging. When signals fail, they do it silently, whether it was a missing parameter or simply a numeric value in the wrong type.
Best practices?
My guess is that we’re simply utilising some functionality improperly, or not in the way Godot’s best practices would recommend. Could someone more knowledgeable share best practises on how to avoid them? For example, is there a more direct way to control the order Godot loads scripts and assets to better enforce an order that doesn’t break?

