BKE: Rework ID swap code to properly handle embedded ID pointers. #107044

Merged
Bastien Montagne merged 1 commits from mont29/blender:F-swap-embedded-id into main 2023-04-18 11:09:47 +02:00
6 changed files with 129 additions and 22 deletions

View File

@ -464,18 +464,27 @@ struct ID *BKE_id_copy_for_use_in_bmain(struct Main *bmain, const struct ID *id)
* Does a mere memory swap over the whole IDs data (including type-specific memory).
* \note Most internal ID data itself is not swapped (only IDProperties are).
*
* \param bmain: May be NULL, in which case there will be no remapping of internal pointers to
* itself.
* \param bmain: May be NULL, in which case there is no guarantee that internal remapping of ID
* pointers to themselves will be complete (reguarding depsgraph and/or runtime data updates).
* \param do_self_remap: Whether to remap internal pointers to itself or not.
* \param self_remap_flags: Flags controlling self remapping, see BKE_lib_remap.h.
*/
void BKE_lib_id_swap(struct Main *bmain, struct ID *id_a, struct ID *id_b);
void BKE_lib_id_swap(struct Main *bmain,
struct ID *id_a,
struct ID *id_b,
const bool do_self_remap,
const int self_remap_flags);
/**
* Does a mere memory swap over the whole IDs data (including type-specific memory).
* \note All internal ID data itself is also swapped.
*
* \param bmain: May be NULL, in which case there will be no remapping of internal pointers to
* itself.
* For parameters description, see #BKE_lib_id_swap above.
*/
void BKE_lib_id_swap_full(struct Main *bmain, struct ID *id_a, struct ID *id_b);
void BKE_lib_id_swap_full(struct Main *bmain,
struct ID *id_a,
struct ID *id_b,
const bool do_self_remap,
const int self_remap_flags);
/**
* Sort given \a id into given \a lb list, using case-insensitive comparison of the id names.

View File

@ -416,7 +416,7 @@ static int brush_undo_preserve_cb(LibraryIDLinkCallbackData *cb_data)
static void brush_undo_preserve(BlendLibReader *reader, ID *id_new, ID *id_old)
{
/* Whole Brush is preserved across undo-steps. */
BKE_lib_id_swap(nullptr, id_new, id_old);
BKE_lib_id_swap(nullptr, id_new, id_old, false, 0);
/* `id_new` now has content from `id_old`, we need to ensure those old ID pointers are valid.
* NOTE: Since we want to re-use all old pointers here, code is much simpler than for Scene. */

View File

@ -765,14 +765,38 @@ ID *BKE_id_copy_for_use_in_bmain(Main *bmain, const ID *id)
return newid;
}
static void id_embedded_swap(ID **embedded_id_a,
ID **embedded_id_b,
const bool do_full_id,
struct IDRemapper *remapper_id_a,
struct IDRemapper *remapper_id_b);
/**
* Does a mere memory swap over the whole IDs data (including type-specific memory).
* \note Most internal ID data itself is not swapped (only IDProperties are).
*/
static void id_swap(Main *bmain, ID *id_a, ID *id_b, const bool do_full_id)
static void id_swap(Main *bmain,
ID *id_a,
ID *id_b,
const bool do_full_id,
const bool do_self_remap,
struct IDRemapper *input_remapper_id_a,
struct IDRemapper *input_remapper_id_b,
const int self_remap_flags)
{
BLI_assert(GS(id_a->name) == GS(id_b->name));
struct IDRemapper *remapper_id_a = input_remapper_id_a;
struct IDRemapper *remapper_id_b = input_remapper_id_b;
if (do_self_remap) {
if (remapper_id_a == NULL) {
remapper_id_a = BKE_id_remapper_create();
}
if (remapper_id_b == NULL) {
remapper_id_b = BKE_id_remapper_create();
}
}
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id_a);
BLI_assert(id_type != NULL);
const size_t id_struct_size = id_type->struct_size;
@ -799,21 +823,87 @@ static void id_swap(Main *bmain, ID *id_a, ID *id_b, const bool do_full_id)
id_b->recalc = id_a_back.recalc;
}
if (bmain != NULL) {
/* Swap will have broken internal references to itself, restore them. */
BKE_libblock_relink_ex(bmain, id_a, id_b, id_a, ID_REMAP_SKIP_NEVER_NULL_USAGE);
BKE_libblock_relink_ex(bmain, id_b, id_a, id_b, ID_REMAP_SKIP_NEVER_NULL_USAGE);
id_embedded_swap((ID **)BKE_ntree_ptr_from_id(id_a),
(ID **)BKE_ntree_ptr_from_id(id_b),
do_full_id,
remapper_id_a,
remapper_id_b);
if (GS(id_a->name) == ID_SCE) {
Scene *scene_a = (Scene *)id_a;
Scene *scene_b = (Scene *)id_b;
id_embedded_swap((ID **)&scene_a->master_collection,
(ID **)&scene_b->master_collection,
do_full_id,
remapper_id_a,
remapper_id_b);
}
if (remapper_id_a != NULL) {
BKE_id_remapper_add(remapper_id_a, id_b, id_a);
}
if (remapper_id_b != NULL) {
BKE_id_remapper_add(remapper_id_b, id_a, id_b);
}
/* Finalize remapping of internal referrences to self broken by swapping, if requested. */
if (do_self_remap) {
LinkNode ids = {.next = NULL, .link = id_a};
BKE_libblock_relink_multiple(
bmain, &ids, ID_REMAP_TYPE_REMAP, remapper_id_a, self_remap_flags);
ids.link = id_b;
BKE_libblock_relink_multiple(
bmain, &ids, ID_REMAP_TYPE_REMAP, remapper_id_b, self_remap_flags);
}
if (input_remapper_id_a == NULL && remapper_id_a != NULL) {
BKE_id_remapper_free(remapper_id_a);
}
if (input_remapper_id_b == NULL && remapper_id_b != NULL) {
BKE_id_remapper_free(remapper_id_b);
}
}
void BKE_lib_id_swap(Main *bmain, ID *id_a, ID *id_b)
/* Conceptually, embedded IDs are part of their owner's data. However, some parts of the code
* (like e.g. the depsgraph) may treat them as independant IDs, so swapping them here and
* switching their pointers in the owner IDs allows to help not break cached relationships and
* such (by preserving the pointer values). */
static void id_embedded_swap(ID **embedded_id_a,
ID **embedded_id_b,
const bool do_full_id,
struct IDRemapper *remapper_id_a,
struct IDRemapper *remapper_id_b)
{
id_swap(bmain, id_a, id_b, false);
if (embedded_id_a != NULL && *embedded_id_a != NULL) {
BLI_assert(embedded_id_b != NULL && *embedded_id_b != NULL);
/* Do not remap internal references to itself here, since embedded IDs pointers also need to be
* potentially remapped in owner ID's data, which will also handle embedded IDs data. */
id_swap(
NULL, *embedded_id_a, *embedded_id_b, do_full_id, false, remapper_id_a, remapper_id_b, 0);
/* Manual 'remap' of owning embedded pointer in owner ID. */
SWAP(ID *, *embedded_id_a, *embedded_id_b);
/* Restore internal pointers to the swapped embedded IDs in their owners' data. This also
* includes the potential self-references inside the embedded IDs themselves. */
if (remapper_id_a != NULL) {
BKE_id_remapper_add(remapper_id_a, *embedded_id_b, *embedded_id_a);
}
if (remapper_id_b != NULL) {
BKE_id_remapper_add(remapper_id_b, *embedded_id_a, *embedded_id_b);
}
}
}
void BKE_lib_id_swap_full(Main *bmain, ID *id_a, ID *id_b)
void BKE_lib_id_swap(
Main *bmain, ID *id_a, ID *id_b, const bool do_self_remap, const int self_remap_flags)
{
id_swap(bmain, id_a, id_b, true);
id_swap(bmain, id_a, id_b, false, do_self_remap, NULL, NULL, self_remap_flags);
}
void BKE_lib_id_swap_full(
Main *bmain, ID *id_a, ID *id_b, const bool do_self_remap, const int self_remap_flags)
{
id_swap(bmain, id_a, id_b, true, do_self_remap, NULL, NULL, self_remap_flags);
}
bool id_single_user(bContext *C, ID *id, PointerRNA *ptr, PropertyRNA *prop)

View File

@ -3784,7 +3784,7 @@ void BKE_lib_override_library_main_unused_cleanup(Main *bmain)
static void lib_override_id_swap(Main *bmain, ID *id_local, ID *id_temp)
{
BKE_lib_id_swap(bmain, id_local, id_temp);
BKE_lib_id_swap(bmain, id_local, id_temp, true, 0);
/* We need to keep these tags from temp ID into orig one.
* ID swap does not swap most of ID data itself. */
id_local->tag |= (id_temp->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC);

View File

@ -130,7 +130,7 @@ static void palette_undo_preserve(BlendLibReader * /*reader*/, ID *id_new, ID *i
/* NOTE: We do not care about potential internal references to self here, Palette has none. */
/* NOTE: We do not swap IDProperties, as dealing with potential ID pointers in those would be
* fairly delicate. */
BKE_lib_id_swap(nullptr, id_new, id_old);
BKE_lib_id_swap(nullptr, id_new, id_old, false, 0);
std::swap(id_new->properties, id_old->properties);
}

View File

@ -72,6 +72,7 @@
#include "BKE_lib_id.h"
#include "BKE_lib_override.h"
#include "BKE_lib_query.h"
#include "BKE_lib_remap.h"
#include "BKE_main.h" /* for Main */
#include "BKE_main_idmap.h"
#include "BKE_material.h"
@ -3095,10 +3096,17 @@ static void read_libblock_undo_restore_at_old_address(FileData *fd, Main *main,
BLI_remlink(old_lb, id_old);
BLI_remlink(new_lb, id);
/* We do not need any remapping from this call here, since no ID pointer is valid in the data
* currently (they are all pointing to old addresses, and need to go through `lib_link`
* process). So we can pass nullptr for the Main pointer parameter. */
BKE_lib_id_swap_full(nullptr, id, id_old);
/* We do need remapping of internal pointers to the ID itself here.
*
* Passing a NULL BMain means that not all potential runtime data (like collections' parent
* pointers etc.) will be up-to-date. However, this should not be a problem here, since these
* data are re-generated later in fileread process anyway.. */
BKE_lib_id_swap_full(nullptr,
id,
id_old,
true,
ID_REMAP_SKIP_NEVER_NULL_USAGE | ID_REMAP_SKIP_UPDATE_TAGGING |
ID_REMAP_SKIP_USER_REFCOUNT);
/* Special temporary usage of this pointer, necessary for the `undo_preserve` call after
* lib-linking to restore some data that should never be affected by undo, e.g. the 3D cursor of