@@ -1261,69 +1261,6 @@ GDScript *GDScript::get_root_script() {
12611261 return result;
12621262}
12631263
1264- RBSet<GDScript *> GDScript::get_dependencies () {
1265- RBSet<GDScript *> dependencies;
1266-
1267- _collect_dependencies (dependencies, this );
1268- dependencies.erase (this );
1269-
1270- return dependencies;
1271- }
1272-
1273- HashMap<GDScript *, RBSet<GDScript *>> GDScript::get_all_dependencies () {
1274- HashMap<GDScript *, RBSet<GDScript *>> all_dependencies;
1275-
1276- List<GDScript *> scripts;
1277- {
1278- MutexLock lock (GDScriptLanguage::singleton->mutex );
1279-
1280- SelfList<GDScript> *elem = GDScriptLanguage::singleton->script_list .first ();
1281- while (elem) {
1282- scripts.push_back (elem->self ());
1283- elem = elem->next ();
1284- }
1285- }
1286-
1287- for (GDScript *scr : scripts) {
1288- if (scr == nullptr || scr->destructing ) {
1289- continue ;
1290- }
1291- all_dependencies.insert (scr, scr->get_dependencies ());
1292- }
1293-
1294- return all_dependencies;
1295- }
1296-
1297- RBSet<GDScript *> GDScript::get_must_clear_dependencies () {
1298- RBSet<GDScript *> dependencies = get_dependencies ();
1299- RBSet<GDScript *> must_clear_dependencies;
1300- HashMap<GDScript *, RBSet<GDScript *>> all_dependencies = get_all_dependencies ();
1301-
1302- RBSet<GDScript *> cant_clear;
1303- for (KeyValue<GDScript *, RBSet<GDScript *>> &E : all_dependencies) {
1304- if (dependencies.has (E.key )) {
1305- continue ;
1306- }
1307- for (GDScript *F : E.value ) {
1308- if (dependencies.has (F)) {
1309- cant_clear.insert (F);
1310- }
1311- }
1312- }
1313-
1314- for (GDScript *E : dependencies) {
1315- if (cant_clear.has (E) || ScriptServer::is_global_class (E->get_fully_qualified_name ())) {
1316- continue ;
1317- }
1318- must_clear_dependencies.insert (E);
1319- }
1320-
1321- cant_clear.clear ();
1322- dependencies.clear ();
1323- all_dependencies.clear ();
1324- return must_clear_dependencies;
1325- }
1326-
13271264bool GDScript::has_script_signal (const StringName &p_signal) const {
13281265 if (_signals.has (p_signal)) {
13291266 return true ;
@@ -1370,55 +1307,60 @@ GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) {
13701307 return Object::cast_to<GDScript>(obj);
13711308}
13721309
1373- void GDScript::_collect_function_dependencies (GDScriptFunction *p_func, RBSet <GDScript *> &p_dependencies, const GDScript *p_except ) {
1310+ void GDScript::_collect_function_dependencies (GDScriptFunction *p_func, RBMap <GDScript *, int > &r_dependencies ) {
13741311 if (p_func == nullptr ) {
13751312 return ;
13761313 }
13771314 for (GDScriptFunction *lambda : p_func->lambdas ) {
1378- _collect_function_dependencies (lambda, p_dependencies, p_except );
1315+ _collect_function_dependencies (lambda, r_dependencies );
13791316 }
13801317 for (const Variant &V : p_func->constants ) {
13811318 GDScript *scr = _get_gdscript_from_variant (V);
1382- if (scr != nullptr && scr != p_except ) {
1383- scr-> _collect_dependencies (p_dependencies, p_except) ;
1319+ if (scr != nullptr ) {
1320+ r_dependencies[ scr] += 1 ;
13841321 }
13851322 }
13861323}
13871324
1388- void GDScript::_collect_dependencies (RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
1389- if (p_dependencies.has (this )) {
1390- return ;
1391- }
1392- if (this != p_except) {
1393- p_dependencies.insert (this );
1394- }
1395-
1325+ void GDScript::count_dependencies (RBMap<GDScript *, int > &r_res) {
13961326 for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
1397- _collect_function_dependencies (E.value , p_dependencies, p_except );
1327+ _collect_function_dependencies (E.value , r_res );
13981328 }
13991329
14001330 if (implicit_initializer) {
1401- _collect_function_dependencies (implicit_initializer, p_dependencies, p_except );
1331+ _collect_function_dependencies (implicit_initializer, r_res );
14021332 }
14031333
14041334 if (implicit_ready) {
1405- _collect_function_dependencies (implicit_ready, p_dependencies, p_except );
1335+ _collect_function_dependencies (implicit_ready, r_res );
14061336 }
14071337
14081338 if (static_initializer) {
1409- _collect_function_dependencies (static_initializer, p_dependencies, p_except );
1339+ _collect_function_dependencies (static_initializer, r_res );
14101340 }
14111341
1412- for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
1413- if (E.value != p_except) {
1414- E.value ->_collect_dependencies (p_dependencies, p_except);
1342+ for (KeyValue<StringName, MemberInfo> &E : member_indices) {
1343+ Ref<GDScript> ref = E.value .data_type .script_type_ref ;
1344+ if (ref.is_valid ()) {
1345+ r_res[*ref] += 1 ;
14151346 }
14161347 }
14171348
1349+ for (KeyValue<StringName, MemberInfo> &E : static_variables_indices) {
1350+ Ref<GDScript> ref = E.value .data_type .script_type_ref ;
1351+ if (ref.is_valid ()) {
1352+ r_res[*ref] += 1 ;
1353+ }
1354+ }
1355+
1356+ for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
1357+ r_res[*E.value ] += 1 ;
1358+ }
1359+
14181360 for (const KeyValue<StringName, Variant> &E : constants) {
14191361 GDScript *scr = _get_gdscript_from_variant (E.value );
1420- if (scr != nullptr && scr != p_except ) {
1421- scr-> _collect_dependencies (p_dependencies, p_except) ;
1362+ if (scr != nullptr ) {
1363+ r_res[ scr] += 1 ;
14221364 }
14231365 }
14241366}
@@ -1434,7 +1376,7 @@ GDScript::GDScript() :
14341376 path = vformat (" gdscript://%d.gd" , get_instance_id ());
14351377}
14361378
1437- void GDScript::_save_orphaned_subclasses (ClearData *p_clear_data ) {
1379+ void GDScript::_save_orphaned_subclasses () {
14381380 struct ClassRefWithName {
14391381 ObjectID id;
14401382 String fully_qualified_name;
@@ -1450,17 +1392,8 @@ void GDScript::_save_orphaned_subclasses(ClearData *p_clear_data) {
14501392 }
14511393
14521394 // clear subclasses to allow unused subclasses to be deleted
1453- for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
1454- p_clear_data->scripts .insert (E.value );
1455- }
14561395 subclasses.clear ();
14571396 // subclasses are also held by constants, clear those as well
1458- for (KeyValue<StringName, Variant> &E : constants) {
1459- GDScript *gdscr = _get_gdscript_from_variant (E.value );
1460- if (gdscr != nullptr ) {
1461- p_clear_data->scripts .insert (gdscr);
1462- }
1463- }
14641397 constants.clear ();
14651398
14661399 // keep orphan subclass only for subclasses that are still in use
@@ -1546,6 +1479,83 @@ void GDScript::_recurse_replace_function_ptrs(const HashMap<GDScriptFunction *,
15461479 }
15471480}
15481481
1482+ /* *
1483+ * Implementation of Tarjan's Algorithm to find strongly connected dependency clusters (e.g. cycles): https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
1484+ *
1485+ * The implementation is not generalized and omits part of the algorithm, e.g. safeguards for disconnected graphs.
1486+ */
1487+ class DependencyCyclesTarjanScc {
1488+ struct Data {
1489+ int index;
1490+ int lowlink;
1491+ int scc;
1492+ };
1493+
1494+ RBMap<GDScript *, Data> script_data;
1495+ LocalVector<GDScript *> stack;
1496+ int next_index = 0 ;
1497+ int next_scc_id = 0 ;
1498+
1499+ void _strong_connect (GDScript *p_script) {
1500+ if (!script_data.has (p_script)) {
1501+ script_data.insert (p_script, Data ());
1502+ }
1503+
1504+ {
1505+ Data &data = script_data[p_script];
1506+ data.index = next_index;
1507+ data.lowlink = next_index;
1508+ data.scc = -1 ;
1509+ }
1510+
1511+ next_index += 1 ;
1512+ stack.push_back (p_script);
1513+
1514+ RBMap<GDScript *, int > dependencies;
1515+ p_script->count_dependencies (dependencies);
1516+ for (KeyValue<GDScript *, int > &dependency : dependencies) {
1517+ if (dependency.key == p_script) {
1518+ continue ;
1519+ }
1520+
1521+ if (!script_data.has (dependency.key )) {
1522+ _strong_connect (dependency.key );
1523+ Data &data = script_data[p_script];
1524+ data.lowlink = MIN (data.lowlink , script_data[dependency.key ].lowlink );
1525+ } else { // Omission from the original algorithm: Check whether `dependency.key` is on the stack. -> This condition can never be `false` when only starting from one node.
1526+ Data &data = script_data[p_script];
1527+ data.lowlink = MIN (data.lowlink , script_data[dependency.key ].lowlink );
1528+ }
1529+ }
1530+
1531+ Data &data = script_data[p_script];
1532+ if (data.lowlink == data.index ) {
1533+ RBSet<GDScript *> scc;
1534+ GDScript *head;
1535+ do {
1536+ head = stack[stack.size () - 1 ];
1537+ stack.resize (stack.size () - 1 );
1538+ scc.insert (head);
1539+ script_data[head].scc = next_scc_id;
1540+ } while (head != p_script);
1541+ sccs.push_back (scc);
1542+ next_scc_id += 1 ;
1543+ }
1544+ }
1545+
1546+ public:
1547+ LocalVector<RBSet<GDScript *>> sccs;
1548+
1549+ DependencyCyclesTarjanScc (GDScript *p_from) {
1550+ // Omission from the original algorithm: Repeat for all not yet visited nodes. -> We only care about the subgraph of transitive dependencies.
1551+ _strong_connect (p_from);
1552+ }
1553+
1554+ int get_scc (GDScript *p_script) {
1555+ return script_data[p_script].scc ;
1556+ }
1557+ };
1558+
15491559void GDScript::clear (ClearData *p_clear_data) {
15501560 if (clearing) {
15511561 return ;
@@ -1557,7 +1567,7 @@ void GDScript::clear(ClearData *p_clear_data) {
15571567 bool is_root = false ;
15581568
15591569 // If `clear_data` is `nullptr`, it means that it's the root.
1560- // The root is in charge to clear functions and scripts of itself and its dependencies
1570+ // The root is in charge to clear functions and scripts of itself and its orphaned dependencies.
15611571 if (clear_data == nullptr ) {
15621572 clear_data = &data;
15631573 is_root = true ;
@@ -1571,12 +1581,59 @@ void GDScript::clear(ClearData *p_clear_data) {
15711581 }
15721582
15731583 // If we're in the process of shutting things down then every single script will be cleared
1574- // anyway, so we can safely skip this very costly operation.
1575- if (!GDScriptLanguage::singleton->finishing ) {
1576- RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies ();
1577- for (GDScript *E : must_clear_dependencies) {
1578- clear_data->scripts .insert (E);
1579- E->clear (clear_data);
1584+ // anyway, so we can safely skip this very costly operation. And if another root is clearing
1585+ // us up, it will take care of our deps as well.
1586+ if (is_root && !GDScriptLanguage::singleton->finishing ) {
1587+ // The transitive dependencies of this script form a graph.
1588+ // 1. Use Tarjan DFS to find the graph and strongly connected components (SCC) in it. This also topologically sorts the SCCs.
1589+ DependencyCyclesTarjanScc tarjan (this );
1590+
1591+ // 2. For each SCC calculate: sum of refcounts - sum of internal refs - (sum of incoming refs from previous SCCs which could be freed).
1592+ // If this count is > 0, there are references from outside of gdscript (e.g. user code) or other scripts that are not dependencies of this script.
1593+
1594+ TightLocalVector<int > counts;
1595+ counts.resize_initialized (tarjan.sccs .size ());
1596+
1597+ // Iterate backwards to start with the scc which has no incoming refs from other SCCs and which contains this node. It is the root to unwinding this mess.
1598+ for (int i = tarjan.sccs .size () - 1 ; i >= 0 ; i--) {
1599+ RBMap<GDScript *, int > dependencies;
1600+ for (GDScript *scr : tarjan.sccs [i]) {
1601+ counts[i] += scr->get_reference_count ();
1602+
1603+ if (GDScriptCache::get_cached_script (scr->path ) == scr) {
1604+ // The GDScriptCache references the script, but that's no reason to keep it alive.
1605+ // An additional ref is hold for static scripts which is unaffected by this.
1606+ counts[i] -= 1 ;
1607+ }
1608+
1609+ scr->count_dependencies (dependencies);
1610+ }
1611+
1612+ for (KeyValue<GDScript *, int > &dependency : dependencies) {
1613+ // The script is a transitive dep of this script so it was visited during SCC detection. It's present in the index.
1614+ if (tarjan.get_scc (dependency.key ) == i) {
1615+ // Script is part of this SCC. Decrease refcount by internal refs.
1616+ counts[i] -= dependency.value ;
1617+ }
1618+ }
1619+
1620+ if (counts[i] > 0 ) {
1621+ // There are external refs. This SCC can not be freed.
1622+ continue ;
1623+ }
1624+
1625+ // Remove references from this SCC.
1626+ for (KeyValue<GDScript *, int > &dependency : dependencies) {
1627+ int scc = tarjan.get_scc (dependency.key );
1628+ if (scc != i) {
1629+ counts[scc] -= dependency.value ;
1630+ }
1631+ }
1632+
1633+ for (GDScript *scr : tarjan.sccs [i]) {
1634+ clear_data->scripts .insert (scr);
1635+ scr->clear (clear_data);
1636+ }
15801637 }
15811638 }
15821639
@@ -1585,15 +1642,6 @@ void GDScript::clear(ClearData *p_clear_data) {
15851642 }
15861643 member_functions.clear ();
15871644
1588- for (KeyValue<StringName, MemberInfo> &E : member_indices) {
1589- clear_data->scripts .insert (E.value .data_type .script_type_ref );
1590- E.value .data_type .script_type_ref = Ref<Script>();
1591- }
1592-
1593- for (KeyValue<StringName, MemberInfo> &E : static_variables_indices) {
1594- clear_data->scripts .insert (E.value .data_type .script_type_ref );
1595- E.value .data_type .script_type_ref = Ref<Script>();
1596- }
15971645 static_variables.clear ();
15981646 static_variables_indices.clear ();
15991647
@@ -1612,7 +1660,7 @@ void GDScript::clear(ClearData *p_clear_data) {
16121660 static_initializer = nullptr ;
16131661 }
16141662
1615- _save_orphaned_subclasses (clear_data );
1663+ _save_orphaned_subclasses ();
16161664
16171665#ifdef TOOLS_ENABLED
16181666 // Clearing inner class doc, script doc only cleared when the script source deleted.
0 commit comments