LibOverride: Cleanup unused&missing data after resync.

Often when the reference linked data is significantly modified, a lot of
'ghost' linked data remain referenced by liboverrides, even after
resync. This is due to the fact that missing data is ignored (skipped)
during resync process, to avoid potential destruction of data in case
the linked data is actually missing.

However, after all resync has been done, we can consider that missing
linked references and their liboverrides can be safely deleted, if the
later are not user-edited or hierarchy roots.
This commit is contained in:
Bastien Montagne 2024-02-21 16:14:51 +01:00 • committed by Bastien Montagne
parent d067cdd94b
commit bef276ab0b
2 changed files with 58 additions and 6 deletions

View file

@ -2666,6 +2666,56 @@ static bool lib_override_library_resync(Main *bmain,
return success;
}
/** Clenup: Remove unused 'place holder' linked IDs. */
static void lib_override_cleanup_after_resync(Main *bmain)
{
LibQueryUnusedIDsData parameters;
parameters.do_local_ids = true;
parameters.do_linked_ids = true;
parameters.do_recursive = true;
parameters.filter_fn = [](const ID *id) -> bool {
if (ID_IS_LINKED(id) && (id->tag & LIB_TAG_MISSING) != 0) {
return true;
}
/* This is a fairly complex case.
*
* LibOverride resync process takes care of removing 'no more valid' liboverrides (see at the
* end of #lib_override_library_main_resync_on_library_indirect_level). However, since it does
* not resync data which linked reference is missing (see
* #lib_override_library_main_resync_id_skip_check), these are kept 'as is'. Indeed,
* liboverride resync code cannot know if a specific liboverride data is only part of its
* hierarchy, or if it is also used by some other data (in which case it should be preserved if
* the linked reference goes missing).
*
* So instead, we consider these cases as also valid candidates for deletion here, since the
* whole recursive process in `BKE_lib_query_unused_ids_tag` will ensure that if there is still
* any valid user of these, they won't get tagged for deletion.
*
* Also, do not delete 'orphaned' liboverrides if it's a hierarchy root, or if its hierarchy
* root's reference is missing, since this is much more likely a case of actual missing data,
* rather than changes in the liboverride's hierarchy in the linked data.
*/
if (ID_IS_OVERRIDE_LIBRARY(id)) {
const IDOverrideLibrary *override_library = BKE_lib_override_library_get(
nullptr, id, nullptr, nullptr);
const ID *root = override_library->hierarchy_root;
if (root == id || (root->override_library->reference->tag & LIB_TAG_MISSING) != 0) {
return false;
}
return ((override_library->reference->tag & LIB_TAG_MISSING) != 0);
}
return false;
};
BKE_lib_query_unused_ids_tag(bmain, LIB_TAG_DOIT, parameters);
CLOG_INFO(&LOG_RESYNC,
2,
"Deleting %d unused linked missing IDs and their unused liboverrides (including %d "
"local ones)\n",
parameters.num_total[INDEX_ID_NULL],
parameters.num_local[INDEX_ID_NULL]);
BKE_id_multi_tagged_delete(bmain);
}
bool BKE_lib_override_library_resync(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
@ -2698,6 +2748,8 @@ bool BKE_lib_override_library_resync(Main *bmain,
* re-create the global namemap on demand. */
BKE_main_namemap_destroy(&bmain->name_map_global);
lib_override_cleanup_after_resync(bmain);
return success;
}
@ -3665,6 +3717,8 @@ void BKE_lib_override_library_main_resync(Main *bmain,
* re-create the global namemap on demand. */
BKE_main_namemap_destroy(&bmain->name_map_global);
lib_override_cleanup_after_resync(bmain);
BLI_assert(BKE_main_namemap_validate(bmain));
}

View file

@ -742,9 +742,8 @@ class TestLibraryOverridesComplex(TestHelper, unittest.TestCase):
# Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
assert len(bpy.data.collections) == 3 * 3 + 3
assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:3 * 3])
# Note that the 'missing' renamed objects from the library are still here as empty placeholders,
# hence the 8 linked ones instead of 6.
assert len(bpy.data.objects) == 3 * 6 + 8
# Note that the 'missing' renamed objects from the library are now cleared as part of the resync process.
assert len(bpy.data.objects) == 3 * 6 + 6
assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:3 * 6])
assert len(bpy.data.meshes) == 0 + 1
assert len(bpy.data.armatures) == 0 + 1
@ -774,9 +773,8 @@ class TestLibraryOverridesComplex(TestHelper, unittest.TestCase):
assert len(bpy.data.collections) == 3 + 6
assert all((id_.override_library is not None)
for id_ in bpy.data.collections if id_.library == test_output_path_lib)
# Note that the 'missing' renamed objects from the library are still here as empty placeholders,
# hence the 8 + 6 linked ones instead of 6 + 6.
assert len(bpy.data.objects) == 6 + 14
# Note that the 'missing' renamed objects from the library are now cleared as part of the resync process.
assert len(bpy.data.objects) == 6 + 12
assert all((id_.override_library is not None)
for id_ in bpy.data.objects if id_.library == test_output_path_lib)
assert len(bpy.data.meshes) == 0 + 1