Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class "XXX" hides global script class on FileAccess.get_var() #68666

Closed
dave2 opened this issue Nov 14, 2022 · 22 comments · Fixed by #90693
Closed

Class "XXX" hides global script class on FileAccess.get_var() #68666

dave2 opened this issue Nov 14, 2022 · 22 comments · Fixed by #90693

Comments

@dave2
Copy link

dave2 commented Nov 14, 2022


Bugsquad note: This issue has been confirmed several times already. No need to confirm it further.


Godot version

v4.0.beta4.official.e6751549c

System information

Ubuntu 22.04

Issue description

GDscript produces an error with no backtrace: "Class 'XXX' hides global script class'.

I've narrowed down an error I started getting when loading saved objects from a file down to the call to FileAccess.get_var(). This occurs when I have a class instance written out with FileAccess.store_var(true) (which works), and doesn't appear to occur for any other reason.

I can just serialise the object myself (and probably should) but this error is definitely harder to track down with no backtrace.

Steps to reproduce

Just run the attached minimum project.

Minimal reproduction project

storavar_bug.zip

@Calinou
Copy link
Member

Calinou commented Nov 14, 2022

Related to #52558 and #65393.

@kleonc
Copy link
Member

kleonc commented Nov 28, 2022

Seems like the cause is:

So when an Object with a GDScript script attached is being saved, its script is saved as a pure data: the whole source code is written directly, no reference (path) to the resource file which it's loaded from is saved. Thus when the loading happens during FileAccess.get_var() (decode_variant function) a new GDScript resource is being created and the saved source code is set to it. At this point the script is reloading itself which leads to a parser error as the global class name defined in the script (with class_name) is already registered (by the original script file, who's source code was just duplicated).


I guess it would make sense to marshall Resources as path-only instead? (and let the ResourceLoader do its job)

@kleonc
Copy link
Member

kleonc commented Dec 26, 2022

The same happens when callling Resource.duplicate(true) (#70570).

@voylin
Copy link
Contributor

voylin commented Jan 10, 2023

Has anybody been working on this? Else I would like to take a look and see if I can fix this as it's a big inconvenience for my current game project.

@aaronfranke
Copy link
Member

Please re-test in the latest master or a recent beta. PR #71142 has likely fixed this issue.

@Eliwi
Copy link

Eliwi commented Feb 17, 2023

Nope, I still get this error (I'm on RC 2)

@jsonify
Copy link

jsonify commented Feb 24, 2023

I just got the same error on RC 4.

@Vennnot
Copy link

Vennnot commented Feb 26, 2023

Getting this error as well currently. Cannot continue work on my project

@gaintap
Copy link

gaintap commented Apr 25, 2023

Still getting this error in 4.0.2, posting hacky workaround. I was able to bypass the error by making a new dummy script and class name that extends the problem class and loading objects with the dummy script attached instead.

Example script: attack_resource_import_only.gd

# attack_resource_import_only.gd

extends AttackResource 
class_name = AttackResourceImportOnly

func _ready():
     pass

My use case is loading a resource that contains an array of resources (a weapon resource with an array of "available attacks" resources). Nesting the attack resource within the weapon resource triggered the error but it's working by using the dummy import only resource. I can use the autocompletion of the main class (AttackResource), so not using the dummy class anywhere other than assigning it in the inspector.

@muhuk
Copy link

muhuk commented Jul 11, 2023

This is still happening in newly released version 4.1.

@DispairingGoose
Copy link

Also happening in 4.1.1

@DispairingGoose
Copy link

DispairingGoose commented Jul 21, 2023

Has anybody been able to come up with any work arounds? I've tried using the var_to_bytes and bytes_to_var methods, however these produce EncodedObjectAsIDs instead of instances of resource classes

Edit: Using var_to_str and str_to_var appears to be working with no harmful effects

Edit 2: Some sample code:

const PATH: String = "res://some_arbitrary_path.txt"


var my_object: CustomResource


func save():
	var file = FileAccess.open(PATH, FileAccess.WRITE)
	file.store_var(var_to_str(my_object))
	file.close()


func load():
	var file = FileAccess.open(PATH, FileAccess.READ)
	my_object = str_to_var(file.get_var())
	file.close()

@ForestGameDev
Copy link

ForestGameDev commented Oct 27, 2023

I wrote this code to convert objects to dictionaries automatically and store them using store_var (with full_objects = false), then read them back from dictionary to object. It's kind of reinventing the wheel, but may help someone until this is fixed: https://www.reddit.com/r/godot/comments/17hrj2q/sharing_my_save_state_code_not_converting_to_json/

@DispairingGoose do you know if using var_to_str / str_to_var approach will force you to export the game with gd script files not compiled (i.e. as plain text)? This question https://ask.godotengine.org/99313/save-file-breaks-after-game-export made my think it could be the case, even when var_to_str is not mentioned there :), but in my mind it would make sense that is the case.

@DispairingGoose
Copy link

I wrote this code to convert objects to dictionaries automatically and store them using store_var (with full_objects = false), then read them back from dictionary to object. It's kind of reinventing the wheel, but may help someone until this is fixed: https://www.reddit.com/r/godot/comments/17hrj2q/sharing_my_save_state_code_not_converting_to_json/

@DispairingGoose do you know if using var_to_str / str_to_var approach will force you to export the game with gd script files not compiled (i.e. as plain text)? This question https://ask.godotengine.org/99313/save-file-breaks-after-game-export made my think it could be the case, even when var_to_str is not mentioned there :), but in my mind it would make sense that is the case.

It seems to be the case that you don't need to export gdscript files as plain text using the following code:

func save() -> void:
	var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
	file.store_string(var_to_str(self))
	file.close()


static func load() -> PlayerData:
	var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
	var object = str_to_var(file.get_as_text())
	file.close()
	print(object)
	return object

Tested on 4.1.3 - I have attached the project I tested with.
spoonsandforks.zip

@dwalter
Copy link

dwalter commented Feb 22, 2024

I found a temporary work around. Change the class_name like:

e.g.
ObjectSpriteCreator.gd

# was: class_name ObjectSpriteCreator
class_name ObjectSpriteCreatorV2

And when you load (keeping the filename the same wasn't an issue):
MainNode2dScript.gd

const ObjectSpriteCreatorV2 = preload("ObjectSpriteCreator.gd")

Changing the filename didn't have an effect and I tried loading the project with this change and then trying to change it back and loading again to see if the cache clears but that didn't work, so this is my current work-around.

@dwalter
Copy link

dwalter commented Apr 7, 2024

This is still an issue and popped up again for all the new classes/ files I created after doing a linux build...

so I realized that the classes are conflicting the files found in the export filter that I placed in the same directory as my project. Once I moved the export folder out to a parent directory, the issue was gone.

@mournguard
Copy link

Happening to me on v4.3.dev5.official [89f70e9] - Doesn't that basically makes store/get_var kind of useless?

@dalexeev
Copy link
Member

dalexeev commented Apr 15, 2024

I tested this. It looks like after #78219 writing is done correctly:

$ hexdump -C example.bin 
00000000  80 00 00 00 18 00 00 00  0a 00 00 00 52 65 66 43  |............RefC|
00000010  6f 75 6e 74 65 64 00 00  04 00 00 00 06 00 00 00  |ounted..........|
00000020  73 63 72 69 70 74 00 00  04 00 00 00 15 00 00 00  |script..........|
00000030  72 65 73 3a 2f 2f 45 78  61 6d 70 6c 65 43 6c 61  |res://ExampleCla|
00000040  73 73 2e 67 64 00 00 00  05 00 00 00 74 68 69 6e  |ss.gd.......thin|
00000050  67 00 00 00 02 00 00 00  99 00 00 00 05 00 00 00  |g...............|
00000060  6f 74 68 65 72 00 00 00  01 00 00 00 01 00 00 00  |other...........|
00000070  07 00 00 00 61 6e 5f 65  6e 75 6d 00 02 00 00 00  |....an_enum.....|
00000080  00 00 00 00                                       |....|
00000084

However, when reading, a crash occurs due to the fact that the script_instance is incorrectly freed when attaching the script to the object:

#0  ScriptInstance::~ScriptInstance (this=0x5555641dff20, __in_chrg=<optimized out>) at core/object/script_instance.cpp:93
#1  0x0000555558e6bfb9 in GDScriptInstance::~GDScriptInstance (this=0x5555641dff20, __in_chrg=<optimized out>) at modules/gdscript/gdscript.cpp:2091
#2  0x000055555d93de60 in memdelete<ScriptInstance> (p_class=0x5555641dff20) at ./core/os/memory.h:116
#3  0x000055555d93b37c in Object::~Object (this=0x55556408ce30, __in_chrg=<optimized out>) at core/object/object.cpp:2058
#4  0x00005555583e960a in RefCounted::~RefCounted (this=0x55556408ce30, __in_chrg=<optimized out>) at ./core/object/ref_counted.h:53
#5  0x0000555558a496b3 in memdelete<RefCounted> (p_class=0x55556408ce30) at ./core/os/memory.h:116
#6  0x000055555d5922e2 in Variant::_clear_internal (this=0x7fffffff8d90) at core/variant/variant.cpp:1374
#7  0x000055555835a0d1 in Variant::clear (this=0x7fffffff8d90) at ./core/variant/variant.h:303
#8  0x000055555835a14a in Variant::~Variant (this=0x7fffffff8d90, __in_chrg=<optimized out>) at ./core/variant/variant.h:794
#9  0x0000555558fea3b8 in GDScriptFunction::call (this=0x55556408f170, p_instance=0x5555641dff20, p_args=0x0, p_argcount=0, r_err=..., p_state=0x0) at modules/gdscript/gdscript_vm.cpp:3577
#10 0x0000555558e5fb05 in GDScript::_super_implicit_constructor (this=0x5555640878b0, p_script=0x5555640878b0, p_instance=0x5555641dff20, r_error=...) at modules/gdscript/gdscript.cpp:138
#11 0x0000555558e5fd38 in GDScript::_create_instance (this=0x5555640878b0, p_args=0x0, p_argcount=0, p_owner=0x55556408ce30, p_is_ref_counted=true, r_error=...) at modules/gdscript/gdscript.cpp:164
#12 0x0000555558e61246 in GDScript::instance_create (this=0x5555640878b0, p_this=0x55556408ce30) at modules/gdscript/gdscript.cpp:413
#13 0x000055555d93246e in Object::set_script (this=0x55556408ce30, p_script=...) at core/object/object.cpp:969
#14 0x000055555d4528ad in decode_variant (r_variant=..., p_buffer=0x5555640617d0 "\030", p_len=128, r_len=0x0, p_allow_objects=true, p_depth=0) at core/io/marshalls.cpp:696
#15 0x000055555d3b8455 in FileAccess::get_var (this=0x555562cebfe0, p_allow_objects=true) at core/io/file_access.cpp:305

We probably need to replace a pointer with Ref<> somewhere.

CC @vnen

@dalexeev
Copy link
Member

Fixed by #78219 and #90693.

@KyleReese
Copy link

I am still seeing this with the latest 4.3 beta when trying to FileAccess.get_var() a dictionary with a custom resource as the value.

@BCG-Jackson
Copy link

not fixed

@dalexeev
Copy link
Member

dalexeev commented Sep 4, 2024

@KyleReese, @BCG-Jackson Sorry for the delay. Can't reproduce this in 4.3 with the MRPs one and two. Note that you must export the variable to be stored in a file. There is currently an inconsistency between text and binary serialization for resources, see #80894 and #80897.

If your issue is still not resolved, please provide reproduction steps, MRPs, and/or other details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.