-
-
Notifications
You must be signed in to change notification settings - Fork 21.6k
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
Fix loading of cyclic scenes from GDScript #92326
Fix loading of cyclic scenes from GDScript #92326
Conversation
e0519cb
to
a1e390b
Compare
Please note: I changed the name of the function from |
// Returns a shallow reference of the loading resource. | ||
// Will fail if the resource is not loading. | ||
Ref<Resource> ResourceLoader::get_loading_resource(const String &p_path, Error *r_error) { | ||
ERR_FAIL_COND_V(r_error == nullptr, nullptr); |
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.
If passing is required I'd pass a reference instead to make the requirement explicit
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.
Given there's no nuance to report about the error, I think returning a null Ref
is enough, so r_error
can go away.
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.
While we're at it, I'm not very sure about the "shallow" wording. Since this is async, by the time the caller deals with the resource, the completeness in its properties having been set and references to dependencies being filled ranges from none (we could call that "shallow") to full. Also, this wouldn't be thread safe, in the sense that there's no sync point with the potentially arbitrary loading thread, which can lead to the caller not only seeing an incomplete resource, but running into data races or race conditions.
Do you think it would be an option to wait for the full resource to be loaded? That would guarantee safety.
If the caller can be happy with the resource in a shallow state (that is, own properties set) the loader needs to be enhanced to support it properly. However, bear in mind that the caller can't safely access anything that can still change (references to external resources, properties already set but that will change due to one of those "queued update" callbacks running later, etc.).
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.
For GDScript this doesn't matter in most cases. Only if the user preload()
a currently loading resource and do something with the resource at the point. This is probably only possible in a static initializer (explicit or implicit).
If we choose this approach, then we should add a comment that ResourceLoader::get_loading_resource()
is intended for GDScript (due to how it works) and should not be used elsewhere in the engine.
Alternatively, we could make such a "shallow" resource null
until the end of loading. In both cases it will look unexpected to the user. Not sure which option is better, but probably null
option is more explicit.
If the required non-GDScript resource is currently being loaded, then we should not call
ResourceLoader::load()
. But we need get it sometime, and its loading will complete later than the script. What could be bad if the script executes a code before that, for instance the static initializer.Here we have two options:
- Add a hook to
ResourceLoader
that allow us to get the resource when it is fully loaded. Until than it isnull
in the script.- Add a posibility to
ResourceLoader
to get a shallow resource, i.e. a currently loading resource, that is not fully initialized yet.
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.
Adding a thought here: I think this only needs to be aware of other loads happening on the same thread (in other words, in the same stack). In that case, ResourceLoader
could have a different function for that case explicitly that wouldn't involve any interaction with other threads. I could implement it if you think that'd do the trick.
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.
Given there's no nuance to report about the error, I think returning a null
Ref
is enough, sor_error
can go away.
It's impossible. 99% of the time, the returned reference will be null. Hence why I force the error return value. We can't differentiate a successful and a failing request only by the returned reference, as both will be null.
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.
If we choose this approach, then we should add a comment that
ResourceLoader::get_loading_resource()
is intended for GDScript (due to how it works) and should not be used elsewhere in the engine.
I don't think it's a good idea. Like, I think we could push this technique to support cyclic loading elsewhere too (cyclic node scenes references?). But I agree that this function should have a warning or something like that.
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.
Adding a thought here: I think this only needs to be aware of other loads happening on the same thread (in other words, in the same stack). In that case,
ResourceLoader
could have a different function for that case explicitly that wouldn't involve any interaction with other threads. I could implement it if you think that'd do the trick.
Elaborating on that:
Since this PR is about a script that is cyclically referenced, ResourceLoader
already has a data member that fits the bill: load_paths_stack
. That's unique per thread, so checking there is good as it keeps the concern within the dependency chain of the current load chain, deterministically as no threads are involved. However, that vector would need to be modified to it contains the resource references as well.
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.
Since this PR is about a script that is cyclically referenced
This PR is more about anything that is cyclically referenced in the context of script loading.
a308f6d
to
71fda08
Compare
CC @rune-scape as you've been poking at loosely related issues I think, and we could use more eyes on this important fix for release blocking issues. |
71fda08
to
d8169fd
Compare
@dalexeev @RandomShaper I removed the cache check. |
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.
Let's YOLO merge and see what happens, this fixes a number of critical regressions.
We can iterate on the design of the core API later, or flag it as something that's a hack only for GDScript and shouldn't be used in other parts of the engine.
Actually I tested again with the Planet Crasher MRP from #90362 (the ZIP archive, not the Git repo which has since changed and no longer seems to exhibit the bug). This PR solves this error:
But introduces these errors instead:
|
Superseded by #93346. Thanks for the great work! |
Makes it possible for ClassA of SceneA to refer to ClassB of SceneB that refers to ClassA back.
This PR creates
Ref<Resource> ResourceLoader::get_loading_resource(const String &p_path, Error *r_error)
. This function returns a (shallow) resource that is currently being loaded. If not, it returnsnullptr
and theFAILED
error. This is important, because most of the time, the returned reference will be not valid, but will still point later to the loaded resource.This allows
GDScriptAnalyzer
to "load" aPackedScene
when analyzing a script file even if the scene is currently loading, preventing a cyclic load error.Fixes #70985
Fixes #90362
Fixes #90954