godotengine / godot

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

After parser error, exported members become null (or NodePaths become empty) #94504

Open hsandt opened 3 months ago

hsandt commented 3 months ago

Tested versions

System information

Godot v4.2.1.stable - Ubuntu 22.04.4 LTS 22.04 - X11 - Vulkan (Mobile) - dedicated NVIDIA GeForce GTX 860M (nvidia; 535.161.07) - Intel(R) Core(TM) i7-4710HQ CPU @ 2.50GHz (8 Threads)

Issue description

While I'm working on a script, I generally reach a state where the script becomes invalid, such as messing up with indentation or removing used variables. This causes a temporary Parser Error, which is not a big deal.

However, I found out that in some situations, this will cause a bunch of members on nodes using my script to suddenly become null (I generally use scene instances, if that matters).

Boss members became null

max_horizontal_speed = null
deceleration_time = null
slide_speed = null

I don't know the exact circumstances to reproduce the bug, but since it has happened many times and is the main reason why I need to restart Godot (along with spurious persistent parser errors that stay cached until I restart), and found so similar bug here, I decided to open one. Unfortunately this is hard to reproduce specifically when you want to, on simple code; it just happens if you work on a project long enough, on code that is complex enough... But I hope that having an issue will allow developers to start can discussing this and share MWE.

The possible actions needed to cause the bug are:

Variant: instead of null, the members containing NodePath become all cleared to empty NodePaths

PlayerCharacter after restart and resave gets short NodePath but still empty

attack_input_buffer_timer = NodePath("")
body_collision_shape_standing = NodePath("")
body_collision_shape_crouching = NodePath("")
...

And today I got, for the first time, the very weird super complex serialization made of many empty NodePaths:

level_boss_tiny after parser error gets weird super long serialized empty NodePath

body_collision_shape_standing = Object(CollisionShape2D,"_import_path":NodePath(""),"unique_name_in_owner":false,"process_mode":0,"process_priority":0,"process_physics_priority":0,"process_thread_group":0,"editor_description":"","visible":true,"modulate":Color(1, 1, 1, 1),"self_modulate":Color(1, 1, 1, 1),"show_behind_parent":false,"top_level":false,"light_mask":1,"visibility_layer":1,"z_index":2,"z_as_relative":true,"y_sort_enabled":false,"texture_filter":0,"texture_repeat":0,"material":null,"use_parent_material":false,"position":Vector2(0, 0.5),"rotation":0.0,"scale":Vector2(1, 1),"skew":0.0,"shape":null,"disabled":false,"one_way_collision":false,"one_way_collision_margin":1.0,"script":null)

body_collision_shape_crouching = Object(CollisionShape2D,"_import_path":NodePath(""),"unique_name_in_owner":false,"process_mode":0,"process_priority":0,"process_physics_priority":0,"process_thread_group":0,"editor_description":"","visible":true,"modulate":Color(1, 1, 1, 1),"self_modulate":Color(1, 1, 1, 1),"show_behind_parent":false,"top_level":false,"light_mask":1,"visibility_layer":1,"z_index":2,"z_as_relative":true,"y_sort_enabled":false,"texture_filter":0,"texture_repeat":0,"material":null,"use_parent_material":false,"position":Vector2(0, 2),"rotation":0.0,"scale":Vector2(1, 1),"skew":0.0,"shape":null,"disabled":false,"one_way_collision":false,"one_way_collision_margin":1.0,"debug_color":Color(0.792157, 0.427451, 0, 0.419608),"script":null)

animation_controller = Object(Node,"_import_path":NodePath(""),"unique_name_in_owner":false,"process_mode":0,"process_priority":1,"process_physics_priority":0,"process_thread_group":0,"editor_description":"","script":Resource("res://Scripts/Animation/AnimationControllerPlayerCharacter.gd"),"player_character":Object(CharacterBody2D,"_import_path":NodePath(""),"unique_name_in_owner":false,"process_mode":0,"process_priority":0,"process_physics_priority":0,"process_thread_group":0,"editor_description":"","visible":true,"modulate":Color(1, 1, 1, 1),"self_modulate":Color(1, 1, 1, 1),"show_behind_parent":false,"top_level":false,"light_mask":1,"visibility_layer":1,"z_index":0,"z_as_relative":true,"y_sort_enabled":false,"texture_filter":0,"texture_repeat":0,"material":null,"use_parent_material":false,"position":Vector2(99, 124),"rotation":0.0,"scale":Vector2(1, 1),"skew":0.0,"disable_mode":0,"collision_layer":256,"collision_mask":31,"collision_priority":1.0,"input_pickable":false,"motion_mode":0,"up_direction":Vector2(0, -1),"velocity":Vector2(0, 0),"slide_on_ceiling":true,"max_slides":4,"wall_min_slide_angle":0.261799,"floor_stop_on_slope":true,"floor_constant_speed":true,"floor_block_on_wall":true,"floor_max_angle":0.785398,"floor_snap_length":1.0,"platform_on_leave":0,"platform_floor_layers":4294967295,"platform_wall_layers":0,"safe_margin":0.08,"script":Resource("res://Scripts/Character/PlayerCharacter.gd"),"attack_input_buffer_timer":Object(Timer,"_import_path":NodePath(""),"unique_name_in_owner":false,"process_mode":0,"process_priority":0,"process_physics_priority":0,"process_thread_group":0,"editor_description":"","process_callback":0,"wait_time":0.1,"one_shot":true,"autostart":false,"script":null)
,"body_collision_shape_standing":Object(CollisionShape2D,"_import_path":NodePath(""),"unique_name_in_owner":false,"process_mode":0,"process_priority":0,"process_physics_priority":0,"process_thread_group":0,"editor_description":"","visible":true,"modulate":Color(1, 1, 1, 1),"self_modulate":Color(1, 1, 1, 1),"show_behind_parent":false,"top_level":false,"light_mask":1,"visibility_layer":1,"z_index":2,"z_as_relative":true,"y_sort_enabled":false,"texture_filter":0,"texture_repeat":0,"material":null,"use_parent_material":false,"position":Vector2(0, 0.5),"rotation":0.0,"scale":Vector2(1, 1),"skew":0.0,"shape":null,"disabled":false,"one_way_collision":false,"one_way_collision_margin":1.0,"script":null)
...

(cool, GitHub recognizes tscn syntax!)

after modifying some field to make the scene dirty and resave, it turned back to just simple empty NodePaths as in the previous screenshot

Steps to reproduce

  1. Create some script hierarchy and scene instances with some of those scripts
  2. Modify script to cause a parser error... exact conditions unknown
  3. Try to play the game anyway
  4. Check the VCS for diff

Minimal reproduction project (MRP)

Call for help!

It's probably easier to repro on a complex, existing project. Unfortunately the project where I just reproduced the bug is private, but if I can repro on a public one I can just share the repo with you and tell you what I did last. I invite other developers to do the same.

AThousandShips commented 3 months ago

I'm not sure how this would be reliably solved as the script stops having these exports, and how to handle the values being kept around until the errors are resolved

Also please just confirm with 4.2.2 (and please try making reports with a supported version)

4X3L82 commented 3 months ago

When I just started with Godot, I had issues where I had cyclical references. These were my own fault/mistakes, but Godot didn't tell/warn me about this so I had a hard time tracking/understanding odd behavior: Files were corrupting themselves. I would have a working setup, code more, save it, play, get errors, and suddenly files were (half) empty and corrupted. Sounds familiar? ;)

So two things:

hsandt commented 3 months ago

OK, considering I don't have an exact repro and can only check it over a long period of time on a real project, I cannot confirm for v4.3.rc2 yet, so I will wait to upgrade my project to v4.3 and then I'll be able to tell if it happens again.

Yes, circular deps are one possible cause to make parsing fail, but I think I also had this issue with other types of things breaking parsing temporarily.