godotengine / godot

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

Nonexistent resource error when trying to use change_scene_to_packed() to change the scene to a PackedScene via an exported variable when the target scene also has a reference to the first scene #77090

Open robobun7 opened 1 year ago

robobun7 commented 1 year ago

Godot version

4.0.2

System information

Windows 10

Issue description

I created a button that, when pressed, called change_scene_to_packed() with a PackedScene variable as the target. The variable is exported into the editor, where I can then drag and drop a scene into it for the button to change the scene to.

This works fine, the button changes the scene to the target scene. If the target scene also has another instance of the button in it linking to a third scene, that also works fine.

However, if one of the scenes links back to an earlier scene, it crashes and only produces an error in the console, not in the editor itself.

ERROR: res://scene2.tscn:4 - Parse Error: [ext_resource] referenced nonexistent resource at: res://scene1.tscn
   at: load (scene/resources/resource_format_text.cpp:490)
ERROR: Failed loading resource: res://scene2.tscn. Make sure resources have been imported by opening the project in the editor at least once.
   at: (core/io/resource_loader.cpp:222)
ERROR: res://scene1.tscn:4 - Parse Error: [ext_resource] referenced nonexistent resource at: res://scene2.tscn
   at: load (scene/resources/resource_format_text.cpp:490)
ERROR: Failed loading resource: res://scene1.tscn. Make sure resources have been imported by opening the project in the editor at least once.
   at: (core/io/resource_loader.cpp:222)
ERROR: Failed loading scene: res://scene1.tscn
   at: (main/main.cpp:2965)
ERROR: 2 RID allocations of type 'N10RendererRD12LightStorage11ShadowAtlasE' were leaked at exit.
ERROR: 1 RID allocations of type 'N10RendererRD12LightStorage15ReflectionAtlasE' were leaked at exit.
ERROR: 1 RID allocations of type 'N10RendererRD14TextureStorage12RenderTargetE' were leaked at exit.
ERROR: 2 RID allocations of type 'N10RendererRD14TextureStorage7TextureE' were leaked at exit.
ERROR: 1 RID allocations of type 'N18RendererCanvasCull6CanvasE' were leaked at exit.
ERROR: 1 RID allocations of type 'N16RendererViewport8ViewportE' were leaked at exit.
ERROR: 1 RID allocations of type 'N17RendererSceneCull8ScenarioE' were leaked at exit.
WARNING: 4 RIDs of type "Texture" were leaked.
     at: finalize (drivers/vulkan/rendering_device_vulkan.cpp:9301)
WARNING: ObjectDB instances leaked at exit (run with --verbose for details).
     at: cleanup (core/object/object.cpp:1982)

Here's the error when I have scene1 link to scene2, scene2 link to scene3, and scene3 link back to scene1:

ERROR: res://scene 3.tscn:4 - Parse Error: [ext_resource] referenced nonexistent resource at: res://scene1.tscn
   at: load (scene/resources/resource_format_text.cpp:490)
ERROR: Failed loading resource: res://scene 3.tscn. Make sure resources have been imported by opening the project in the editor at least once.
   at: (core/io/resource_loader.cpp:222)
ERROR: res://scene2.tscn:4 - Parse Error: [ext_resource] referenced nonexistent resource at: res://scene 3.tscn
   at: load (scene/resources/resource_format_text.cpp:490)
ERROR: Failed loading resource: res://scene2.tscn. Make sure resources have been imported by opening the project in the editor at least once.
   at: (core/io/resource_loader.cpp:222)
ERROR: res://scene1.tscn:4 - Parse Error: [ext_resource] referenced nonexistent resource at: res://scene2.tscn
   at: load (scene/resources/resource_format_text.cpp:490)
ERROR: Failed loading resource: res://scene1.tscn. Make sure resources have been imported by opening the project in the editor at least once.
   at: (core/io/resource_loader.cpp:222)
ERROR: Failed loading scene: res://scene1.tscn
   at: (main/main.cpp:2965)
ERROR: 2 RID allocations of type 'N10RendererRD12LightStorage11ShadowAtlasE' were leaked at exit.
ERROR: 1 RID allocations of type 'N10RendererRD12LightStorage15ReflectionAtlasE' were leaked at exit.
ERROR: 1 RID allocations of type 'N10RendererRD14TextureStorage12RenderTargetE' were leaked at exit.
ERROR: 2 RID allocations of type 'N10RendererRD14TextureStorage7TextureE' were leaked at exit.
ERROR: 1 RID allocations of type 'N18RendererCanvasCull6CanvasE' were leaked at exit.
ERROR: 1 RID allocations of type 'N16RendererViewport8ViewportE' were leaked at exit.
ERROR: 1 RID allocations of type 'N17RendererSceneCull8Sce

When I tried saving the project and reloading it, I got a "Load Error" dialog with an empty message box and this error in both the console and the debugger:

ERROR: res://scene2.tscn:20 - Parse Error: [ext_resource] referenced non-loaded resource at: res://scene1.tscn
   at: _parse_ext_resource (scene/resources/resource_format_text.cpp:174)

The button in the last scene is also stripped of its target scene. This happens whether or not I run the project after assigning the variables in the editor.

If I use change_scene_to_file() and use a file path string instead of a PackedScene variable, the scene loop works fine. This only happens when using change_scene_to_packed() and a PackedScene variable.

Steps to reproduce

  1. Create a button with the following GDScript code, with _on_pressed() connected to the pressed() signal node:
extends Button

@export var targetscene: PackedScene

# Called when the node enters the scene tree for the first time.
func _ready():
    pass # Replace with function body.

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    pass

func _on_pressed():
    get_tree().change_scene_to_packed(targetscene)
  1. Create a series of at least two scenes, each containing an instance of the button that targets the next scene, and the final scene containing an instance of the button that targets the first scene.
  2. Run the project with the first scene as the starting scene.
  3. Save and load the project.

The minimal reproduction project contains a version with two scenes. If you use it, make sure you reassign the button's targetscene variable on the second scene to scene1.tscn since Godot empties it upon loading.

Minimal reproduction project

repro test both scenes.zip

Zireael07 commented 1 year ago

IIRC cyclic scene references are NOT a supported case

KoBeWi commented 1 year ago

Maybe we should improve the error message. We had many such reports.

nlupugla commented 1 year ago

I just spent hours debugging a problem that turned out to be this issue. It would be great if the error message stated where the script has been preloaded so it's easier to debug this kind of problem.

Knight-XAura commented 1 year ago

I just ran into this same issue, but it seems that the affected files become permanently broken. We need a way for a warning or error to happen, but be able to fix it. Someone does this by accident in a large game and it could become disastrous before they truly realize what has happened. Given we should all have our projects backed up, this is still problematic.

nlupugla commented 1 year ago

In my case, I was able to fix it by getting rid of a preload statement in one of my scripts that ended up causing the circular dependency. It was super annoying, but I don't think the files are permanently broken

Knight-XAura commented 1 year ago

Yes you are correct. In some cases I later found if I reloaded the engine it fixed it at that point in time.

seanluse41 commented 1 year ago

Godot Engine v4.2.dev3.official [013e8e3af]

I have been scratching my head at the same problem, and have had to manually edit the scene files to un-corrupt them once or twice. After some fiddling, I have found that setting the ResourceLoader.load cache to IGNORE :

var s = ResourceLoader.load(nextScene, "PackedScene",ResourceLoader.CACHE_MODE_IGNORE)

allows me to use one scene such as a "door" to load back and forth between levels.

full switch scene function (accepts a PackedScene.resource_path)

func _deferredSwitchScene(nextScene):
    print(nextScene)
    Signals.loadingStarted.emit()
    currentScene.queue_free()
    var s = ResourceLoader.load(nextScene, "PackedScene",ResourceLoader.CACHE_MODE_IGNORE)
    currentScene = s.instantiate()
    get_tree().root.add_child(currentScene)
    get_tree().current_scene = currentScene
    await get_tree().create_timer(1).timeout
    Signals.loadingFinished.emit()

This does spit out an error everytime the next scene in the circular scene switcher loads:

E 0:00:00:0909   _parse_ext_resource: res://scenes/levels/puzzles/orangeBox/orangeBoxPuzzle.tscn:106 - Parse Error: [ext_resource] referenced non-existent resource at: res://scenes/levels/main.tscn
  <C++ Source>   scene/resources/resource_format_text.cpp:162 @ _parse_ext_resource()

E 0:00:03:0207   sceneSwitcher.gd:16 @ _deferredSwitchScene(): res://scenes/levels/main.tscn:91 - Parse Error: [ext_resource] referenced non-existent resource at: res://scenes/levels/puzzles/orangeBox/orangeBoxPuzzle.tscn
  <C++ Source>   scene/resources/resource_format_text.cpp:162 @ _parse_ext_resource()
  <Stack Trace>  sceneSwitcher.gd:16 @ _deferredSwitchScene()

E 0:00:20:0143   sceneSwitcher.gd:16 @ _deferredSwitchScene(): res://scenes/levels/puzzles/orangeBox/orangeBoxPuzzle.tscn:106 - Parse Error: [ext_resource] referenced non-existent resource at: res://scenes/levels/main.tscn
  <C++ Source>   scene/resources/resource_format_text.cpp:162 @ _parse_ext_resource()
  <Stack Trace>  sceneSwitcher.gd:16 @ _deferredSwitchScene()

and so on. I agree very wholeheartedly that while "circular dependencies" may not be supported, they are a very common landing spot for beginner and advanced game devs, as many game designs have the player switching back and forth between two areas / scenes.

jedomed commented 11 months ago

seanluse41's workaround works until I close and reopen Godot, then my scenes get corrupted and I have to remove all the "ext_resource" lines and set them again in the editor. Is there any way to achieve this that doesn't cause corruption? Using a string path instead of PackedScene for the exported variables solves it, but it means I can't use drag and drop (I have to manually copy the node path) and I don't see the node preview in the editor.

KoBeWi commented 11 months ago

You can use @export_file. The variable will still be a String and you will be able to use drag and drop.

pelcore commented 9 months ago

"Use load() instead of preload()"

Just came across this same issue. As said in another ticket (https://github.com/godotengine/godot/issues/77436) by the team staff: https://github.com/godotengine/godot/issues/77436#issuecomment-1561098873

Also refer to: https://github.com/godotengine/godot/pull/71004 https://github.com/godotengine/godot/issues/70985#issuecomment-1564326877

InkstainTheBat commented 1 month ago

Are there any plans on fixing this yet? If not what are some simple flexible ways to implement scene switching?

KoBeWi commented 1 month ago

97912 will allow to export PackedScenes by path, so you will be able to do change_scene_to_file(exported_scene_path), with exported_scene_path being insusceptible to cyclic dependencies or moving the scene file.

InkstainTheBat commented 1 month ago

I was also able to find that the issue can be partially circumvented (at least in my case) by replacing my @export var... with @export_file var... and adjusting my code a little.