godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.73k stars 21.12k forks source link

Custom resource silently fails to preload in complicated cyclic dependency chain #98551

Open tracefree opened 5 days ago

tracefree commented 5 days ago

Tested versions

System information

Godot v4.4.dev3 - Arch Linux # 1 SMP PREEMPT_DYNAMIC Thu, 12 Sep 2024 17:21:02 +0000 on Wayland - Wayland display driver, Single-window, 2 monitors - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 1080 Ti (nvidia; 560.35.03) - AMD Ryzen 5 2600X Six-Core Processor (12 threads)

Issue description

I encountered a highly convoluted cyclic dependency issue involving preloading and autoloading scenes with scripts. The MRP attached and reproduction steps below describe it in detail. It may seem highly esoteric but I ran into this naturally in my own project where it was extremely difficult to track down the problem and boil it down to these steps (in reality my dependency chain was much, much longer). At runtime, a resource file I was loading with preload was just an empty Resource object without any of the properties of the custom class it should have had, and there hadn't been any errors or warnings until I tried to access those missing properties (where the game crashed).

Steps to reproduce

As far as I can tell this is the minimal setup to produce the error. Change any of the preload statements to load or use a script instead of a scene for the autoload, or remove any of the references to another class in the scripts, and the error will go away.

Minimal reproduction project (MRP)

preload_mrp.zip

tracefree commented 5 days ago

Tried digging around a bit and found that during ResourceLoaderText::load(), which gets called when loading item.tres with preload, the GDScript object for Item gets loaded and assigned, but is_valid returns false. When I remove the reference to Item in the autoload, the scripts get loaded in a different order and is_valid returns true right before it's assigned to the script and everything works fine.

What can be done about this? Can a resource with an invalid script be updated later once the script becomes valid? Or must this scenario not happen in the first place i.e. measures need to be taken to prevent the script from being invalid at this stage?

tracefree commented 4 days ago

Found another, possibly related issue: When I remove the autoload from the MRP the problem I described goes away (item.tres loads successfully) but I then get this error:

E 0:00:00:0479   _printerr: res://model.tscn:6 - Parse Error: [ext_resource] referenced non-existent resource at: res://main.gd
  <C++ Source>   scene/resources/resource_format_text.cpp:41 @ _printerr()

model.tscn is what I called the scene created in the third step of the reproduction steps described above. Interestingly, this error does not appear when the autoload is present. If this turns out to be a separate bug I can open a new issue for this.

EDIT: Sorry nevermind, I messed something up and attached the a wrong script to model.tscn. The original issue still stands, just ignore this comment

donn-xx commented 1 day ago

Just gave this a try. Can confirm it freezes Godot v4.3.stable.official [77dcf97d8]

Noticed this, in main.gd, comments added:

func _ready() -> void:
    var item: Item = preload("res://item.tres")
    # 1. Disable the autoload in settings->global
    # 2. Run
    # Invalid access to property or key 'name' on a base object of type 'Resource'.
    # So, the resource is not yet instantiated.
    print(item.name)

(BTW, with autoload.gd enabled in settings, the game freezes.)

tracefree commented 1 day ago

Thanks for confirming! By the way just for completeness, the resource technically is (partially) instantiated, but it's an empty Resource object that doesn't have the custom class attached. print(item) and print(item.resource_path) should work for example.

Interesting about the freeze, I hadn't noticed that. Will investigate later.