From 1dca5af712524ca48759d5114aa3bff629d9aab5 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 22 Feb 2024 17:11:39 +0100 Subject: [PATCH] Core: Libraries: Fix library parenting when libraries are deleted. So far, when deleting a library (either explicitely, or through e.g. relocation), its dependencies would get a `nullptr` parent, and therefore become 'directly used' libraries. This commit adds a new util to rebuild the libraries hieararchy, and calls it when a Library ID is deleted. NOTE: While logic is somewhat similar to what liboverride resync does to sort the libraries by indirect levels (`lib_override_libraries_index_define`), there are some key differences here, notably the fact that if a library has a valid `parent` pointer, it is not replaced, even if a 'better' parent (less indirect library) could be found. --- source/blender/blenkernel/BKE_library.hh | 6 + .../intern/blendfile_link_append.cc | 3 + .../blenkernel/intern/lib_id_delete.cc | 15 ++ source/blender/blenkernel/intern/library.cc | 157 ++++++++++++++++++ 4 files changed, 181 insertions(+) diff --git a/source/blender/blenkernel/BKE_library.hh b/source/blender/blenkernel/BKE_library.hh index edef4d18c77..e78cf1529a4 100644 --- a/source/blender/blenkernel/BKE_library.hh +++ b/source/blender/blenkernel/BKE_library.hh @@ -15,3 +15,9 @@ struct Library; struct Main; void BKE_library_filepath_set(Main *bmain, Library *lib, const char *filepath); + +/** + * Rebuild the hierarchy of libraries, after e.g. deleting or relocating one, often some indirectly + * linked libraries lose their 'parent' pointer, making them wrongly directly used ones. + */ +void BKE_library_main_rebuild_hierarchy(Main *bmain); diff --git a/source/blender/blenkernel/intern/blendfile_link_append.cc b/source/blender/blenkernel/intern/blendfile_link_append.cc index 2edeabe55a4..acbf26660bc 100644 --- a/source/blender/blenkernel/intern/blendfile_link_append.cc +++ b/source/blender/blenkernel/intern/blendfile_link_append.cc @@ -43,6 +43,7 @@ #include "BKE_lib_override.hh" #include "BKE_lib_query.hh" #include "BKE_lib_remap.hh" +#include "BKE_library.hh" #include "BKE_main.hh" #include "BKE_main_namemap.hh" #include "BKE_material.h" @@ -1916,6 +1917,8 @@ void BKE_blendfile_library_relocate(BlendfileLinkAppendContext *lapp_context, } FOREACH_MAIN_ID_END; + BKE_library_main_rebuild_hierarchy(bmain); + /* Resync overrides if needed. */ if (!USER_EXPERIMENTAL_TEST(&U, no_override_auto_resync)) { BlendFileReadReport report{}; diff --git a/source/blender/blenkernel/intern/lib_id_delete.cc b/source/blender/blenkernel/intern/lib_id_delete.cc index f9ff7f325df..0b0d439f71c 100644 --- a/source/blender/blenkernel/intern/lib_id_delete.cc +++ b/source/blender/blenkernel/intern/lib_id_delete.cc @@ -31,6 +31,7 @@ #include "BKE_lib_id.hh" #include "BKE_lib_override.hh" #include "BKE_lib_remap.hh" +#include "BKE_library.hh" #include "BKE_main.hh" #include "BKE_main_namemap.hh" @@ -213,9 +214,15 @@ void BKE_id_free_us(Main *bmain, void *idv) /* test users */ } if (id->us == 0) { + const bool is_lib = GS(id->name) == ID_LI; + BKE_libblock_unlink(bmain, id, false, false); BKE_id_free(bmain, id); + + if (is_lib) { + BKE_library_main_rebuild_hierarchy(bmain); + } } } @@ -226,6 +233,7 @@ static size_t id_delete(Main *bmain, const int tag = LIB_TAG_DOIT; ListBase *lbarray[INDEX_ID_MAX]; int base_count, i; + bool has_deleted_library = false; /* Used by batch tagged deletion, when we call BKE_id_free then, id is no more in Main database, * and has already properly unlinked its other IDs usages. @@ -400,6 +408,9 @@ static size_t id_delete(Main *bmain, ID_REAL_USERS(id), (id->tag & LIB_TAG_EXTRAUSER_SET) != 0 ? 1 : 0); } + if (!has_deleted_library && GS(id->name) == ID_LI) { + has_deleted_library = true; + } id_free(bmain, id, free_flag, !do_tagged_deletion); ++num_datablocks_deleted; } @@ -409,6 +420,10 @@ static size_t id_delete(Main *bmain, BKE_layer_collection_resync_allow(); BKE_main_collection_sync_remap(bmain); + if (has_deleted_library) { + BKE_library_main_rebuild_hierarchy(bmain); + } + bmain->is_memfile_undo_written = false; return num_datablocks_deleted; } diff --git a/source/blender/blenkernel/intern/library.cc b/source/blender/blenkernel/intern/library.cc index 5b5970fa102..70c883ccd68 100644 --- a/source/blender/blenkernel/intern/library.cc +++ b/source/blender/blenkernel/intern/library.cc @@ -18,6 +18,8 @@ #include "BLI_utildefines.h" #include "BLI_blenlib.h" +#include "BLI_ghash.h" +#include "BLI_set.hh" #include "BLT_translation.h" @@ -136,3 +138,158 @@ void BKE_library_filepath_set(Main *bmain, Library *lib, const char *filepath) BLI_path_abs(lib->filepath_abs, blendfile_path); } } + +static void rebuild_hierarchy_best_parent_find(Main *bmain, + blender::Set &directly_used_libs, + Library *lib) +{ + BLI_assert(!directly_used_libs.contains(lib)); + + Library *best_parent_lib = nullptr; + bool do_break = false; + ListBase *lb; + ID *id_iter; + FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) { + FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id_iter) { + if (!ID_IS_LINKED(id_iter) || id_iter->lib != lib) { + continue; + } + MainIDRelationsEntry *entry = static_cast( + BLI_ghash_lookup(bmain->relations->relations_from_pointers, id_iter)); + for (MainIDRelationsEntryItem *item = entry->from_ids; item; item = item->next) { + ID *from_id = item->id_pointer.from; + if (!ID_IS_LINKED(from_id)) { + BLI_assert_unreachable(); + continue; + } + Library *from_id_lib = from_id->lib; + if (from_id_lib == lib) { + continue; + } + if (directly_used_libs.contains(from_id_lib)) { + /* Found the first best possible candidate, no need to search further. */ + BLI_assert(best_parent_lib == nullptr || best_parent_lib->temp_index > 0); + best_parent_lib = from_id_lib; + do_break = true; + break; + } + if (!from_id_lib->parent) { + rebuild_hierarchy_best_parent_find(bmain, directly_used_libs, from_id_lib); + } + if (!best_parent_lib || best_parent_lib->temp_index > from_id_lib->temp_index) { + best_parent_lib = from_id_lib; + if (best_parent_lib->temp_index == 0) { + /* Found the first best possible candidate, no need to search further. */ + BLI_assert(directly_used_libs.contains(best_parent_lib)); + do_break = true; + break; + } + } + } + if (do_break) { + break; + } + } + FOREACH_MAIN_LISTBASE_ID_END; + if (do_break) { + break; + } + } + FOREACH_MAIN_LISTBASE_END; + + /* NOTE: It may happen that no parent library is found, e.g. if after deleting a directly used + * library, its indirect dependency is still around, but none of its linked IDs are used by local + * data. */ + if (best_parent_lib) { + lib->parent = best_parent_lib; + lib->temp_index = best_parent_lib->temp_index + 1; + } + else { + lib->parent = nullptr; + lib->temp_index = 0; + directly_used_libs.add(lib); + } +} + +void BKE_library_main_rebuild_hierarchy(Main *bmain) +{ + BKE_main_relations_create(bmain, 0); + + /* Find all libraries with directly linked IDs (i.e. IDs used by local data). */ + blender::Set directly_used_libs; + ID *id_iter; + FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { + if (!ID_IS_LINKED(id_iter)) { + continue; + } + id_iter->lib->temp_index = 0; + if (directly_used_libs.contains(id_iter->lib)) { + continue; + } + MainIDRelationsEntry *entry = static_cast( + BLI_ghash_lookup(bmain->relations->relations_from_pointers, id_iter)); + for (MainIDRelationsEntryItem *item = entry->from_ids; item; item = item->next) { + if (!ID_IS_LINKED(item->id_pointer.from)) { + directly_used_libs.add(id_iter->lib); + id_iter->lib->parent = nullptr; + break; + } + } + } + FOREACH_MAIN_ID_END; + + LISTBASE_FOREACH (Library *, lib_iter, &bmain->libraries) { + /* A directly used library. */ + if (directly_used_libs.contains(lib_iter)) { + BLI_assert(lib_iter->temp_index == 0); + continue; + } + + /* Assume existing parent is still valid, since it was not cleared in previous loop above. + * Just compute 'hierarchy value' in temp index, if needed. */ + if (lib_iter->parent) { + if (lib_iter->temp_index > 0) { + continue; + } + blender::Vector parent_libraries; + for (Library *parent_lib_iter = lib_iter; + parent_lib_iter && parent_lib_iter->temp_index == 0; + parent_lib_iter = parent_lib_iter->parent) + { + parent_libraries.append(parent_lib_iter); + } + int parent_temp_index = parent_libraries.last()->temp_index + int(parent_libraries.size()) - + 1; + for (Library *parent_lib_iter : parent_libraries) { + BLI_assert(parent_lib_iter != parent_libraries.last() || + parent_lib_iter->temp_index == parent_temp_index); + parent_lib_iter->temp_index = parent_temp_index--; + } + continue; + } + + /* Otherwise, it's an indirectly used library with no known parent, another loop is needed to + * ansure all knwon hierarcy has valid indices when trying to find the best valid parent + * library. */ + } + + /* For all libraries known to be indirect, but without a known parent, find a best valid parent + * (i.e. a 'most directly used' library). */ + LISTBASE_FOREACH (Library *, lib_iter, &bmain->libraries) { + /* A directly used library. */ + if (directly_used_libs.contains(lib_iter)) { + BLI_assert(lib_iter->temp_index == 0); + continue; + } + + if (lib_iter->parent) { + BLI_assert(lib_iter->temp_index > 0); + } + else { + BLI_assert(lib_iter->temp_index == 0); + rebuild_hierarchy_best_parent_find(bmain, directly_used_libs, lib_iter); + } + } + + BKE_main_relations_free(bmain); +}