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
This commit is contained in:
Bastien Montagne 2022-02-11 15:18:01 +01:00 committed by Bastien Montagne
parent e6f3bb984b
commit 1695d38989
Notes: blender-bot 2023-09-13 08:48:34 +02:00
Referenced by issue #99272, Regression: location override ignored when used in a shadertree (Texture Coordinates node, object coordinates)
Referenced by issue #96864, Overrides reverting after reopening file
1 changed files with 401 additions and 207 deletions

View File

@ -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;
/* 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;
}
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;
}
/* 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;
FOREACH_MAIN_ID_END;
/* Code above may have added some tags, we need to update this too. */
/* 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);
}
/* 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,89 +1727,37 @@ 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.
*
* WARNING: Expects `bmain` to have valid relation data.
*
* NOTE: Related to `lib_override_library_main_resync_find_root_recurse` below.
*
* 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.
*/
static void lib_override_resync_tagging_finalize_recurse(Main *bmain,
ID *id,
const int library_indirect_level)
static bool lib_override_resync_id_lib_level_is_valid(ID *id,
const int library_indirect_level,
const bool do_strict_equal)
{
if (id->lib != NULL && id->lib->temp_index > library_indirect_level) {
CLOG_ERROR(
&LOG,
"While processing indirect level %d, ID %s from lib %s of indirect level %d detected "
"as needing resync, skipping.",
library_indirect_level,
id->name,
id->lib->filepath,
id->lib->temp_index);
id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC;
return;
}
MainIDRelationsEntry *entry = BLI_ghash_lookup(bmain->relations->relations_from_pointers, id);
BLI_assert(entry != NULL);
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) {
/* This ID has already been processed. */
return;
}
/* This way we won't process again that ID, should we encounter it again through another
* relationship hierarchy. */
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
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);
}
}
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;
}
/* 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.
*/
/* 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)) {
@ -1753,6 +1774,140 @@ static ID *lib_override_library_main_resync_root_get(Main *bmain, ID *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.
*
* Returns `true` if it finds an ancestor within the current liboverride hierarchy also tagged as
* needing resync, `false` otherwise.
*
* 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 bool lib_override_resync_tagging_finalize_recurse(
Main *bmain, ID *id, GHash *id_roots, const int library_indirect_level, const bool check_only)
{
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 "
"as needing resync, skipping.",
library_indirect_level,
id->name,
id->lib->filepath,
id->lib->temp_index);
id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC;
return false;
}
MainIDRelationsEntry *entry = BLI_ghash_lookup(bmain->relations->relations_from_pointers, id);
BLI_assert(entry != NULL);
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) {
/* This ID has already been processed. */
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;
/* 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);
}
}
}
/* 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_linklist_append(*id_resync_roots_p, id);
is_ancestor_tagged_for_resync = true;
}
return is_ancestor_tagged_for_resync;
}
/* Ensure resync of all overrides at one level of indirect usage.
*
* We need to handle each level independently, since an override at level n may be affected by
@ -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;