blendfile write: Fix handling of embedded IDs. #106935

Closed
Bastien Montagne wants to merge 1 commits from mont29:F-fix-embedded-id-write into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
9 changed files with 208 additions and 80 deletions

View File

@ -137,8 +137,13 @@ static void light_blend_write(BlendWriter *writer, ID *id, const void *id_addres
/* Node-tree is integral part of lights, no libdata. */
if (la->nodetree) {
BLO_write_struct(writer, bNodeTree, la->nodetree);
ntreeBlendWrite(writer, la->nodetree);
BLO_Write_IDBuffer *temp_embedded_id_buffer = BLO_write_allocate_id_buffer();
BLO_write_init_id_buffer_from_id(
temp_embedded_id_buffer, &la->nodetree->id, BLO_write_is_undo(writer));
BLO_write_struct_at_address(
writer, bNodeTree, la->nodetree, BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer));
ntreeBlendWrite(writer, (bNodeTree *)BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer));
BLO_write_destroy_id_buffer(&temp_embedded_id_buffer);
}
BKE_previewimg_blend_write(writer, la->preview);

View File

@ -463,8 +463,17 @@ static void linestyle_blend_write(BlendWriter *writer, ID *id, const void *id_ad
}
}
if (linestyle->nodetree) {
BLO_write_struct(writer, bNodeTree, linestyle->nodetree);
ntreeBlendWrite(writer, linestyle->nodetree);
BLO_Write_IDBuffer *temp_embedded_id_buffer = BLO_write_allocate_id_buffer();
BLO_write_init_id_buffer_from_id(
temp_embedded_id_buffer, &linestyle->nodetree->id, BLO_write_is_undo(writer));
BLO_write_struct_at_address(writer,
bNodeTree,
linestyle->nodetree,
BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer));
ntreeBlendWrite(
writer,
reinterpret_cast<bNodeTree *>(BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer)));
BLO_write_destroy_id_buffer(&temp_embedded_id_buffer);
}
}

View File

@ -185,8 +185,15 @@ static void material_blend_write(BlendWriter *writer, ID *id, const void *id_add
/* nodetree is integral part of material, no libdata */
if (ma->nodetree) {
BLO_write_struct(writer, bNodeTree, ma->nodetree);
ntreeBlendWrite(writer, ma->nodetree);
BLO_Write_IDBuffer *temp_embedded_id_buffer = BLO_write_allocate_id_buffer();
BLO_write_init_id_buffer_from_id(
temp_embedded_id_buffer, &ma->nodetree->id, BLO_write_is_undo(writer));
BLO_write_struct_at_address(
writer, bNodeTree, ma->nodetree, BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer));
ntreeBlendWrite(
writer,
reinterpret_cast<bNodeTree *>(BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer)));
BLO_write_destroy_id_buffer(&temp_embedded_id_buffer);
}
BKE_previewimg_blend_write(writer, ma->preview);

View File

@ -947,6 +947,8 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres
{
Scene *sce = (Scene *)id;
BLO_Write_IDBuffer *temp_embedded_id_buffer = BLO_write_allocate_id_buffer();
if (BLO_write_is_undo(writer)) {
/* Clean up, important in undo case to reduce false detection of changed data-blocks. */
/* XXX This UI data should not be stored in Scene at all... */
@ -1074,8 +1076,15 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres
}
if (sce->nodetree) {
BLO_write_struct(writer, bNodeTree, sce->nodetree);
ntreeBlendWrite(writer, sce->nodetree);
BLO_write_init_id_buffer_from_id(
temp_embedded_id_buffer, &sce->nodetree->id, BLO_write_is_undo(writer));
BLO_write_struct_at_address(writer,
bNodeTree,
sce->nodetree,
BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer));
ntreeBlendWrite(
writer,
reinterpret_cast<bNodeTree *>(BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer)));
}
BKE_color_managed_view_settings_blend_write(writer, &sce->view_settings);
@ -1102,8 +1111,15 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres
}
if (sce->master_collection) {
BLO_write_struct(writer, Collection, sce->master_collection);
BKE_collection_blend_write_nolib(writer, sce->master_collection);
BLO_write_init_id_buffer_from_id(
temp_embedded_id_buffer, &sce->master_collection->id, BLO_write_is_undo(writer));
BLO_write_struct_at_address(writer,
Collection,
sce->master_collection,
BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer));
BKE_collection_blend_write_nolib(
writer,
reinterpret_cast<Collection *>(BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer)));
}
/* Eevee Light-cache */
@ -1116,6 +1132,8 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres
/* Freed on `do_versions()`. */
BLI_assert(sce->layer_properties == nullptr);
BLO_write_destroy_id_buffer(&temp_embedded_id_buffer);
}
static void direct_link_paint_helper(BlendDataReader *reader, const Scene *scene, Paint **paint)

View File

@ -107,8 +107,17 @@ static void simulation_blend_write(BlendWriter *writer, ID *id, const void *id_a
/* nodetree is integral part of simulation, no libdata */
if (simulation->nodetree) {
BLO_write_struct(writer, bNodeTree, simulation->nodetree);
ntreeBlendWrite(writer, simulation->nodetree);
BLO_Write_IDBuffer *temp_embedded_id_buffer = BLO_write_allocate_id_buffer();
BLO_write_init_id_buffer_from_id(
temp_embedded_id_buffer, &simulation->nodetree->id, BLO_write_is_undo(writer));
BLO_write_struct_at_address(writer,
bNodeTree,
simulation->nodetree,
BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer));
ntreeBlendWrite(
writer,
reinterpret_cast<bNodeTree *>(BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer)));
BLO_write_destroy_id_buffer(&temp_embedded_id_buffer);
}
}

View File

@ -155,8 +155,17 @@ static void texture_blend_write(BlendWriter *writer, ID *id, const void *id_addr
/* nodetree is integral part of texture, no libdata */
if (tex->nodetree) {
BLO_write_struct(writer, bNodeTree, tex->nodetree);
ntreeBlendWrite(writer, tex->nodetree);
BLO_Write_IDBuffer *temp_embedded_id_buffer = BLO_write_allocate_id_buffer();
BLO_write_init_id_buffer_from_id(
temp_embedded_id_buffer, &tex->nodetree->id, BLO_write_is_undo(writer));
BLO_write_struct_at_address(writer,
bNodeTree,
tex->nodetree,
BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer));
ntreeBlendWrite(
writer,
reinterpret_cast<bNodeTree *>(BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer)));
BLO_write_destroy_id_buffer(&temp_embedded_id_buffer);
}
BKE_previewimg_blend_write(writer, tex->preview);

View File

@ -144,8 +144,15 @@ static void world_blend_write(BlendWriter *writer, ID *id, const void *id_addres
/* nodetree is integral part of world, no libdata */
if (wrld->nodetree) {
BLO_write_struct(writer, bNodeTree, wrld->nodetree);
ntreeBlendWrite(writer, wrld->nodetree);
BLO_Write_IDBuffer *temp_embedded_id_buffer = BLO_write_allocate_id_buffer();
BLO_write_init_id_buffer_from_id(
temp_embedded_id_buffer, &wrld->nodetree->id, BLO_write_is_undo(writer));
BLO_write_struct_at_address(writer,
bNodeTree,
wrld->nodetree,
BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer));
ntreeBlendWrite(writer, (bNodeTree *)BLO_write_get_id_buffer_temp_id(temp_embedded_id_buffer));
BLO_write_destroy_id_buffer(&temp_embedded_id_buffer);
}
BKE_previewimg_blend_write(writer, wrld->preview);

View File

@ -157,6 +157,23 @@ void blo_write_id_struct(BlendWriter *writer,
#define BLO_write_id_struct(writer, struct_name, id_address, id) \
blo_write_id_struct(writer, BLO_get_struct_id(writer, struct_name), id_address, id)
/**
* Specific code to prepare IDs to be written.
*
* Required for writing properly embedded IDs currently.
*
* \note: Once there is a better generic handling of embedded IDs, this may go back to private code
* in writefile.c
*/
typedef struct BLO_Write_IDBuffer BLO_Write_IDBuffer;
BLO_Write_IDBuffer *BLO_write_allocate_id_buffer(void);
void BLO_write_init_id_buffer_from_id(BLO_Write_IDBuffer *id_buffer,
struct ID *id,
const bool is_undo);
struct ID *BLO_write_get_id_buffer_temp_id(BLO_Write_IDBuffer *id_buffer);
void BLO_write_destroy_id_buffer(BLO_Write_IDBuffer **id_buffer);
/**
* Write raw data.
*/

View File

@ -1088,6 +1088,78 @@ static void write_thumb(WriteData *wd, const BlendThumbnail *thumb)
/** \name File Writing (Private)
* \{ */
#define ID_BUFFER_STATIC_SIZE 8192
typedef struct BLO_Write_IDBuffer {
const struct IDTypeInfo *id_type;
ID *temp_id;
char id_buffer_static[ID_BUFFER_STATIC_SIZE];
} BLO_Write_IDBuffer;
static void id_buffer_init_for_id_type(BLO_Write_IDBuffer *id_buffer, const IDTypeInfo *id_type)
{
if (id_type != id_buffer->id_type) {
const size_t idtype_struct_size = id_type->struct_size;
if (idtype_struct_size > ID_BUFFER_STATIC_SIZE) {
CLOG_ERROR(&LOG,
"ID maximum buffer size (%d bytes) is not big enough to fit IDs of type %s, "
"which needs %lu bytes",
ID_BUFFER_STATIC_SIZE,
id_type->name,
idtype_struct_size);
id_buffer->temp_id = static_cast<ID *>(MEM_mallocN(idtype_struct_size, __func__));
}
else {
if (static_cast<void *>(id_buffer->temp_id) != id_buffer->id_buffer_static) {
MEM_SAFE_FREE(id_buffer->temp_id);
}
id_buffer->temp_id = reinterpret_cast<ID *>(id_buffer->id_buffer_static);
}
id_buffer->id_type = id_type;
}
}
static void id_buffer_init_from_id(BLO_Write_IDBuffer *id_buffer, ID *id, const bool is_undo)
{
BLI_assert(id_buffer->id_type == BKE_idtype_get_info_from_id(id));
if (is_undo) {
/* Record the changes that happened up to this undo push in
* recalc_up_to_undo_push, and clear `recalc_after_undo_push` again
* to start accumulating for the next undo push. */
id->recalc_up_to_undo_push = id->recalc_after_undo_push;
id->recalc_after_undo_push = 0;
}
/* Copy ID data itself into buffer, to be able to freely modify it. */
const size_t idtype_struct_size = id_buffer->id_type->struct_size;
ID *temp_id = id_buffer->temp_id;
memcpy(temp_id, id, idtype_struct_size);
/* Clear runtime data to reduce false detection of changed data in undo/redo context. */
if (is_undo) {
temp_id->tag &= LIB_TAG_KEEP_ON_UNDO;
}
else {
temp_id->tag = 0;
}
temp_id->us = 0;
temp_id->icon_id = 0;
/* Those listbase data change every time we add/remove an ID, and also often when
* renaming one (due to re-sorting). This avoids generating a lot of false 'is changed'
* detections between undo steps. */
temp_id->prev = nullptr;
temp_id->next = nullptr;
/* Those runtime pointers should never be set during writing stage, but just in case clear
* them too. */
temp_id->orig_id = nullptr;
temp_id->newid = nullptr;
/* Even though in theory we could be able to preserve this python instance across undo even
* when we need to re-read the ID into its original address, this is currently cleared in
* #direct_link_id_common in `readfile.c` anyway. */
temp_id->py_instance = nullptr;
}
/* Helper callback for checking linked IDs used by given ID (assumed local), to ensure directly
* linked data is tagged accordingly. */
static int write_id_direct_linked_data_process_cb(LibraryIDLinkCallbackData *cb_data)
@ -1187,11 +1259,11 @@ static bool write_file_handle(Main *mainvar,
nullptr :
BKE_lib_override_library_operations_store_init();
#define ID_BUFFER_STATIC_SIZE 8192
/* This outer loop allows to save first data-blocks from real mainvar,
* then the temp ones from override process,
* if needed, without duplicating whole code. */
Main *bmain = mainvar;
BLO_Write_IDBuffer *id_buffer = BLO_write_allocate_id_buffer();
do {
ListBase *lbarray[INDEX_ID_MAX];
int a = set_listbasepointers(bmain, lbarray);
@ -1202,19 +1274,8 @@ static bool write_file_handle(Main *mainvar,
continue; /* Libraries are handled separately below. */
}
char id_buffer_static[ID_BUFFER_STATIC_SIZE];
void *id_buffer = id_buffer_static;
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
const size_t idtype_struct_size = id_type->struct_size;
if (idtype_struct_size > ID_BUFFER_STATIC_SIZE) {
CLOG_ERROR(&LOG,
"ID maximum buffer size (%d bytes) is not big enough to fit IDs of type %s, "
"which needs %lu bytes",
ID_BUFFER_STATIC_SIZE,
id_type->name,
id_type->struct_size);
id_buffer = MEM_mallocN(idtype_struct_size, __func__);
}
id_buffer_init_for_id_type(id_buffer, id_type);
for (; id; id = static_cast<ID *>(id->next)) {
/* We should never attempt to write non-regular IDs
@ -1249,57 +1310,12 @@ static bool write_file_handle(Main *mainvar,
BKE_lib_override_library_operations_store_start(bmain, override_storage, id);
}
if (wd->use_memfile) {
/* Record the changes that happened up to this undo push in
* recalc_up_to_undo_push, and clear `recalc_after_undo_push` again
* to start accumulating for the next undo push. */
id->recalc_up_to_undo_push = id->recalc_after_undo_push;
id->recalc_after_undo_push = 0;
bNodeTree *nodetree = ntreeFromID(id);
if (nodetree != nullptr) {
nodetree->id.recalc_up_to_undo_push = nodetree->id.recalc_after_undo_push;
nodetree->id.recalc_after_undo_push = 0;
}
if (GS(id->name) == ID_SCE) {
Scene *scene = (Scene *)id;
if (scene->master_collection != nullptr) {
scene->master_collection->id.recalc_up_to_undo_push =
scene->master_collection->id.recalc_after_undo_push;
scene->master_collection->id.recalc_after_undo_push = 0;
}
}
}
mywrite_id_begin(wd, id);
memcpy(id_buffer, id, idtype_struct_size);
/* Clear runtime data to reduce false detection of changed data in undo/redo context. */
if (wd->use_memfile) {
((ID *)id_buffer)->tag &= LIB_TAG_KEEP_ON_UNDO;
}
else {
((ID *)id_buffer)->tag = 0;
}
((ID *)id_buffer)->us = 0;
((ID *)id_buffer)->icon_id = 0;
/* Those listbase data change every time we add/remove an ID, and also often when
* renaming one (due to re-sorting). This avoids generating a lot of false 'is changed'
* detections between undo steps. */
((ID *)id_buffer)->prev = nullptr;
((ID *)id_buffer)->next = nullptr;
/* Those runtime pointers should never be set during writing stage, but just in case clear
* them too. */
((ID *)id_buffer)->orig_id = nullptr;
((ID *)id_buffer)->newid = nullptr;
/* Even though in theory we could be able to preserve this python instance across undo even
* when we need to re-read the ID into its original address, this is currently cleared in
* #direct_link_id_common in `readfile.c` anyway, */
((ID *)id_buffer)->py_instance = nullptr;
id_buffer_init_from_id(id_buffer, id, wd->use_memfile);
if (id_type->blend_write != nullptr) {
id_type->blend_write(&writer, (ID *)id_buffer, id);
id_type->blend_write(&writer, static_cast<ID *>(id_buffer->temp_id), id);
}
if (do_override) {
@ -1309,14 +1325,12 @@ static bool write_file_handle(Main *mainvar,
mywrite_id_end(wd, id);
}
if (id_buffer != id_buffer_static) {
MEM_SAFE_FREE(id_buffer);
}
mywrite_flush(wd);
}
} while ((bmain != override_storage) && (bmain = override_storage));
BLO_write_destroy_id_buffer(&id_buffer);
if (override_storage) {
BKE_lib_override_library_operations_store_finalize(override_storage);
override_storage = nullptr;
@ -1571,6 +1585,39 @@ bool BLO_write_file_mem(Main *mainvar, MemFile *compare, MemFile *current, int w
return (err == 0);
}
/*
* API to handle writing IDs while clearing some of their runtime data.
*/
BLO_Write_IDBuffer *BLO_write_allocate_id_buffer()
{
return MEM_cnew<BLO_Write_IDBuffer>(__func__);
}
void BLO_write_init_id_buffer_from_id(BLO_Write_IDBuffer *id_buffer, ID *id, const bool is_undo)
{
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
id_buffer_init_for_id_type(id_buffer, id_type);
id_buffer_init_from_id(id_buffer, id, is_undo);
}
ID *BLO_write_get_id_buffer_temp_id(BLO_Write_IDBuffer *id_buffer)
{
return id_buffer->temp_id;
}
void BLO_write_destroy_id_buffer(BLO_Write_IDBuffer **id_buffer)
{
if (static_cast<void *>((*id_buffer)->temp_id) != (*id_buffer)->id_buffer_static) {
MEM_SAFE_FREE((*id_buffer)->temp_id);
}
MEM_SAFE_FREE(*id_buffer);
}
/*
* API to write chunks of data.
*/
void BLO_write_raw(BlendWriter *writer, size_t size_in_bytes, const void *data_ptr)
{
writedata(writer->wd, BLO_CODE_DATA, size_in_bytes, data_ptr);