WIP: Core: experiment for embedding linked data-blocks #128545

Draft
Jacques Lucke wants to merge 5 commits from JacquesLucke/blender:embedded-linked into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
6 changed files with 204 additions and 13 deletions

View File

@ -304,7 +304,7 @@ static bool library_foreach_ID_link(Main *bmain,
* In that case, we do not want to generate those 'generic flags' from our current sub-data ID
* (the node tree), but re-use those generated for the 'owner' ID (the material). */
if (inherit_data == nullptr) {
data.cb_flag = ID_IS_LINKED(id) ? IDWALK_CB_INDIRECT_USAGE : 0;
data.cb_flag = ID_IS_DYNAMIC_LINKED(id) ? IDWALK_CB_INDIRECT_USAGE : 0;
/* When an ID is defined as not reference-counting its ID usages, it should never do it. */
data.cb_flag_clear = (id->tag & ID_TAG_NO_USER_REFCOUNT) ?
IDWALK_CB_USER | IDWALK_CB_USER_ONE :

View File

@ -58,6 +58,7 @@
#include "BLI_map.hh"
#include "BLI_memarena.h"
#include "BLI_mempool.h"
#include "BLI_multi_value_map.hh"
#include "BLI_threads.h"
#include "BLI_time.h"
@ -2866,7 +2867,9 @@ static BHead *read_libblock(FileData *fd,
BHead *bhead,
int id_tag,
const bool placeholder_set_indirect_extern,
ID **r_id)
ID **r_id,
blender::MultiValueMap<const void *, ID *>
*r_static_linked_data_blocks_by_old_library = nullptr)
{
const bool do_partial_undo = (fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0;
@ -2957,6 +2960,10 @@ static BHead *read_libblock(FileData *fd,
return blo_bhead_next(fd, bhead);
}
if (!main->curlib && id->lib && r_static_linked_data_blocks_by_old_library) {
r_static_linked_data_blocks_by_old_library->add(id->lib, id);
}
/* Read datablock contents.
* Use convenient malloc name for debugging and better memory link prints. */
bhead = read_data_into_datamap(fd, bhead, blockname, id_type_index);
@ -3578,6 +3585,9 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
read_undo_reuse_noundo_local_ids(fd);
}
/* Used to keep track of data-blocks that were statically linked. */
blender::MultiValueMap<const void *, ID *> static_linked_data_blocks_by_old_library;
Review

If embedded IDs are written as part of their library in blendfile, there should not be any need for this mapping?

If embedded IDs are written as part of their library in blendfile, there should not be any need for this mapping?
while (bhead) {
switch (bhead->code) {
case BLO_CODE_DATA:
@ -3624,7 +3634,13 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
bhead = blo_bhead_next(fd, bhead);
}
else {
bhead = read_libblock(fd, bfd->main, bhead, ID_TAG_LOCAL, false, nullptr);
bhead = read_libblock(fd,
bfd->main,
bhead,
ID_TAG_LOCAL,
false,
nullptr,
&static_linked_data_blocks_by_old_library);
}
}
@ -3706,7 +3722,11 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
/* Yep, second splitting... but this is a very cheap operation, so no big deal. */
blo_split_main(&mainlist, bfd->main);
LISTBASE_FOREACH (Main *, mainvar, &mainlist) {
BLI_assert(mainvar->versionfile != 0);
if (mainvar->versionfile == 0) {
Review

This is going to be an interesting topic to get right. We'll need a way to differentiate the handling of these IDs now, since regular linked one need to be processed according to their source lib blendfile version, while embedded ones need to be handled according to their owning user blendfile version (which may also be a library btw ;) ).

Might end up being simpler to store the 'source version' in each ID runtime data, rather than trying to keep track of this?

This is going to be an interesting topic to get right. We'll need a way to differentiate the handling of these IDs now, since regular linked one need to be processed according to their source lib blendfile version, while embedded ones need to be handled according to their owning user blendfile version (which may also be a library btw ;) ). Might end up being simpler to store the 'source version' in each ID runtime data, rather than trying to keep track of this?
/* No data-block was loaded from this library, e.g. because all data-blocks were
* statically linked. */
continue;
}
do_versions_after_linking((mainvar->curlib && mainvar->curlib->runtime.filedata) ?
mainvar->curlib->runtime.filedata :
fd,
@ -3714,6 +3734,14 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
}
blo_join_main(&mainlist);
/* Restore lib pointer of statically linked data-blocks. */
for (const auto &&[old_lib_ptr, ids] : static_linked_data_blocks_by_old_library.items()) {
Library *new_lib = static_cast<Library *>(newlibadr(fd, nullptr, false, old_lib_ptr));
for (ID *id : ids) {
id->lib = new_lib;
}
}
BKE_layer_collection_resync_forbid();
/* And we have to compute those user-reference-counts again, as `do_versions_after_linking()`

View File

@ -91,6 +91,7 @@
#include "BLI_blenlib.h"
#include "BLI_endian_defines.h"
#include "BLI_endian_switch.h"
#include "BLI_fileops.hh"
#include "BLI_implicit_sharing.hh"
#include "BLI_link_utils.h"
#include "BLI_linklist.h"
@ -401,6 +402,7 @@ bool ZstdWriteWrap::write(const void *buf, size_t buf_len)
struct WriteData {
const SDNA *sdna;
std::ostream *debug_dst = nullptr;
struct {
/** Use for file and memory writing (size stored in max_size). */
@ -746,6 +748,10 @@ static void writestruct_at_address_nr(
return;
}
if (wd->debug_dst) {
DNA_struct_debug_print(*wd->sdna, *wd->sdna->structs[struct_nr], data, adr, 0, *wd->debug_dst);
}
mywrite(wd, &bh, sizeof(BHead));
mywrite(wd, data, size_t(bh.len));
}
@ -1087,7 +1093,9 @@ static void write_libraries(WriteData *wd, Main *main)
id->name,
main->curlib->runtime.filepath_abs);
}
writestruct(wd, ID_LINK_PLACEHOLDER, ID, 1, id);
if (ID_IS_DYNAMIC_LINKED(id)) {
writestruct(wd, ID_LINK_PLACEHOLDER, ID, 1, id);
}
}
}
}
@ -1263,7 +1271,7 @@ static int write_id_direct_linked_data_process_cb(LibraryIDLinkCallbackData *cb_
if (id == nullptr || !ID_IS_LINKED(id)) {
return IDWALK_RET_NOP;
}
BLI_assert(!ID_IS_LINKED(self_id));
BLI_assert(!ID_IS_LINKED(self_id) || ID_IS_STATIC_LINKED(self_id));
BLI_assert((cb_flag & IDWALK_CB_INDIRECT_USAGE) == 0);
if (self_id->tag & ID_TAG_RUNTIME) {
@ -1278,7 +1286,7 @@ static int write_id_direct_linked_data_process_cb(LibraryIDLinkCallbackData *cb_
return IDWALK_RET_NOP;
}
if (cb_flag & IDWALK_CB_DIRECT_WEAK_LINK) {
if (cb_flag & IDWALK_CB_DIRECT_WEAK_LINK && ID_IS_DYNAMIC_LINKED(id)) {
Review

Would not check this here, but rather update code of id_lib_indirect_weak_link itself to handle (ignore in fact) embedded linked IDs.

Would not check this here, but rather update code of `id_lib_indirect_weak_link` itself to handle (ignore in fact) embedded linked IDs.
id_lib_indirect_weak_link(id);
}
else {
@ -1300,7 +1308,8 @@ static bool write_file_handle(Main *mainvar,
MemFile *current,
int write_flags,
bool use_userdef,
const BlendThumbnail *thumb)
const BlendThumbnail *thumb,
std::ostream *debug_dst)
{
BHead bhead;
ListBase mainlist;
@ -1308,6 +1317,7 @@ static bool write_file_handle(Main *mainvar,
WriteData *wd;
wd = mywrite_begin(ww, compare, current);
wd->debug_dst = debug_dst;
BlendWriter writer = {wd};
/* Clear 'directly linked' flag for all linked data, these are not necessarily valid/up-to-date
@ -1380,9 +1390,15 @@ static bool write_file_handle(Main *mainvar,
/* 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;
blender::Vector<Main *> bmains;
Review

Don't think that writing embedded linked IDs together with actual local data is a good idea. Would rather write them as part of their libraries.

Don't think that writing embedded linked IDs together with actual local data is a good idea. Would rather write them as part of their libraries.
LISTBASE_FOREACH (Main *, bmain, &mainlist) {
bmains.append(bmain);
}
if (override_storage) {
Review

This is fairly theoretical currently, but the override_storage should be written immediately after the 'local' main (i.e. in second position in the bmains vector), since it also contains purely local IDs.

This is fairly theoretical currently, but the `override_storage` should be written immediately after the 'local' `main` (i.e. in second position in the `bmains` vector), since it also contains purely local IDs.
bmains.append(override_storage);
}
BLO_Write_IDBuffer *id_buffer = BLO_write_allocate_id_buffer();
do {
for (Main *bmain : bmains) {
ListBase *lbarray[INDEX_ID_MAX];
int a = set_listbasepointers(bmain, lbarray);
while (a--) {
Review

I would move most of the code in this loop into a new utils (something like write_id). Then this can be called both from here for actual local IDs, and from write_libraries for embedded linked data.

Would even do that as a separate 'refactor' PR to get it in main asap and reduce diffing noise in actual PR?

I would move most of the code in this loop into a new utils (something like `write_id`). Then this can be called both from here for actual local IDs, and from `write_libraries` for embedded linked data. Would even do that as a separate 'refactor' PR to get it in `main` asap and reduce diffing noise in actual PR?
@ -1475,7 +1491,7 @@ static bool write_file_handle(Main *mainvar,
mywrite_flush(wd);
}
} while ((bmain != override_storage) && (bmain = override_storage));
}
BLO_write_destroy_id_buffer(&id_buffer);
@ -1718,9 +1734,12 @@ static bool BLO_write_file_impl(Main *mainvar,
}
}
std::string debug_dst_path = blender::StringRef(filepath) + ".debug.txt";
blender::fstream debug_dst{debug_dst_path, std::ios::out};
/* Actual file writing. */
const bool err = write_file_handle(
mainvar, &ww, nullptr, nullptr, write_flags, use_userdef, thumb);
mainvar, &ww, nullptr, nullptr, write_flags, use_userdef, thumb, &debug_dst);
ww.close();
@ -1782,7 +1801,7 @@ bool BLO_write_file_mem(Main *mainvar, MemFile *compare, MemFile *current, int w
bool use_userdef = false;
const bool err = write_file_handle(
mainvar, nullptr, compare, current, write_flags, use_userdef, nullptr);
mainvar, nullptr, compare, current, write_flags, use_userdef, nullptr, nullptr);
return (err == 0);
}

View File

@ -652,6 +652,8 @@ typedef struct PreviewImage {
#define ID_MISSING(_id) ((((const ID *)(_id))->tag & ID_TAG_MISSING) != 0)
#define ID_IS_LINKED(_id) (((const ID *)(_id))->lib != NULL)
#define ID_IS_DYNAMIC_LINKED(_id) (false)
Review

Would rather only have anew ID_IS_LINKED_EMBEDDED for the time being, and check !ID_IS_LINKED_EMBEDDED when only handling 'regular' linked data is needed.

Would rather only have anew `ID_IS_LINKED_EMBEDDED` for the time being, and check `!ID_IS_LINKED_EMBEDDED` when only handling 'regular' linked data is needed.
#define ID_IS_STATIC_LINKED(_id) (ID_IS_LINKED(_id))
#define ID_TYPE_SUPPORTS_ASSET_EDITABLE(id_type) ELEM(id_type, ID_BR, ID_TE, ID_NT, ID_IM)

View File

@ -12,6 +12,7 @@
#include "intern/dna_utils.h"
struct SDNA;
struct SDNA_Struct;
#ifdef __cplusplus
extern "C" {
@ -258,3 +259,14 @@ void DNA_sdna_alias_data_ensure_structs_map(struct SDNA *sdna);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
# include <ostream>
void DNA_struct_debug_print(const SDNA &sdna,
const SDNA_Struct &sdna_struct,
const void *data,
const void *address,
int indent,
std::ostream &stream);
#endif

View File

@ -21,9 +21,11 @@
#include "MEM_guardedalloc.h" /* for MEM_freeN MEM_mallocN MEM_callocN */
#include "BLI_endian_switch.h"
#include "BLI_index_range.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_memarena.h"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLI_utildefines.h"
#include "BLI_ghash.h"
@ -2050,4 +2052,132 @@ void DNA_sdna_alias_data_ensure_structs_map(SDNA *sdna)
#endif
}
void DNA_struct_debug_print(const SDNA &sdna,
const SDNA_Struct &sdna_struct,
const void *initial_data,
const void *address,
int indent,
std::ostream &stream)
{
using namespace blender;
std::string indentation = StringRef(" ").substr(0, indent);
const void *data = initial_data;
const char *struct_name = sdna.types[sdna_struct.type_index];
if (indent == 0) {
stream << indentation << "<" << struct_name << "> " << address << "\n";
}
for (const int member_i : IndexRange(sdna_struct.members_num)) {
const SDNA_StructMember &member = sdna_struct.members[member_i];
const char *member_type_name = sdna.types[member.type_index];
const char *member_name = sdna.members[member.member_index];
const eStructMemberCategory member_category = get_struct_member_category(&sdna, &member);
const int array_elem_num = sdna.members_array_num[member.member_index];
stream << indentation << " " << member_type_name << " " << member_name << ":";
switch (member_category) {
case STRUCT_MEMBER_CATEGORY_STRUCT: {
stream << "\n";
const int substruct_i = DNA_struct_find_index_without_alias(&sdna, member_type_name);
const SDNA_Struct &sub_sdna_struct = *sdna.structs[substruct_i];
int substruct_size = sdna.types_size[member.type_index];
for (int elem_i = 0; elem_i < array_elem_num; elem_i++) {
const void *sub_data = POINTER_OFFSET(data, elem_i * substruct_size);
const intptr_t subdata_offset = intptr_t(sub_data) - intptr_t(initial_data);
DNA_struct_debug_print(sdna,
sub_sdna_struct,
sub_data,
POINTER_OFFSET(address, subdata_offset),
indent + 2,
stream);
break;
}
break;
}
case STRUCT_MEMBER_CATEGORY_PRIMITIVE: {
stream << " ";
const int type_size = sdna.types_size[member.type_index];
for ([[maybe_unused]] const int elem_i : IndexRange(array_elem_num)) {
const void *current_data = POINTER_OFFSET(data, elem_i * type_size);
switch (member.type_index) {
case SDNA_TYPE_CHAR: {
const char value = *(char *)current_data;
if (std::isprint(value)) {
stream << value;
}
else {
stream << int(value);
}
break;
}
case SDNA_TYPE_UCHAR: {
const uchar value = *(char *)current_data;
if (std::isprint(value)) {
stream << value;
}
else {
stream << int(value);
}
break;
}
case SDNA_TYPE_INT8: {
stream << *(int8_t *)current_data;
break;
}
case SDNA_TYPE_SHORT: {
stream << *(short *)current_data;
break;
}
case SDNA_TYPE_USHORT: {
stream << *(ushort *)current_data;
break;
}
case SDNA_TYPE_INT: {
stream << *(int *)current_data;
break;
}
case SDNA_TYPE_FLOAT: {
stream << *(float *)current_data;
break;
}
case SDNA_TYPE_INT64: {
stream << *(int64_t *)current_data;
break;
}
/* Somehow the types are a bit messed up after VOID, not sure what's going on. */
case SDNA_TYPE_VOID:
case SDNA_TYPE_UINT64: {
stream << *(uint64_t *)current_data;
break;
}
case SDNA_TYPE_DOUBLE: {
stream << *(double *)current_data;
break;
}
default: {
BLI_assert_unreachable();
break;
}
}
stream << " ";
}
stream << "\n";
break;
}
case STRUCT_MEMBER_CATEGORY_POINTER: {
for ([[maybe_unused]] const int elem_i : IndexRange(array_elem_num)) {
const void *current_data = POINTER_OFFSET(data, sdna.pointer_size * elem_i);
stream << " " << *(void **)current_data << " ";
}
stream << "\n";
break;
}
}
const int member_size = get_member_size_in_bytes(&sdna, &member);
data = POINTER_OFFSET(data, member_size);
}
}
/** \} */