godot: Some usages of `class_name` can produce cyclic errors unexpectedly


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


Windows 10 64-bit - Godot 3.1 alpha

Edit - Refined issue to focus on cyclic errors.

Certain uses of class_name can produce cyclic errors in situations where there are no cyclic references, and in fact sometimes no other references outside of the single one given.

I’ve noticed this most frequently in tool script when using is to make a class comparison, but it seems to happen at times in normal script situations.

Recreating it in a sample project has eluded me.

godot master_2018-08-27_00-47-02

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 110
  • Comments: 81 (37 by maintainers)

Commits related to this issue

Most upvoted comments

The fix I was working on is still WIP and causing crashes. I’m not confident to merge such a big change it in beta stage, so it’ll likely be done in a 3.2.x release later.

I ask to please not bump issues without new information. We all know this happens, have already plenty of examples, won’t magically go away with more comments.

I’m currently trying to fix this at @JavaryGames, the solution is already underway. Not sure if it’ll make into 3.2 since it’s a big change (had to rewire somewhat how GDScript works internally) and we’re close to release, but maybe we can integrate in 3.2.1 or something. Will definitely be fixed in 4.0.

Hi Devs when this long … long bug will be finally solved?

It will be resolved in 4.0 as part of the GDScript rewrite.

  • This one is probably what makes it hard to repro: I have to be in the middle of editing a file and solving other errors. After I get the cyclic error, I just restart Godot and the error usually goes away if the other script problems (like syntax errors) have been solved.

Not only this^^ but sometimes restarting Godot doesn’t work if you’ve gone in deeper than a couple files. And, occasionally, a restart is not needed if I do dummy edits on the files in cyclic lock, in just the right order. What I am currently having to do while refactoring and breaking several files at once is temporarily downgrade the var and parameter typing to just Object until everything is working again. And, then, put the correct typing back in, and then restart Godot. Working like this is brutal and I have a hard time recommending Godot until it is fixed. Okay, if you don’t need to code much, Godot is great. But, once you start classing stuff and renaming classes, forget about it. Classes don’t “just work” in Godot. They are a constant fight.

So, regardless of whatever parts of this are being fixed in 4.0, it would be nice to at least be able to force a re-analysis of all dependencies in 3.x (or do it more often behind the scenes) so that we don’t have to restart or resort to other trickery all the time just to get the parts that do work… working again.

Sorry I’m not more useful, I don’t have access to Godot right now. But on 3.2.3 I often still get this problem whenever:

  • I statically type everything possible
  • I give class names to everything
  • Classes reference each other (class A has a variable explicitly typed as class B, and vice versa)
  • This one is probably what makes it hard to repro: I have to be in the middle of editing a file and solving other errors. After I get the cyclic error, I just restart Godot and the error usually goes away if the other script problems (like syntax errors) have been solved.

@harraps that will be done (as I said a few times in the thread), but it requires some fundamental changes in how GDScript is wired. I won’t touch this now since we’re close to release and this change will likely break compatibility.

I’m also getting the error shown in the original post (Windows x64 Godot 3.1 alpha):

image

However, it happens in a different context. I have 3 scripts which each have a different class_name, and each of them extend Node2D. The error does not occur unless one of the scripts references it with the is operator. For example if some_var is Incomer. The weird thing though is that if the same script references the other class_name, there’s no problem. And if the other script references the class_name in question, no problem either.

So there’s a ClassA, ClassB, ClassC. If ClassC references ClassA with the is operator, then error at load time. If ClassC references ClassB, no error. If ClassB references ClassA, no error.

None of the scripts are in the same scene, and all of the nodes are siblings (no ancestor relationship).

I’m not sure why it’s giving this error, I’ll have to investigate.

As has been mentioned a few times, this will be solved in 4.0 and no sooner. Not sure when other 4.0 features are marked done…

@vnen Is there branch with fix for this bug, which we can compile and test?

Yes, I have it in my public fork: https://github.com/vnen/godot/commits/gdscript-no-cycles

Not sure if more information at this point helps, but a workaround (that worked for me) is to change the class_name of the problematic class and then change it back. Which makes it seem to be cache-related.

You might get some caching artifacts in the editor since everything is being reloaded all the time as you’re editing. But if you reach this problem it most likely won’t work when running even if you manage to get it error-free in the editor.

I’ve been hitting this one often in 3.1.1, and jumping through hoops to avoid it. Hoping it will be fixed in 3.2!

Just wanted to make a note that I hit the “is” class_name bug today as well.

I have reverted to using object.has_method(“name”) rather than testing if object is className

@Angular-Angel I’ve noticed that restarting Godot resolves the errors. It’s disappointing that I can’t use static typing in Godot with custom classes/types yet without bizarre errors popping up.

Well this should not be closed, no, the sheer amount of duplicate reports definitely show that there is something to fix 😃

I had a similar dependency issue when that prevented me to implement a Builder pattern using a static method:

class_name MyClass

static func build() -> MyClass:
  var new_instance  = MyClass.new()
  ...
  return new_instance

Workaround described here to load class from source file path is nice but require to not change file tree without changing source code.

I use this trick to have current source file path even in static method (in which get_script() is not available):

static func build() :
    var new_instance = load(GodotExt.current_script_path()).new()
    ...
    return new_instance

with

class_name GodotExt

static func current_script_path():
    return get_stack()[1].source    <- return usable script path

This trick to get current class seems to work from anywhere. It is raw and can be used to avoid similar cyclic dependency issue waiting the global dependency fixing.

@Ploppy3 These fixes are due to massive changes that come with the GDScript rewrite and it’s likely not possible to cherry-pick them. You will have to upgrade to 4.0 if you want to have these fixes.

Not sure if more information at this point helps, but a workaround (that worked for me) is to change the class_name of the problematic class and then change it back. Which makes it seem to be cache-related.

It happened to me at a line where I was casting a class member using as right after changing the signature of a method of the class to which the variable was cast to, but before I could fix the actual method. Then after fixing the method, the line kept giving this error, and it only stopped when I finally changed the cast class’ name to something else, and then changed it back.

Hmm. The errors seem to have to do with the order that things are loaded in the editor window? I can get them to go away by closing and reopening files in the order in which they inherit. So, probably some kind of trouble around it not declaring things in some files. 😕

Edit: Actually no, it doesn’t matter what order I open files, or even if I have other files open at all. When I first load one of these files, they don;t show any errors, and won’t until I change them, and then all the errors show up. 😕

@bfishman script classes are not a feature in 3.0.x. That is why it is not working.

In 3.5.1, it seems sometimes is will cause a cycle when as will not:

extends Spatial
class_name Thing

func _ready():
	var t := self as Thing # allowed
	assert(self is Thing) # Parser Error: Using own name in class file is not allowed (creates a cyclic reference)

That seems like a bug, but also a potential workaround (for some use cases) until 4.0? This doesn’t seem to be one of the cases that goes away after a reload.

@MikeSchulze In this case, I feel like it’s partly a design issue: WfcSlot should probably not know about WfcSlotHistory. The save action should probably be on the latter.

(This behavior/bug is annoying, though, don’t get me wrong.)

As I said before, it’s very easy to create cycles with class_name, especially when using is and as. It’s less easy with paths, but you might bump into similar problems too. The problem is GDScript was never wired to work like that, so it depends too much on resource loading. I have some ideas to fix this (in particular just ignoring resource loading and doing shallow parse of dependencies directly), but this requires a structural change of the parser.

Probably it will properly solved in next versions, since it’s not a good time to mess with this. class_name will be a “lesser feature” for now. You can still use it in some cases, but not as extensively as you expect.

@avencherus the “class names” of script classes are global variables that reference the script. So…

class_name CustomType
match get_script():
    CustomType:
        print("I am a CustomType!") # should print

@HorseLuvver that is called an abstract class/interface, and it should work with autocomplete if you create dummy (virtual) empty methods just to declare its fields. These are basic concepts of Object Oriented Programming and are a feature of classes in general programming.

However, this does not cover all cases of circular reference. It does help to some extent and I have been doing so for some time now. It is good, albeit tedius, practice.

This will only be fixed in Godot 4.0?

From the lead maintainers, unlikely to be backported to 3.x, since resources are allocated to 4.0. Anyone is free to open a pull request though.

hi @AliBouarab nice trick but this is only working on debug mode

I have this issue as well here are my two scripts.

class_name Entity extends Area2D

export var resource : Resource

onready var sprite   = get_node("sprite")
onready var collider = get_node("collider")

func _process(delta: float) -> void:
	if sprite   == null: sprite   = get_node("sprite")
	if collider == null: collider = get_node("collider")
	if resource is EntityConfig:
		sprite.frame = wrapf(sprite.frame+resource.gfx_speed, resource.gfx_start, resource.gfx_start+resource.gfx_count)
class_name EntityConfig extends Resource

export var gfx_image   := ""
export var gfx_start   := 0
export var gfx_speed   := 1.0
export var gfx_count   := 0
export var gfx_xoffset := 0.0
export var gfx_yoffset := 0.0
export var gfx_xsize   := 16.0
export var gfx_ysize   := 16.0
export var hit_xoffset := 0.0
export var hit_yoffset := 0.0
export var hit_xsize   := 16.0
export var hit_ysize   := 16.0

func new()->Area2D:
	var entity = Entity.new()
	entity.sprite            = Content.load_texture(gfx_image)
	if entity.sprite != null:
		entity.sprite.frame      = gfx_start
		entity.sprite.position.x = gfx_xoffset
		entity.sprite.position.y = gfx_yoffset
		entity.sprite.hframes    = entity.sprite.texture.get_width () / gfx_xsize
		entity.sprite.vframes    = entity.sprite.texture.get_height() / gfx_ysize
	
	entity.collider.position.x      = hit_xoffset
	entity.collider.position.y      = hit_yoffset
	entity.collider.shape.extents.x = hit_xsize / 2
	entity.collider.shape.extents.y = hit_ysize / 2
	return entity

When this fix will be come in the main line? It is super annoying because i use type save scripts and run very often into this problem. Today i run into not solfable situation.

extends Resource
class_name WfcSlotHistory

func _init(slot:WfcSlot):
	pass
extends Resource
class_name WfcSlot

function saveHistory():
   var history:WfcSlotHistory = WfcSlotHistory.new(self)
   ...

res://assets/wfc/WfcSlotHistory.gd:5 - Parse Error: The class “WfcSlot” couldn’t be fully loaded (script error or cyclic dependency). modules/gdscript/gdscript.cpp:576 - Method failed. Returning: ERR_PARSE_ERROR

Sorry, but I am a bit frustrated too, and I didn’t know about that workaround since you guys commented, I stopped typing my code a while a go, but I still miss it A LOT.

This might be a naive question but shouldn’t we get rid of this non-cyclic restriction. I mean other languages like Java, C#, C++, etc… can deal with types without issues…

I will periodically keep making an effort to tease it out. Wish I could be more help, but I’m imagining these will have to wait until those with deep knowledge of their implementation are working on their own projects and run head first into these problems. I would wager they’d know where to set their sights in that situation.

A quick summary of some of the issues (I’ll get these posted sometime in the coming weeks):

  1. I would get these cyclic errors when doing obj is MyClass and there was no other connection or reference between the two. Though I don’t know if it is scanning the scene tree and counting its children as cyclic references.
  2. I have also run into the inconvenience you’ve described. I wish it wasn’t so, because it works fine with Built-In / native class names in the same types of situations. So you can be writing a whole bunch of code with this end aim in mind thinking all is well, until you begin to add your custom types, and you have to resort to a hack/work-around/older method, and then what was the value of it if not to avoid verbose path names?
  3. I’ve had these things error-free in the editor and executing clean during test runs, but then go on to have errors and failures in the exported game. An example below (though most likely unrelated to this issue) is the class_name usage couldn’t find their icons in the build, but no issues in runs and tests from the editor.

gtc_2018-10-12_13-21-35

Just overall my experiences with class_name has been a nearly daily encounter with a lot of unpredictable, difficult to explain, and hard to reproduce errors. A lot of time is wasted dancing around it and trying to resolve them.

I was really looking forward to them, and they absolutely would make coding certain things a whole lot more compact and readable. For practical time reasons I have to set them aside and opt out of using them in my current project.

Maybe in 4.0. X)

I’m going to revert to the old method of script paths. In doing so it would just be my own preference to use them all the way through to avoid any random class_name surprises, and to keep the code more consistent.

None of the scripts are in the same scene, and all of the nodes are siblings (no ancestor relationship).

This has nothing to do with the nodes in the scene. Cycles happens in the script themselves.

For now it’s quite easy to get cycles when using is and as and if it happens it’s not really a bug, it’s the way it is. I’ll try to improve this but not for 3.1. The original report is a bug because there are no actual cycles in the code.

@willnationsdev Oh neat, I see now. I wasn’t expecting class_name to work like that, or the custom type to be available to the match pattern . Thanks for sharing this tip Will.