Update GDScript 2.0 engine to use weak references for scripts and packed scenes to enable cyclic references#65752
Conversation
985105d to
79a1203
Compare
27a4429 to
e7a96d3
Compare
4c8a512 to
f5eb5d7
Compare
|
I tested it with my project and got this crash: |
f5eb5d7 to
42787db
Compare
|
@KoBeWi Thanks! This should be fixed by now. |
|
Another crash, this time I didn't get any stack trace, had to use debugger to find it: EDIT: |
44a11de to
582af39
Compare
bbdcb23 to
36a4c42
Compare
|
I opened this in my Godot 4 ported RPG (around 2 years of development on it already) and it gave me no errors either! (apart from regression errors that are already covered by other issues) |
|
for my project any of the errors / crashes I posted earlier in this thread have been resolved as well! I get this one when loading the project in the editor or when starting test play: the file in question appears to be a built-in script attached to the main scene. then, when dragging GD script files that register classes between folders in the FileSystem panel, I get these errros: this can be easily reconstructed.
|
36a4c42 to
936aa21
Compare
|
Hopefully this makes it into Beta 3 keeps fingers crossed |
modules/gdscript/gdscript_cache.cpp
Outdated
There was a problem hiding this comment.
would it make more sense to introduce a method for this that something like clear_cache that unreferences all references and then clears it afterwards? You then can call it on both the shallow_gdscript_cache and full_gdscript_cache
There was a problem hiding this comment.
This code was removed, as this was quite a ugly hack. Now, as references are not leaking, clearing shallow_gdscript_cache and full_gdscript_cache suffice.
265377d to
df5a846
Compare
Add new algorithm for removing scripts based on dependencies Expose GDScriptCache::remove_script() Use .notest.gd system to bypass tests Add ScriptRef Add weakref for GDScriptParser Add global classes support (replaces PR#65664) Add GDScriptResource::get() Add packed scene script support
df5a846 to
032a7d1
Compare
|
Superseded by #67714 |
Superseded by #67714
TL;DR
This PR makes possible to use cyclic references in GDScript 2.0
Summary
This PR makes the GDScript 2.0 engine compatible with weak references of Scripts and GDScripts. Instead of passing the scripts around, which makes the counter up, this PR stores the scripts inside their own weak references. So, the scripts can be used as much as needed without making the counter up. This makes cyclic references possible.
What it tries to solve
The current GDScript engine uses strong references to store the scripts and scenes that it loads. This usually work well in the standard situations, as those scripts and scenes are
RefCountedobjects.Standard case
Let's say
MainloadsAthat loadsB.Here,
Mainreference count is 1 (referenced by the engine),Ais 1 (referenced byMain) andBis 1 (referenced byA).When the engine unloads,
Mainreference count will be decremented by 1 and will be deleted, because it's reference count will hit 0.AandBwill also be deleted becauseAlost his reference fromMainandBwill lose it's reference fromAlater.Current cyclic reference issue
Currently, the engine makes it so that using cyclic references is impossible. Why, you say?
Let's bring back our example, but with a cyclic reference. Let's say
MainloadsAthat loadsBthat loads backA.Here,
Mainreference count is 1 (referenced by the engine),Ais 2 (referenced byMainandB) andBis 1 (referenced byA).Do you see the issue? When
Mainwill be deleted, there will be no way to deleteAandB.Awill lose it's reference toMainand will decrement, but it will not be deleted asAreference count is now 1.Bis not deleted because it didn't lose it's reference toA.So, the answer is: the engine makes cyclic references impossible due to memory leaks concerns. That explains errors thrown by the parser or the analyzer like
Constant value uses script from "res://b.gd" which is loaded but not compiled.Proposed solution
Let's bring back our example.
MainloadsAthat loadsBthat loads backA.What if loading increments a wrapper instead of the script reference? And what if we use a dependency chart to detect when a script needs to be deleted?
Weak references
Instead of using strong references for scripts and scenes, this PR uses weak references. Weak references are referenced values that holds another value. When accessing that later value, it doesn't increase the reference counter, making it so that it does not matter how cyclic references are.
Instead of having a strong reference to
AinB(which would increaseAto 2), let's use altogether weak references for scripts and scenes.Bwill still be able to useA, butAreference count will stay at 1.The engine had to be changed to support values that may be a weak reference or not. Hence the rather heavy PR.
Dependency chart
If we don't use references as the way to dispose of the loaded objects, we need another way to delete them. Here comes the dependency chart.
Take the dependency chart of our example
MainAABBAWith this, we know that
AandBcannot be unloaded. BecauseAandBare found in other dependencies. But whenMainwill be disposed of, it's only a matter of disposing of every linked dependency.List of changes:
GDScriptCache::remove_script().notest.gdsystem to bypass testsScriptRefGDScriptParserKnown issues
Code example
So, this code works with this PR, which returns this error on
master:Constant value uses script from "res://b.gd" which is loaded but not compiled.:Minimal reproduction project:
cyclic3.zip
Fixes
Fixes #21461 - Some usages of
class_namecan produce cyclic errors unexpectedlyFixes #32165 - Cyclic Errors After Autoload of Imported Asset
Fixes #41027 - Cyclic reference error in GDScript 2.0
Fixes #58551 - Can't preload a scene in AutoLoad script when the scene's script references the AutoLoad
Fixes #58181 - GDScript 2.0: Cannot preload cyclic const script references
Fixes #58871 - Cyclic Autoload Singleton Plugin Bug
Fixes #61043 - New cyclic reference error with autoload and preloaded script
Notes
This PR was the follow-up to #65672.
This PR replaces #65664 as it implements global classes too.