main sync #3

Merged
Patrick Busch merged 318 commits from blender/blender:main into main 2023-03-17 15:52:21 +01:00
5 changed files with 193 additions and 181 deletions
Showing only changes of commit e7b0a23283 - Show all commits

View File

@ -181,18 +181,13 @@ bool BKE_scene_collections_object_remove(struct Main *bmain,
bool free_us); bool free_us);
/** /**
* Check all collections in \a bmain (including embedded ones in scenes) for CollectionObject with * Check all collections in \a bmain (including embedded ones in scenes) for invalid
* NULL object pointer, and remove them. * CollectionObject (either with NULL object pointer, or duplicates), and remove them.
*/
void BKE_collections_object_remove_nulls(struct Main *bmain);
/**
* Check all collections in \a bmain (including embedded ones in scenes) for duplicate
* CollectionObject with a same object pointer within a same object, and remove them.
* *
* NOTE: Always keeps the first of the detected duplicates. * \note In case of duplicates, the first CollectionObject in the list is kept, all others are
* removed.
*/ */
void BKE_collections_object_remove_duplicates(struct Main *bmain); void BKE_collections_object_remove_invalids(struct Main *bmain);
/** /**
* Remove all NULL children from parent collections of changed \a collection. * Remove all NULL children from parent collections of changed \a collection.

View File

@ -83,17 +83,10 @@ static bool collection_find_child_recursive(const Collection *parent,
const Collection *collection); const Collection *collection);
static void collection_gobject_hash_ensure(Collection *collection); static void collection_gobject_hash_ensure(Collection *collection);
static void collection_gobject_hash_remove_object(Collection *collection, const Object *ob);
static void collection_gobject_hash_update_object(Collection *collection, static void collection_gobject_hash_update_object(Collection *collection,
Object *ob_old, Object *ob_old,
CollectionObject *cob); CollectionObject *cob);
/** Does nothing unless #USE_DEBUG_EXTRA_GOBJECT_ASSERT is defined. */
static void collection_gobject_hash_assert_internal_consistency(Collection *collection);
#define BLI_ASSERT_COLLECION_GOBJECT_HASH_IS_VALID(collection) \
collection_gobject_hash_assert_internal_consistency(collection)
/** \} */ /** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@ -183,8 +176,16 @@ static void collection_foreach_id(ID *id, LibraryForeachIDData *data)
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, cob->ob, IDWALK_CB_USER); BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, cob->ob, IDWALK_CB_USER);
if (collection->runtime.gobject_hash) { if (collection->runtime.gobject_hash) {
/* If the remapping does not create inconsistent data (NULL object pointer or duplicate
* CollectionObjects), keeping the ghash consistent is also possible. Otherwise, this call
* will take care of tagging the collection objects list as dirty. */
collection_gobject_hash_update_object(collection, cob_ob_old, cob); collection_gobject_hash_update_object(collection, cob_ob_old, cob);
} }
else if (cob_ob_old != cob->ob || cob->ob == NULL) {
/* If there is no reference GHash, duplicates cannot be reliably detected, so assume that any
* NULL pointer or changed pointer may create an invalid collection object list. */
collection->runtime.tag |= COLLECTION_TAG_COLLECTION_OBJECT_DIRTY;
}
} }
LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { LISTBASE_FOREACH (CollectionChild *, child, &collection->children) {
BKE_LIB_FOREACHID_PROCESS_IDSUPER( BKE_LIB_FOREACHID_PROCESS_IDSUPER(
@ -983,7 +984,6 @@ bool BKE_collection_has_object(Collection *collection, const Object *ob)
if (ELEM(NULL, collection, ob)) { if (ELEM(NULL, collection, ob)) {
return false; return false;
} }
BLI_ASSERT_COLLECION_GOBJECT_HASH_IS_VALID(collection);
collection_gobject_hash_ensure(collection); collection_gobject_hash_ensure(collection);
return BLI_ghash_lookup(collection->runtime.gobject_hash, ob); return BLI_ghash_lookup(collection->runtime.gobject_hash, ob);
} }
@ -1053,6 +1053,171 @@ bool BKE_collection_is_empty(const Collection *collection)
/** \name Collection Objects /** \name Collection Objects
* \{ */ * \{ */
static void collection_gobject_assert_internal_consistency(Collection *collection,
const bool do_extensive_check);
static GHash *collection_gobject_hash_alloc(const Collection *collection)
{
return BLI_ghash_ptr_new_ex(__func__, (uint)BLI_listbase_count(&collection->gobject));
}
static void collection_gobject_hash_create(Collection *collection)
{
GHash *gobject_hash = collection_gobject_hash_alloc(collection);
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
if (UNLIKELY(cob->ob == NULL)) {
BLI_assert(collection->runtime.tag & COLLECTION_TAG_COLLECTION_OBJECT_DIRTY);
continue;
}
CollectionObject **cob_p;
/* Do not overwrite an already existing entry. */
if (UNLIKELY(BLI_ghash_ensure_p(gobject_hash, cob->ob, (void ***)&cob_p))) {
BLI_assert(collection->runtime.tag & COLLECTION_TAG_COLLECTION_OBJECT_DIRTY);
continue;
}
*cob_p = cob;
}
collection->runtime.gobject_hash = gobject_hash;
}
static void collection_gobject_hash_ensure(Collection *collection)
{
if (collection->runtime.gobject_hash) {
#ifdef USE_DEBUG_EXTRA_GOBJECT_ASSERT
collection_gobject_assert_internal_consistency(collection, true);
#endif
return;
}
collection_gobject_hash_create(collection);
collection_gobject_assert_internal_consistency(collection, true);
}
/** Similar to #collection_gobject_hash_ensure/#collection_gobject_hash_create, but does fix
* inconsistencies in the collection objects list. */
static void collection_gobject_hash_ensure_fix(Collection *collection)
{
bool changed = false;
if ((collection->runtime.tag & COLLECTION_TAG_COLLECTION_OBJECT_DIRTY) == 0) {
#ifdef USE_DEBUG_EXTRA_GOBJECT_ASSERT
collection_gobject_assert_internal_consistency(collection, true);
#endif
return;
}
GHash *gobject_hash = collection->runtime.gobject_hash;
if (gobject_hash) {
BLI_ghash_clear_ex(gobject_hash, NULL, NULL, BLI_ghash_len(gobject_hash));
}
else {
collection->runtime.gobject_hash = gobject_hash = collection_gobject_hash_alloc(collection);
}
LISTBASE_FOREACH_MUTABLE (CollectionObject *, cob, &collection->gobject) {
if (cob->ob == NULL) {
BLI_freelinkN(&collection->gobject, cob);
changed = true;
continue;
}
CollectionObject **cob_p;
if (BLI_ghash_ensure_p(gobject_hash, cob->ob, (void ***)&cob_p)) {
BLI_freelinkN(&collection->gobject, cob);
changed = true;
continue;
}
*cob_p = cob;
}
if (changed) {
BKE_collection_object_cache_free(collection);
}
collection->runtime.tag &= ~COLLECTION_TAG_COLLECTION_OBJECT_DIRTY;
collection_gobject_assert_internal_consistency(collection, true);
}
/**
* Update the collections object hash, removing `ob_old`, inserting `cob->ob` as the new key.
*
* \note This function is called fron foreach_id callback, and a difference of Object pointers is
* only expected in case ID remapping is happening. This code is the only are in Blender allowed to
* (temporarily) leave the CollectionObject list in an inconsistent/invalid state (with NULL object
* pointers, or duplicates of CollectionObjects). If such invalid cases are encountered, it will
* tag the collection objects list as dirty.
*
* \param ob_old: The existing key to `cob` in the hash, not removed when NULL.
* \param cob: The `cob->ob` is to be used as the new key,
* when NULL it's not added back into the hash.
*/
static void collection_gobject_hash_update_object(Collection *collection,
Object *ob_old,
CollectionObject *cob)
{
if (ob_old == cob->ob) {
return;
}
if (ob_old) {
CollectionObject *cob_old = BLI_ghash_popkey(collection->runtime.gobject_hash, ob_old, NULL);
if (cob_old != cob) {
/* Old object alredy removed from the ghash. */
collection->runtime.tag |= COLLECTION_TAG_COLLECTION_OBJECT_DIRTY;
}
}
if (cob->ob) {
CollectionObject **cob_p;
if (!BLI_ghash_ensure_p(collection->runtime.gobject_hash, cob->ob, (void ***)&cob_p)) {
*cob_p = cob;
}
else {
/* Duplicate CollectionObject entries. */
collection->runtime.tag |= COLLECTION_TAG_COLLECTION_OBJECT_DIRTY;
}
}
else {
/* CollectionObject with NULL objetc pointer. */
collection->runtime.tag |= COLLECTION_TAG_COLLECTION_OBJECT_DIRTY;
}
}
/**
* Validate the integrity of the collection's CollectionObject list, and of its mapping.
*
* Simple test is very fast, as it only checks that the 'dirty' tag for collection's objects is not
* set.
*
* The extensive check is expensive. This should not be done from within loops over collections
* items, or from low-level operations that can be assumed safe (like adding or removing an object
* from a colleciton). It ensures that:
* - There is a `gobject_hash` mapping.
* - There is no NULL-object CollectionObject items.
* - there is no duplicate CollectionObject items (two or more referencing the same Object).
*/
static void collection_gobject_assert_internal_consistency(Collection *collection,
const bool do_extensive_check)
{
BLI_assert((collection->runtime.tag & COLLECTION_TAG_COLLECTION_OBJECT_DIRTY) == 0);
if (!do_extensive_check) {
return;
}
if (collection->runtime.gobject_hash == NULL) {
/* NOTE: If the ghash does not exist yet, it's creation will assert on errors, so in theory the
* second loop below could be skipped. */
collection_gobject_hash_create(collection);
}
GHash *gobject_hash = collection->runtime.gobject_hash;
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
BLI_assert(cob->ob != NULL);
/* If there are more than one CollectionObject for the same object, at most one of them will
* pass this test. */
BLI_assert(BLI_ghash_lookup(gobject_hash, cob->ob) == cob);
}
}
static void collection_tag_update_parent_recursive(Main *bmain, static void collection_tag_update_parent_recursive(Main *bmain,
Collection *collection, Collection *collection,
const int flag) const int flag)
@ -1336,83 +1501,14 @@ bool BKE_scene_collections_object_remove(Main *bmain, Scene *scene, Object *ob,
return scene_collections_object_remove(bmain, scene, ob, free_us, NULL); return scene_collections_object_remove(bmain, scene, ob, free_us, NULL);
} }
/* void BKE_collections_object_remove_invalids(Main *bmain)
* Remove all NULL objects from collections.
* This is used for library remapping, where these pointers have been set to NULL.
* Otherwise this should never happen.
*/
static void collection_object_remove_nulls(Collection *collection)
{
bool changed = false;
LISTBASE_FOREACH_MUTABLE (CollectionObject *, cob, &collection->gobject) {
if (cob->ob == NULL) {
BLI_freelinkN(&collection->gobject, cob);
changed = true;
}
}
if (changed) {
BKE_collection_object_cache_free(collection);
}
}
void BKE_collections_object_remove_nulls(Main *bmain)
{ {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
collection_object_remove_nulls(scene->master_collection); collection_gobject_hash_ensure_fix(scene->master_collection);
} }
LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
collection_object_remove_nulls(collection); collection_gobject_hash_ensure_fix(collection);
}
}
/*
* Remove all duplicate objects from collections.
* This is used for library remapping, happens when remapping an object to another one already
* present in the collection. Otherwise this should never happen.
*/
static void collection_object_remove_duplicates(Collection *collection)
{
bool changed = false;
BLI_ASSERT_COLLECION_GOBJECT_HASH_IS_VALID(collection);
const bool use_hash_exists = (collection->runtime.gobject_hash != NULL);
LISTBASE_FOREACH_MUTABLE (CollectionObject *, cob, &collection->gobject) {
if (cob->ob->runtime.collection_management) {
if (use_hash_exists) {
collection_gobject_hash_remove_object(collection, cob->ob);
}
BLI_freelinkN(&collection->gobject, cob);
changed = true;
continue;
}
cob->ob->runtime.collection_management = true;
}
/* Cleanup. */
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
cob->ob->runtime.collection_management = false;
}
if (changed) {
BKE_collection_object_cache_free(collection);
}
}
void BKE_collections_object_remove_duplicates(struct Main *bmain)
{
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
ob->runtime.collection_management = false;
}
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
collection_object_remove_duplicates(scene->master_collection);
}
LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
collection_object_remove_duplicates(collection);
} }
} }
@ -1646,79 +1742,6 @@ static CollectionParent *collection_find_parent(Collection *child, Collection *c
return BLI_findptr(&child->runtime.parents, collection, offsetof(CollectionParent, collection)); return BLI_findptr(&child->runtime.parents, collection, offsetof(CollectionParent, collection));
} }
static void collection_gobject_hash_ensure(Collection *collection)
{
if (collection->runtime.gobject_hash) {
BLI_ASSERT_COLLECION_GOBJECT_HASH_IS_VALID(collection);
return;
}
GHash *gobject_hash = BLI_ghash_ptr_new_ex(__func__, BLI_listbase_count(&collection->gobject));
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
BLI_ghash_insert(gobject_hash, cob->ob, cob);
}
collection->runtime.gobject_hash = gobject_hash;
BLI_ASSERT_COLLECION_GOBJECT_HASH_IS_VALID(collection);
}
static void collection_gobject_hash_remove_object(Collection *collection, const Object *ob)
{
const bool found = BLI_ghash_remove(collection->runtime.gobject_hash, ob, NULL, NULL);
BLI_assert(found);
UNUSED_VARS_NDEBUG(found);
}
/**
* Update the collections object hash, removing `ob_old`, inserting `cob->ob` as the new key.
*
* \param ob_old: The existing key to `cob` in the hash, not removed when NULL.
* \param cob: The `cob->ob` is to be used as the new key,
* when NULL it's not added back into the hash.
*/
static void collection_gobject_hash_update_object(Collection *collection,
Object *ob_old,
CollectionObject *cob)
{
if (ob_old == cob->ob) {
return;
}
BLI_ASSERT_COLLECION_GOBJECT_HASH_IS_VALID(collection);
if (ob_old) {
collection_gobject_hash_remove_object(collection, ob_old);
}
/* The object may be set to NULL if the ID is being cleared from #collection_foreach_id,
* generally `cob->ob` is not expected to be NULL. */
if (cob->ob) {
BLI_ghash_insert(collection->runtime.gobject_hash, cob->ob, cob);
}
}
/**
* Should only be called by: #BLI_ASSERT_COLLECION_GOBJECT_HASH_IS_VALID macro,
* this is an expensive operation intended only to be used for debugging.
*/
static void collection_gobject_hash_assert_internal_consistency(Collection *collection)
{
#ifdef USE_DEBUG_EXTRA_GOBJECT_ASSERT
if (collection->runtime.gobject_hash == NULL) {
return;
}
GHash *gobject_hash = collection->runtime.gobject_hash;
int gobject_count = 0;
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
CollectionObject *cob_test = BLI_ghash_lookup(gobject_hash, cob->ob);
BLI_assert(cob == cob_test);
gobject_count += 1;
}
const int gobject_hash_count = BLI_ghash_len(gobject_hash);
BLI_assert(gobject_count == gobject_hash_count);
#else
UNUSED_VARS(collection);
#endif /* USE_DEBUG_EXTRA_GOBJECT_ASSERT */
}
static bool collection_child_add(Collection *parent, static bool collection_child_add(Collection *parent,
Collection *collection, Collection *collection,
const int flag, const int flag,

View File

@ -325,20 +325,12 @@ static void libblock_remap_data_preprocess(ID *id_owner,
*/ */
static void libblock_remap_data_postprocess_object_update(Main *bmain, static void libblock_remap_data_postprocess_object_update(Main *bmain,
Object *old_ob, Object *old_ob,
Object *new_ob, Object *UNUSED(new_ob),
const bool do_sync_collection) const bool do_sync_collection)
{ {
if (new_ob == NULL) { /* Will only effectively process collections that have been tagged with
/* In case we unlinked old_ob (new_ob is NULL), the object has already * #COLLECTION_TAG_COLLECTION_OBJECT_DIRTY. See #collection_foreach_id callback. */
* been removed from the scenes and their collections. We still have BKE_collections_object_remove_invalids(bmain);
* to remove the NULL children from collections not used in any scene. */
BKE_collections_object_remove_nulls(bmain);
}
else {
/* Remapping may have created duplicates of CollectionObject pointing to the same object within
* the same collection. */
BKE_collections_object_remove_duplicates(bmain);
}
if (do_sync_collection) { if (do_sync_collection) {
BKE_main_collection_sync_remap(bmain); BKE_main_collection_sync_remap(bmain);

View File

@ -128,6 +128,13 @@ enum {
* Using a generic tag like #LIB_TAG_DOIT for this is just impossible, we need our very own. * Using a generic tag like #LIB_TAG_DOIT for this is just impossible, we need our very own.
*/ */
COLLECTION_TAG_RELATION_REBUILD = (1 << 0), COLLECTION_TAG_RELATION_REBUILD = (1 << 0),
/**
* Mark the `gobject` list and/or its `runtime.gobject_hash` mapping as dirty, i.e. that their
* data is not reliable and should be cleaned-up or updated.
*
* This should typically only be set by ID remapping code.
*/
COLLECTION_TAG_COLLECTION_OBJECT_DIRTY = (1 << 1),
}; };
/** #Collection.color_tag */ /** #Collection.color_tag */

View File

@ -113,12 +113,7 @@ typedef struct Object_Runtime {
/** Did last modifier stack generation need mapping support? */ /** Did last modifier stack generation need mapping support? */
char last_need_mapping; char last_need_mapping;
/** Opaque data reserved for management of objects in collection context. char _pad0[3];
* E.g. used currently to check for potential duplicates of objects in a collection, after
* remapping process. */
char collection_management;
char _pad0[2];
/** Only used for drawing the parent/child help-line. */ /** Only used for drawing the parent/child help-line. */
float parent_display_origin[3]; float parent_display_origin[3];