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.
This commit is contained in:
Bastien Montagne 2024-02-22 17:11:39 +01:00
parent dc2ec78f1d
commit 1dca5af712
4 changed files with 181 additions and 0 deletions

View File

@ -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);

View File

@ -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{};

View File

@ -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;
}

View File

@ -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<Library *> &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<MainIDRelationsEntry *>(
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<Library *> 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<MainIDRelationsEntry *>(
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<Library *> 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);
}