-
-
Notifications
You must be signed in to change notification settings - Fork 21.2k
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
GDScript: Solve _populate_class_members()
cyclic dependency problem
#79205
GDScript: Solve _populate_class_members()
cyclic dependency problem
#79205
Conversation
@@ -787,11 +787,11 @@ Error GDScript::reload(bool p_keep_state) { | |||
err = compiler.compile(&parser, this, p_keep_state); | |||
|
|||
if (err) { | |||
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now we don't get compilation-related warnings in the editor. This line enables them. This does not mean analyzer errors, which presumably there are none of.
Compilation errors, if the analyzer passes, are our problem, not a game developer's, so I understand the desire to hide them. But if the error only occurs when game devs run the game, that just puts a barrier towards bug reporting/fixing.
Currently running into failure of the |
cba736c
to
b8010d4
Compare
This appears to have been caused by the fact that the test runner doesn't use |
ef34710
to
57841be
Compare
57841be
to
7d29ac0
Compare
@@ -2968,7 +2973,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri | |||
GDScriptCache::add_static_script(p_script); | |||
} | |||
|
|||
return GDScriptCache::finish_compiling(main_script->get_path()); | |||
return GDScriptCache::finish_compiling(main_script->path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the issue with testing, which doesn't use ResourceLoader::load()
to load scripts, meaning that Resource::path
, which you get using main_script->get_path()
was empty, so that GDScriptCache::finish_compiling()
was getting an empty string and being a no-op :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work, ocean!
@@ -2591,7 +2591,12 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri | |||
_set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); | |||
return ERR_COMPILATION_FAILED; | |||
} | |||
ERR_FAIL_COND_V(!base->is_valid() && !base->reloading, ERR_BUG); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I'm worried about! I haven't noticed the absence of this causing problems in the small tests I have done, but I'd love to see this merged so we can fix any regressions this might lead to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that the checks above should cause the function to return early in the case of errors so that this isn't necessary. But you never know when things are this subtle :)
@YuriSizov If you could merge this PR, I think it could fix some issues. |
_populate_class_members()
cyclic dependency problem _populate_class_members()
cyclic dependency problem
Sure, let's go ahead with this. Thanks! |
Nice, thank you! Let's keep an eye out for potential regressions :) |
Given the regressions I'd err on the side of caution and skip this from cherry-picking. If there is still desire to bring this to 4.1.x, I'd appreciate a summary of the PRs that need to be picked and the stability of this change. But given that some of the fixes are pretty recent, I'd at least give it more time. |
The bug this fixes can be pretty project-stopping with unclear or impossible workarounds, but it only affects with cyclic dependencies and specific load orders. Unless we see a lot of folks complaining about class members that aren't recognized in 4.1, I think you're right in letting this + the recent fixes stew a little longer in 4.2 :) The current other PRs that continue this effort to fix cyclic dependencies in compilation are #81577 and #81201. If you do consider cherry-picking at some point, let me know and I can check whether other PRs are needed as a follow-up! |
Fixes #78146 and fixes #79153 (which is pretty much a duplicate). The issue occurs because
_populate_class_members(descendant_class)
is getting called during a call for_populate_class_members(ancestor_class)
. Thus class members of ancestors are not available for the descendant class when it is populating its own, resulting theidentifier not found
error of the linked issue when the descendant class gets compiled.Issue 🔥
GDScriptCompiler::_populate_class_members()
sets up the datastructures so that the compiler knows the addresses of member variables of the class being compiled. For this, it needs to know members its base class, so that those are also addressable. In order to obtain those members, it asks for the fully compiled script of the base class usingGDScriptCache::get_full_script()
.However in certain configurations of dependencies, the base class of the current class may depend on a descendant class. Let's say
Daughter(D) extends Mother(M) extends Grandmother(GM)
, as in the linked issue. If you start by compiling (not just analyzing) M, that requires GM's class members, so youget_full_script(GM)
, but ifGM
somehow referencesD
in a way that asks for itsget_full_script(D)
, thenD
will run its_populate_class_members()
withoutM
having hers populated fromGM
.Oof, that's a mouthful.
I don't believe it is possible to replicate the issue within the test framework, as it relies on global class names that exist in different files. Instead, I am attaching 🐛 MRP.zip 🐞, which causes the issue when running the scene, before this PR, but works properly after this PR.
The dependency is as follows:
Daughter extends Mother extends Grandmother
, withGrandmother
referring to (but not extendingDaughter
), andMother
being the first file loaded, in this case by being attached to the main scene node.Solution ⚕️
The solution here was to make it so that
_populate_class_members()
did not requireget_full_script()
, but instead only requires the shallow script (which does resolve any necessary inheritance dependencies), followed by calling its_populate_class_members()
. This no longer triggersget_full_script()
, and so descendant classes don't get compiled before their base classes are done populating their members.Concerns 😨
I'm not super well versed in the meta-organization of the GDScript infrastructure, so I wonder whether this is the best solution. This doesn't so much solve cyclic dependencies as it sidesteps them insofar as
_populate_class_members
is concerned. Maybe that's not so bad, since it's such a specific case?Either way, I'd love to see a refactor of the way scripts are loaded, maybe doing away with
get_shallow/full_script
and usingGDScriptCache/raise_status()
to give us more fine-grained control over what is needed at each point in time, and makingGDScript::reload()
, whichget_full_script()
relies on, a little less monolithic.