-
-
Notifications
You must be signed in to change notification settings - Fork 21.3k
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
Update GDScript 2.0 engine to use weak references for scripts and packed scenes to enable cyclic references #65752
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
@@ -394,7 +394,19 @@ GDScriptCache::~GDScriptCache() { | |||
packed_scene_cache.clear(); | |||
|
|||
parser_map.clear(); | |||
|
|||
for (KeyValue<String, Ref<GDScript>> &E : shallow_gdscript_cache) { |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
Just fixed it.
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
RefCounted
objects.Standard case
Let's say
Main
loadsA
that loadsB
.Here,
Main
reference count is 1 (referenced by the engine),A
is 1 (referenced byMain
) andB
is 1 (referenced byA
).When the engine unloads,
Main
reference count will be decremented by 1 and will be deleted, because it's reference count will hit 0.A
andB
will also be deleted becauseA
lost his reference fromMain
andB
will lose it's reference fromA
later.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
Main
loadsA
that loadsB
that loads backA
.Here,
Main
reference count is 1 (referenced by the engine),A
is 2 (referenced byMain
andB
) andB
is 1 (referenced byA
).Do you see the issue? When
Main
will be deleted, there will be no way to deleteA
andB
.A
will lose it's reference toMain
and will decrement, but it will not be deleted asA
reference count is now 1.B
is 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.
Main
loadsA
that loadsB
that 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
A
inB
(which would increaseA
to 2), let's use altogether weak references for scripts and scenes.B
will still be able to useA
, butA
reference 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
Main
A
A
B
B
A
With this, we know that
A
andB
cannot be unloaded. BecauseA
andB
are found in other dependencies. But whenMain
will be disposed of, it's only a matter of disposing of every linked dependency.List of changes:
GDScriptCache::remove_script()
.notest.gd
system to bypass testsScriptRef
GDScriptParser
Known 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_name
can 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.