Skip to content

Conversation

@HolonProduction
Copy link
Member

@HolonProduction HolonProduction commented Jan 9, 2026

Alternative to #114651

Fixes #114420
Fixes #98141 (tested using the 4.3 MRP by myself. Not the original one.)

Might be related to: #95428 -> No MRP, but my hypothesis is, that script members were cleared from a still referenced script.


#114651 tries to fix the logic which cleans up dependencies. This PR in contrast removes the logic completely.

This means that GDScripts with cyclic dependencies will only be cleaned up at engine shutdown.

The approach is fine, because it's very unlikely for the dependency cleanup logic to run at all, because it is only called from the GDScript destructor. The GDScriptCache holds refs to all GDScripts so this destructor never runs until engine shutdown. The only exception are inner classes but they are referenced by their outer class. So for them to get freed an outer script needs to drop a ref to an inner class. This mostly happens in editor when editing scripts.

More verbose rubberducking

This whole cycle detection is located in GDScript::clear. There are three possible calls to clear

  • during GDScriptLanguage::finalize -> irrelevant, the whole island detection is protected to not run during language shutdown
  • in GDScript::clear -> recursion, skip
  • in ~GDScript

So for this to run the destructor of a GDScript needs to run. GDScript's are ref counted so the ref count of a GDScript needs to go to 0. GDScriptCache::full_gdscript_cache holds a reference to every GDScript.

(only exception are inner classes those are not referenced by the GDScript cache. But they are referenced by their outer GDScripts).

So for the refcount to drop to to 0 one of those things needs to happen:
A. The GDScriptCache drops it reference to a gdscript
B. An outer GDScript drops it reference to an inner gdscript

About A: this is very unlikely. The only easy way is GDScriptCache::remove_script which only gets called from GDScript::clear and some unused method. Some edge cases with take_over_path might also be able to trigger it I guess.

B: This can only really happen if the outer script gets destructed (see A). It also happens when editing scripts in the script editor. And maybe when messing with reload I guess.

And well you can force it with RefCounted::unreference.

All those possibilities seem so far fetched. So why even bother if all we can safe is some memory from the deps of inner classes. Which will be basically nothing because inner scripts will share most of their deps with their outer class anyway.

@HolonProduction HolonProduction requested a review from a team as a code owner January 9, 2026 17:09
@akien-mga akien-mga added this to the 4.6 milestone Jan 9, 2026
@AThousandShips AThousandShips changed the title GDScript: Don't cleanup other scripts GDScript: Don't clean up other scripts Jan 10, 2026
@akien-mga akien-mga requested review from adamscott and vnen January 10, 2026 22:22
@akien-mga akien-mga merged commit 8d69b6b into godotengine:master Jan 12, 2026
20 checks passed
@akien-mga
Copy link
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants