Possible parse/compile order error
Tested versions
- Reproductible in 4.2.1 and 4.2.2 versions, haven't tested dev ones yet
System information
Windows 11 godot v4.2.2 Mobile
Issue description
In all languages, following class loading order, when it isn't cyclic, will first load classes that latter ones depends upon
It seems that during @static_initialization the compiling/parsing order of scripts are somehow dependent on the content of functions, including non-static functions, meaning that when one writes a primary class and has a second class in another file, one using that Primary class to generate a static list/info base, but that the parent uses that second class in a non-static function for whatever means, Godot will load the second class's static values first, even if they are meant to be of the type of the primary class, foregoing the fact the second class requires the primary class's static variables to be initialized first, returning errors from null values to non-existing fields.
Here is a comprehensive example:
A primary class, Colour, which, to get its ID, requires a secondary class that act as a database, (has builtin values and automatically fetches other files to add to its Colour list and ID-Colour map)
class_name Colour
var name : String
var formattingNames : PackedStringArray
var colour : Color
var lightColour : Color
var metallic : bool
var tags := []
func _init(colour, name := "", metallic := false, formattingNames := PackedStringArray(), lightColour : Color = colour.lightened(0.2)) -> void:
self.name = name
self.colour = colour
self.lightColour = lightColour
self.metallic = metallic
self.formattingNames = formattingNames
static func base(colour : Color, lighter : Color) -> Colour:
return Colour.new(colour, "", false, [], lighter)
func getId() -> String:
#↓ doesn't cause PresetColour to be loaded first but wants to be avoided for good measure
#return load("res://src/utils/colours/PresetColour.gd").colourToIdMap.get(colour, colour.to_html())
return PresetColour.colourToIdMap.get(colour, colour.to_html())
#↑ causes PresetColour to be loaded first
[Other constructors and functions...]
static var WHITE := base(Color.hex(0xFFFFFFFF), Color.hex(0x636363FF))
static var SILVER := base(Color.hex(0xF3F3F3FF), Color.hex(0x636363FF))
static var PLATINUM := base(Color.hex(0xE4E5E2FF), Color.hex(0xE4E5E2FF))
[other static values...]
The secondary class, who acts as a database and external data grabber
class_name PresetColour
static var BASE_WHITE : Colour = load("res://src/vanilla/colours/PresetColours/base_white.gd").new()
static var BASE_GREY_LIGHT : Colour = load("res://src/vanilla/colours/presetcolours/base_grey_light.gd").new()
static var allPresetColours := []
static var colourToIdMap := {}
static var idToColorMap = {}
# External Colours
static func _static_init() -> void:
for c : String in ModLoader.moddedAssets["colours"]:
var id = c.get_file().get_basename().to_upper()
match c.get_extension():
"gd":
var color = load(c).new()
allPresetColours.append(color)
colourToIdMap[color] = id
idToColorMap[id] = color
"xml":
var color = Colour.fromXML(c)
allPresetColours.append(color)
colourToIdMap[color] = id
idToColorMap[id] = color
[static field getter to add hardcoded ones to the mist and maps...]
For clarity, here is what a file like BASE_WHITE recieves as script to create a color (for moddability reasons, that structure allows one to override whatever functions Color has access to for itself.
extends Colour
func _init():
super._init(WHITE.colour, #if PresetColour is done first, WHITE doesn't exist, causing errors.
"white", false, ["white"])
Unless i'm mistaken, getId() shouldn't be initialized before other static variables given it can be accessed only when a Color instance has been created and has its function called.
Yet, it is being taken in consideration for compilation and @static_initialization order
Steps to reproduce
The Explanation contains the code;
Open project Make sure there are breakpoints at: -PresetColour.gd: line 3 -Colour.gd: line 28 -(optional) DummyScriptLoader: line 5 launch
Breakpoint at PresetColour.gd will be reached first, because of return PresetColour.colourToIdMap.get(colour, colour.to_html())
at Colour.gd line 25, you can comment/uncomment it with the previous line to test with load() instead to see it does dissapear.
Minimal reproduction project (MRP)
May be related:
- #81250
- #82141
- #godotengine/godot-proposals#7716
May be related:
Sadly, no. in short, Colour.gd does not require PresetColour to be initialized at any time during static variable initialization. Yet, the compiler, seeing that it references another class in a non-static function, thinks it does PresetColour, on the other hand, requires Colour.gd's static variables to be initialized first but yet it is initialized first, throwing errors because its variables depends on Colour.gd
Although i do not get how the compiler's source code fully works, i can see that during @static_initialization, the code treats static variables by skipping variable members that aren't static, but it doesn't seem like it does the same for functions, and that may be the root of the cause