diff options
author | Bastien Montagne <mont29> | 2022-02-11 17:18:01 +0300 |
---|---|---|
committer | Bastien Montagne <bastien@blender.org> | 2022-02-11 17:18:32 +0300 |
commit | 1695d38989fd482d3c010dc9ba4b0cb8e07a7139 (patch) | |
tree | 4f8d438cb2175294abf8844b976575df86291f48 /source/blender | |
parent | e6f3bb984b45738e1c3e72c4f16e631569cba350 (diff) |
LibOverride: Add partial resync.
Reduce compute effort of liboverrides resync process by only re-syncing
the parts of the override hierarchy that actually need it.
The main change compared to existing code (which was systematically resyncing
a whole override hierarchy), is that resyncing now operates over several
sub-hierarchies at once, each defined by their own 'resync root' ID.
This ensures that we do not get several new overrides for the same data inside
of the same hierarchy.
Implements T95682.
Differential Revision: https://developer.blender.org/D14079
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/blenkernel/intern/lib_override.c | 562 |
1 files changed, 378 insertions, 184 deletions
diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index d65c0bb4e95..e42320fa2ca 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -724,7 +724,23 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data) /* For each object tagged for override, ensure we get at least one local or liboverride * collection to host it. Avoids getting a bunch of random object in the scene's master * collection when all objects' dependencies are not properly 'packed' into a single root - * collection. */ + * collection. + * + * NOTE: In resync case, we do not handle this at all, since: + * - In normal, valid cases nothing would be needed anyway (resync process takes care + * of tagging needed 'owner' collection then). + * - Partial resync makes it extremely difficult to properly handle such extra + * collection 'tagging for override' (since one would need to know if the new object + * is actually going to replace an already existing override [most common case], or + * if it is actually a real new 'orphan' one). + * - While not ideal, having objects dangling around is less critical than both points + * above. + * So if users add new objects to their library override hierarchy in an invalid way, so + * be it. Trying to find a collection to override and host this new object would most + * likely make existing override very unclean anyway. */ + if (is_resync) { + return; + } LISTBASE_FOREACH (Object *, ob, &bmain->objects) { if (ID_IS_LINKED(ob) && (ob->id.tag & data->tag) != 0) { Collection *instantiating_collection = NULL; @@ -738,17 +754,17 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data) instantiating_collection_linknode != NULL; instantiating_collection_linknode = instantiating_collection_linknode->next) { instantiating_collection = instantiating_collection_linknode->link; - /* In (recursive) resync case, if a collection of a 'parent' lib instantiates the - * linked object, it is also fine. */ - if (!ID_IS_LINKED(instantiating_collection) || - (is_resync && ID_IS_LINKED(id_root) && - instantiating_collection->id.lib->temp_index < id_root->lib->temp_index)) { + if (!ID_IS_LINKED(instantiating_collection)) { + /* There is a local collection instantiating the linked object to override, nothing + * else to be done here. */ break; } - if (ID_IS_LINKED(instantiating_collection) && - (!is_resync || instantiating_collection->id.lib == id_root->lib)) { - instantiating_collection_override_candidate = instantiating_collection; + if (instantiating_collection->id.tag & data->tag) { + /* There is a linked collection instantiating the linked object to override, + * already tagged to be overridden, nothing else to be done here. */ + break; } + instantiating_collection_override_candidate = instantiating_collection; instantiating_collection = NULL; } } @@ -1293,6 +1309,8 @@ static bool lib_override_library_resync(Main *bmain, Scene *scene, ViewLayer *view_layer, ID *id_root, + LinkNode *id_resync_roots, + ListBase *no_main_ids_list, Collection *override_resync_residual_storage, const bool do_hierarchy_enforce, const bool do_post_process, @@ -1301,6 +1319,7 @@ static bool lib_override_library_resync(Main *bmain, BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root)); ID *id_root_reference = id_root->override_library->reference; + ID *id; if (id_root_reference->tag & LIB_TAG_MISSING) { BKE_reportf(reports != NULL ? reports->reports : NULL, @@ -1321,94 +1340,145 @@ static bool lib_override_library_resync(Main *bmain, .is_override = true, .is_resync = true}; lib_override_group_tag_data_object_to_collection_init(&data); - lib_override_overrides_group_tag(&data); - - BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); - data.id_root = id_root_reference; - data.is_override = false; - lib_override_linked_group_tag(&data); - - BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); - lib_override_hierarchy_dependencies_recursive_tag(&data); - /* Make a mapping 'linked reference IDs' -> 'Local override IDs' of existing overrides. */ + /* Mapping 'linked reference IDs' -> 'Local override IDs' of existing overrides, populated from + * each sub-tree that actually needs to be resynced. */ GHash *linkedref_to_old_override = BLI_ghash_new( BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); - ID *id; - FOREACH_MAIN_ID_BEGIN (bmain, id) { - /* IDs that get fully removed from linked data remain as local overrides (using place-holder - * linked IDs as reference), but they are often not reachable from any current valid local - * override hierarchy anymore. This will ensure they get properly deleted at the end of this - * function. */ - if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id) && - (id->override_library->reference->tag & LIB_TAG_MISSING) != 0 && - /* Unfortunately deleting obdata means deleting their objects too. Since there is no - * guarantee that a valid override object using an obsolete override obdata gets properly - * updated, we ignore those here for now. In practice this should not be a big issue. */ - !OB_DATA_SUPPORT_ID(GS(id->name))) { - id->tag |= LIB_TAG_MISSING; - } - - if (id->tag & LIB_TAG_DOIT && (id->lib == id_root->lib) && ID_IS_OVERRIDE_LIBRARY(id)) { - /* While this should not happen in typical cases (and won't be properly supported here), user - * is free to do all kind of very bad things, including having different local overrides of a - * same linked ID in a same hierarchy. */ - IDOverrideLibrary *id_override_library = lib_override_get(bmain, id); - ID *reference_id = id_override_library->reference; - if (GS(reference_id->name) != GS(id->name)) { - switch (GS(id->name)) { - case ID_KE: - reference_id = (ID *)BKE_key_from_id(reference_id); - break; - case ID_GR: - BLI_assert(GS(reference_id->name) == ID_SCE); - reference_id = (ID *)((Scene *)reference_id)->master_collection; - break; - case ID_NT: - reference_id = (ID *)ntreeFromID(id); - break; - default: - break; - } + + /* Only tag linked IDs from related linked reference hierarchy that are actually part of + * the sub-trees of each detected sub-roots needing resync. */ + for (LinkNode *resync_root_link = id_resync_roots; resync_root_link != NULL; + resync_root_link = resync_root_link->next) { + ID *id_resync_root = resync_root_link->link; + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_resync_root)); + + if ((id_resync_root->tag & LIB_TAG_NO_MAIN) != 0) { + CLOG_ERROR(&LOG, + "While dealing with root '%s', resync root ID '%s' (%p) found to be alreaady " + "resynced.\n", + id_root->name, + id_resync_root->name, + id_resync_root); + } + // if (no_main_ids_list && BLI_findindex(no_main_ids_list, id_resync_root) != -1) { + // CLOG_ERROR( + // &LOG, + // "While dealing with root '%s', resync root ID '%s' found to be alreaady + // resynced.\n", id_root->name, id_resync_root->name); + // } + + ID *id_resync_root_reference = id_resync_root->override_library->reference; + + if (id_resync_root_reference->tag & LIB_TAG_MISSING) { + BKE_reportf( + reports != NULL ? reports->reports : NULL, + RPT_ERROR, + "Impossible to resync data-block %s and its dependencies, as its linked reference " + "is missing", + id_root->name + 2); + BLI_ghash_free(linkedref_to_old_override, NULL, NULL); + BKE_main_relations_free(bmain); + lib_override_group_tag_data_clear(&data); + return false; + } + + /* Tag local overrides of the current resync sub-hierarchy. */ + BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); + data.id_root = id_resync_root; + data.is_override = true; + lib_override_overrides_group_tag(&data); + + /* Tag reference data matching the current resync sub-hierarchy. */ + BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); + data.id_root = id_resync_root->override_library->reference; + data.is_override = false; + lib_override_linked_group_tag(&data); + + BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); + lib_override_hierarchy_dependencies_recursive_tag(&data); + + FOREACH_MAIN_ID_BEGIN (bmain, id) { + /* IDs that get fully removed from linked data remain as local overrides (using place-holder + * linked IDs as reference), but they are often not reachable from any current valid local + * override hierarchy anymore. This will ensure they get properly deleted at the end of this + * function. */ + if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id) && + (id->override_library->reference->tag & LIB_TAG_MISSING) != 0 && + /* Unfortunately deleting obdata means deleting their objects too. Since there is no + * guarantee that a valid override object using an obsolete override obdata gets properly + * updated, we ignore those here for now. In practice this should not be a big issue. */ + !OB_DATA_SUPPORT_ID(GS(id->name))) { + id->tag |= LIB_TAG_MISSING; } - BLI_assert(GS(reference_id->name) == GS(id->name)); - if (!BLI_ghash_haskey(linkedref_to_old_override, reference_id)) { - BLI_ghash_insert(linkedref_to_old_override, reference_id, id); - if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { - continue; - } - if ((id->override_library->reference->tag & LIB_TAG_DOIT) == 0) { - /* We have an override, but now it does not seem to be necessary to override that ID - * anymore. Check if there are some actual overrides from the user, otherwise assume - * that we can get rid of this local override. */ - LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &id->override_library->properties) { - if (!ELEM(op->rna_prop_type, PROP_POINTER, PROP_COLLECTION)) { - id->override_library->reference->tag |= LIB_TAG_DOIT; + if (id->tag & LIB_TAG_DOIT && (id->lib == id_root->lib) && ID_IS_OVERRIDE_LIBRARY(id)) { + /* While this should not happen in typical cases (and won't be properly supported here), + * user is free to do all kind of very bad things, including having different local + * overrides of a same linked ID in a same hierarchy. */ + IDOverrideLibrary *id_override_library = lib_override_get(bmain, id); + ID *reference_id = id_override_library->reference; + if (GS(reference_id->name) != GS(id->name)) { + switch (GS(id->name)) { + case ID_KE: + reference_id = (ID *)BKE_key_from_id(reference_id); break; - } + case ID_GR: + BLI_assert(GS(reference_id->name) == ID_SCE); + reference_id = (ID *)((Scene *)reference_id)->master_collection; + break; + case ID_NT: + reference_id = (ID *)ntreeFromID(id); + break; + default: + break; + } + } + BLI_assert(GS(reference_id->name) == GS(id->name)); - bool do_break = false; - LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) { - if ((opop->flag & IDOVERRIDE_LIBRARY_FLAG_IDPOINTER_MATCH_REFERENCE) == 0) { + if (!BLI_ghash_haskey(linkedref_to_old_override, reference_id)) { + BLI_ghash_insert(linkedref_to_old_override, reference_id, id); + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + continue; + } + if ((id->override_library->reference->tag & LIB_TAG_DOIT) == 0) { + /* We have an override, but now it does not seem to be necessary to override that ID + * anymore. Check if there are some actual overrides from the user, otherwise assume + * that we can get rid of this local override. */ + LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &id->override_library->properties) { + if (!ELEM(op->rna_prop_type, PROP_POINTER, PROP_COLLECTION)) { id->override_library->reference->tag |= LIB_TAG_DOIT; - do_break = true; break; } - } - if (do_break) { - break; + + bool do_break = false; + LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) { + if ((opop->flag & IDOVERRIDE_LIBRARY_FLAG_IDPOINTER_MATCH_REFERENCE) == 0) { + id->override_library->reference->tag |= LIB_TAG_DOIT; + do_break = true; + break; + } + } + if (do_break) { + break; + } } } } } } + FOREACH_MAIN_ID_END; + + /* Code above may have added some tags, we need to update this too. */ + BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); + lib_override_hierarchy_dependencies_recursive_tag(&data); } - FOREACH_MAIN_ID_END; - /* Code above may have added some tags, we need to update this too. */ + /* Tag all local overrides of the current hierarchy. */ BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); - lib_override_hierarchy_dependencies_recursive_tag(&data); + data.id_root = id_root; + data.is_override = true; + lib_override_overrides_group_tag(&data); BKE_main_relations_free(bmain); lib_override_group_tag_data_clear(&data); @@ -1421,6 +1491,7 @@ static bool lib_override_library_resync(Main *bmain, bmain, NULL, id_root_reference, id_root->override_library->hierarchy_root, true); if (!success) { + BLI_ghash_free(linkedref_to_old_override, NULL, NULL); return success; } @@ -1469,6 +1540,8 @@ static bool lib_override_library_resync(Main *bmain, lib_override_library_property_copy(op_new, op_old); } } + + BLI_addtail(no_main_ids_list, id_override_old); } else { /* Add to proper main list, ensure unique name for local ID, sort, and clear relevant @@ -1565,7 +1638,7 @@ static bool lib_override_library_resync(Main *bmain, id->newid->tag &= ~LIB_TAG_DOIT; id_override_old->tag |= LIB_TAG_DOIT; if (id_override_old->tag & LIB_TAG_NO_MAIN) { - BKE_id_free(bmain, id_override_old); + BLI_assert(BLI_findindex(no_main_ids_list, id_override_old) != -1); } } } @@ -1654,33 +1727,71 @@ bool BKE_lib_override_library_resync(Main *bmain, const bool do_hierarchy_enforce, BlendFileReadReport *reports) { + ListBase no_main_ids_list = {NULL}; + LinkNode id_resync_roots = {.link = id_root, .next = NULL}; + const bool success = lib_override_library_resync(bmain, scene, view_layer, id_root, + &id_resync_roots, + &no_main_ids_list, override_resync_residual_storage, do_hierarchy_enforce, true, reports); + LISTBASE_FOREACH_MUTABLE (ID *, id_iter, &no_main_ids_list) { + BKE_id_free(bmain, id_iter); + } + return success; } -/* Also tag ancestors overrides for resync. +static bool lib_override_resync_id_lib_level_is_valid(ID *id, + const int library_indirect_level, + const bool do_strict_equal) +{ + const int id_lib_level = (ID_IS_LINKED(id) ? id->lib->temp_index : 0); + return do_strict_equal ? id_lib_level == library_indirect_level : + id_lib_level <= library_indirect_level; +} + +/* Find the root of the override hierarchy the given `id` belongs to. */ +static ID *lib_override_library_main_resync_root_get(Main *bmain, ID *id) +{ + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id); + if (id_type->owner_get != NULL) { + id = id_type->owner_get(bmain, id); + } + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id)); + } + + ID *hierarchy_root_id = id->override_library->hierarchy_root; + BLI_assert(hierarchy_root_id != NULL); + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(hierarchy_root_id)); + return hierarchy_root_id; +} + +/* Check ancestors overrides for resync, to ensure all IDs in-between two tagged-for-resync ones + * are also properly tagged, and to identify the resync root of this subset of the whole hierarchy. * * WARNING: Expects `bmain` to have valid relation data. * - * NOTE: Related to `lib_override_library_main_resync_find_root_recurse` below. + * Returns `true` if it finds an ancestor within the current liboverride hierarchy also tagged as + * needing resync, `false` otherwise. * - * TODO: This is a sub-optimal, simple solution. At some point, we should rather find a way to - * resync a set of 'sub-roots' overrides, instead of having to 'go back' to the real root and - * resync the whole hierarchy. + * NOTE: If `check_only` is true, it only does the check and returns, without any modification to + * the data. Used for debug/data validation purposes. */ -static void lib_override_resync_tagging_finalize_recurse(Main *bmain, - ID *id, - const int library_indirect_level) +static bool lib_override_resync_tagging_finalize_recurse( + Main *bmain, ID *id, GHash *id_roots, const int library_indirect_level, const bool check_only) { - if (id->lib != NULL && id->lib->temp_index > library_indirect_level) { + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id)); + + if (!lib_override_resync_id_lib_level_is_valid(id, library_indirect_level, false) && + !check_only) { CLOG_ERROR( &LOG, "While processing indirect level %d, ID %s from lib %s of indirect level %d detected " @@ -1690,7 +1801,7 @@ static void lib_override_resync_tagging_finalize_recurse(Main *bmain, id->lib->filepath, id->lib->temp_index); id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; - return; + return false; } MainIDRelationsEntry *entry = BLI_ghash_lookup(bmain->relations->relations_from_pointers, id); @@ -1698,59 +1809,103 @@ static void lib_override_resync_tagging_finalize_recurse(Main *bmain, if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) { /* This ID has already been processed. */ - return; + return (ID_IS_OVERRIDE_LIBRARY(id) && (id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) != 0); } + /* This way we won't process again that ID, should we encounter it again through another * relationship hierarchy. */ entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED; + /* There can be dependency loops in relationships, e.g.: + * - Rig object uses armature ID. + * - Armature ID has some drivers using rig object as variable. + * + * In that case, if this function is initially called on the rig object (therefore tagged as + * needing resync), it will: + * - Process the rig object + * -- Recurse over the armature ID + * --- Recurse over the rig object again + * --- The rig object is detected as already processed and returns true. + * -- The armature has a tagged ancestor (the rig object), so it is not a resync root. + * - The rig object has a tagged ancestor (the armature), so it is not a resync root. + * ...and this ends up with no resync root. + * + * Removing the 'need resync' tag before doing the recursive calls allow to break this loop and + * results in actually getting a resync root, even though it may not be the 'logical' one. + * + * In the example above, the armature will become the resync root. + */ + const int id_tag_need_resync = (id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC); + id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; + + bool is_ancestor_tagged_for_resync = false; for (MainIDRelationsEntryItem *entry_item = entry->from_ids; entry_item != NULL; entry_item = entry_item->next) { if (entry_item->usage_flag & (IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE | IDWALK_CB_LOOPBACK)) { continue; } + ID *id_from = entry_item->id_pointer.from; - /* Case where this ID pointer was to a linked ID, that now needs to be overridden. */ - if (id_from != id && ID_IS_OVERRIDE_LIBRARY_REAL(id_from) && id_from->lib == id->lib) { - id_from->tag |= LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; - CLOG_INFO(&LOG, - 4, - "ID %s (%p) now tagged as needing resync because they use %s (%p) that needs to " - "be overridden", - id_from->name, - id_from->lib, - id->name, - id->lib); - lib_override_resync_tagging_finalize_recurse(bmain, id_from, library_indirect_level); + /* Check if this ID has an override hierarchy ancestor already tagged for resync. */ + if (id_from != id && ID_IS_OVERRIDE_LIBRARY_REAL(id_from) && id_from->lib == id->lib && + id_from->override_library->hierarchy_root == id->override_library->hierarchy_root) { + const bool is_ancestor_tagged_for_resync_prev = is_ancestor_tagged_for_resync; + is_ancestor_tagged_for_resync |= lib_override_resync_tagging_finalize_recurse( + bmain, id_from, id_roots, library_indirect_level, check_only); + + if (!check_only && is_ancestor_tagged_for_resync && !is_ancestor_tagged_for_resync_prev) { + CLOG_INFO(&LOG, + 4, + "ID %s (%p) now tagged as needing resync because they are used by %s (%p) " + "that needs to be resynced", + id->name, + id->lib, + id_from->name, + id_from->lib); + } } } -} -/* Ensures parent collection (or objects) in the same override group are also tagged for resync. - * - * This is needed since otherwise, some (new) ID added in one sub-collection might be used in - * another unrelated sub-collection, if 'root' collection is not resynced separated resync of those - * sub-collections would be unaware that this is the same ID, and would re-generate several - * overrides for it. - * - * NOTE: Related to `lib_override_resync_tagging_finalize` above. - */ -static ID *lib_override_library_main_resync_root_get(Main *bmain, ID *id) -{ - if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { - const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id); - if (id_type->owner_get != NULL) { - id = id_type->owner_get(bmain, id); + /* Re-enable 'need resync' tag if needed. */ + id->tag |= id_tag_need_resync; + + if (check_only) { + return is_ancestor_tagged_for_resync; + } + + if (is_ancestor_tagged_for_resync) { + /* If a tagged-for-resync ancestor was found, this id is not a resync sub-tree root, but it is + * part of one, and therefore needs to be tagged for resync too. */ + id->tag |= LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; + } + else if (id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) { + CLOG_INFO(&LOG, + 4, + "ID %s (%p) is tagged as needing resync, but none of its override hierarchy " + "ancestors are tagged for resync, so it is a partial resync root", + id->name, + id->lib); + + /* If no tagged-for-resync ancestor was found, but this id is tagged for resync, then it is a + * root of a resync sub-tree. Find the root of the whole override hierarchy and add this ID as + * one of its resync sub-tree roots. */ + ID *id_root = lib_override_library_main_resync_root_get(bmain, id); + BLI_assert(id_root->lib == id->lib); + + CLOG_INFO(&LOG, 4, "Found root ID '%s' for resync root ID '%s'", id_root->name, id->name); + + LinkNodePair **id_resync_roots_p; + if (!BLI_ghash_ensure_p(id_roots, id_root, (void ***)&id_resync_roots_p)) { + *id_resync_roots_p = MEM_callocN(sizeof(**id_resync_roots_p), __func__); } - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id)); + + BLI_linklist_append(*id_resync_roots_p, id); + is_ancestor_tagged_for_resync = true; } - ID *hierarchy_root_id = id->override_library->hierarchy_root; - BLI_assert(hierarchy_root_id != NULL); - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(hierarchy_root_id)); - return hierarchy_root_id; + return is_ancestor_tagged_for_resync; } /* Ensure resync of all overrides at one level of indirect usage. @@ -1810,6 +1965,8 @@ static void lib_override_library_main_resync_on_library_indirect_level( FOREACH_MAIN_ID_END; lib_override_group_tag_data_clear(&data); + GHash *id_roots = BLI_ghash_ptr_new(__func__); + /* Now check existing overrides, those needing resync will be the one either already tagged as * such, or the one using linked data that is now tagged as needing override. */ BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); @@ -1818,6 +1975,10 @@ static void lib_override_library_main_resync_on_library_indirect_level( continue; } + if (!lib_override_resync_id_lib_level_is_valid(id, library_indirect_level, true)) { + continue; + } + if (id->override_library->flag & IDOVERRIDE_LIBRARY_FLAG_NO_HIERARCHY) { /* This ID is not part of an override hierarchy. */ BLI_assert((id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0); @@ -1826,7 +1987,8 @@ static void lib_override_library_main_resync_on_library_indirect_level( if (id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) { CLOG_INFO(&LOG, 4, "ID %s (%p) was already tagged as needing resync", id->name, id->lib); - lib_override_resync_tagging_finalize_recurse(bmain, id, library_indirect_level); + lib_override_resync_tagging_finalize_recurse( + bmain, id, id_roots, library_indirect_level, false); continue; } @@ -1851,80 +2013,112 @@ static void lib_override_library_main_resync_on_library_indirect_level( id->lib, id_to->name, id_to->lib); - lib_override_resync_tagging_finalize_recurse(bmain, id, library_indirect_level); + lib_override_resync_tagging_finalize_recurse( + bmain, id, id_roots, library_indirect_level, false); break; } } } FOREACH_MAIN_ID_END; +#ifndef NDEBUG + /* Check for validity/integrity of the computed set of root IDs, and their sub-branches defined + * by their resync root IDs. */ + { + BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); + GHashIterator *id_roots_iter = BLI_ghashIterator_new(id_roots); + while (!BLI_ghashIterator_done(id_roots_iter)) { + ID *id_root = BLI_ghashIterator_getKey(id_roots_iter); + LinkNodePair *id_resync_roots = BLI_ghashIterator_getValue(id_roots_iter); + CLOG_INFO( + &LOG, 2, "Checking validity of computed TODO data for root '%s'... \n", id_root->name); + for (LinkNode *id_resync_root_iter = id_resync_roots->list; id_resync_root_iter != NULL; + id_resync_root_iter = id_resync_root_iter->next) { + ID *id_resync_root = id_resync_root_iter->link; + BLI_assert(id_resync_root == id_root || !BLI_ghash_haskey(id_roots, id_resync_root)); + if (id_resync_root == id_root) { + BLI_assert(id_resync_root_iter == id_resync_roots->list && + id_resync_root_iter == id_resync_roots->last_node); + } + BLI_assert(!lib_override_resync_tagging_finalize_recurse( + bmain, id_resync_root, id_roots, library_indirect_level, true)); + } + BLI_ghashIterator_step(id_roots_iter); + } + BLI_ghashIterator_free(id_roots_iter); + } +#endif + BKE_main_relations_free(bmain); BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); - /* And do the actual resync for all IDs detected as needing it. - * NOTE: Since this changes `bmain` (adding **and** removing IDs), we cannot use - * `FOREACH_MAIN_ID_BEGIN/END` here, and need special multi-loop processing. */ - bool do_continue = true; - while (do_continue) { - do_continue = false; - ListBase *lb; - FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) { - FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) { - if ((id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0 || - (ID_IS_LINKED(id) && id->lib->temp_index < library_indirect_level) || - (!ID_IS_LINKED(id) && library_indirect_level != 0)) { - continue; - } + ListBase no_main_ids_list = {NULL}; - if (ID_IS_LINKED(id)) { - id->lib->tag |= LIBRARY_TAG_RESYNC_REQUIRED; - } + GHashIterator *id_roots_iter = BLI_ghashIterator_new(id_roots); + while (!BLI_ghashIterator_done(id_roots_iter)) { + ID *id_root = BLI_ghashIterator_getKey(id_roots_iter); + Library *library = id_root->lib; + LinkNodePair *id_resync_roots = BLI_ghashIterator_getValue(id_roots_iter); - /* We cannot resync a scene that is currently active. */ - if (id == &scene->id) { - id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; - BKE_reportf(reports->reports, - RPT_WARNING, - "Scene '%s' was not resynced as it is the currently active one", - scene->id.name + 2); - continue; - } + if (ID_IS_LINKED(id_root)) { + id_root->lib->tag |= LIBRARY_TAG_RESYNC_REQUIRED; + } - Library *library = id->lib; - - /* In complex non-supported cases, with several different override hierarchies sharing - * relations between each-other, we may end up not actually updating/replacing the given - * root id (see e.g. pro/shots/110_rextoria/110_0150_A/110_0150_A.anim.blend of sprites - * project repository, r2687). - * This can lead to infinite loop here, at least avoid this. */ - id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; - id = lib_override_library_main_resync_root_get(bmain, id); - id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id)); - BLI_assert(id->lib == library); - do_continue = true; - - CLOG_INFO(&LOG, 2, "Resyncing %s (%p)...", id->name, library); - const bool success = lib_override_library_resync( - bmain, scene, view_layer, id, override_resync_residual_storage, false, false, reports); - CLOG_INFO(&LOG, 2, "\tSuccess: %d", success); - if (success) { - reports->count.resynced_lib_overrides++; - if (library_indirect_level > 0 && reports->do_resynced_lib_overrides_libraries_list && - BLI_linklist_index(reports->resynced_lib_overrides_libraries, library) < 0) { - BLI_linklist_prepend(&reports->resynced_lib_overrides_libraries, library); - reports->resynced_lib_overrides_libraries_count++; - } - } - break; - } - FOREACH_MAIN_LISTBASE_ID_END; - if (do_continue) { - break; + CLOG_INFO(&LOG, + 2, + "Resyncing all dependencies under root %s (%p), first one being '%s'...", + id_root->name, + library, + ((ID *)id_resync_roots->list->link)->name); + const bool success = lib_override_library_resync(bmain, + scene, + view_layer, + id_root, + id_resync_roots->list, + &no_main_ids_list, + override_resync_residual_storage, + false, + false, + reports); + CLOG_INFO(&LOG, 2, "\tSuccess: %d", success); + if (success) { + reports->count.resynced_lib_overrides++; + if (library_indirect_level > 0 && reports->do_resynced_lib_overrides_libraries_list && + BLI_linklist_index(reports->resynced_lib_overrides_libraries, library) < 0) { + BLI_linklist_prepend(&reports->resynced_lib_overrides_libraries, library); + reports->resynced_lib_overrides_libraries_count++; } } - FOREACH_MAIN_LISTBASE_END; + + BLI_linklist_free(id_resync_roots->list, NULL); + BLI_ghashIterator_step(id_roots_iter); + } + BLI_ghashIterator_free(id_roots_iter); + + LISTBASE_FOREACH_MUTABLE (ID *, id_iter, &no_main_ids_list) { + BKE_id_free(bmain, id_iter); } + BLI_listbase_clear(&no_main_ids_list); + + /* Check there are no left-over IDs needing resync from the current (or higher) level of indirect + * library level. */ + FOREACH_MAIN_ID_BEGIN (bmain, id) { + const bool is_valid_tagged_need_resync = ((id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0 || + lib_override_resync_id_lib_level_is_valid( + id, library_indirect_level - 1, false)); + if (!is_valid_tagged_need_resync) { + CLOG_ERROR(&LOG, + "ID override %s from library level %d still found as needing resync, when all " + "IDs from that level should have been processed after tackling library level %d", + id->name, + id->lib != NULL ? id->lib->temp_index : 0, + library_indirect_level); + id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; + } + } + FOREACH_MAIN_ID_END; + + BLI_ghash_free(id_roots, NULL, MEM_freeN); if (do_reports_recursive_resync_timing) { reports->duration.lib_overrides_recursive_resync += PIL_check_seconds_timer() - init_time; |