diff options
-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; |