Initial implementation of local ID re-use when appending.

This commit adds to ID struct a new optional 'weak reference' to a
linked ID (in the form of a blend file library path and full ID name).

This can then be used on next append to try to find a matching local ID
instead of re-making the linked data local again.

Ref. T90545

NOTE: ID re-use will be disabled for regular append for the time being
(3.0 release), and only used for assets. Therefore, this commit should
not change anything user-wise.

Differential Revision: https://developer.blender.org/D12545
This commit is contained in:
2021-09-17 16:22:29 +02:00
parent 707bcd5693
commit 794c2828af
36 changed files with 412 additions and 29 deletions

View File

@@ -49,8 +49,11 @@ enum {
* appended.
* NOTE: Mutually exclusive with `IDTYPE_FLAGS_NO_LIBLINKING`. */
IDTYPE_FLAGS_ONLY_APPEND = 1 << 2,
/** Allow to re-use an existing local ID with matching weak library reference instead of creating
* a new copy of it, when appending. See also #LibraryWeakReference in `DNA_ID.h`. */
IDTYPE_FLAGS_APPEND_IS_REUSABLE = 1 << 3,
/** Indicates that the given IDType does not have animation data. */
IDTYPE_FLAGS_NO_ANIMDATA = 1 << 3,
IDTYPE_FLAGS_NO_ANIMDATA = 1 << 4,
};
typedef struct IDCacheKey {
@@ -290,6 +293,7 @@ bool BKE_idtype_idcode_is_valid(const short idcode);
bool BKE_idtype_idcode_is_linkable(const short idcode);
bool BKE_idtype_idcode_is_only_appendable(const short idcode);
bool BKE_idtype_idcode_append_is_reusable(const short idcode);
/* Macro currently, since any linkable IDtype should be localizable. */
#define BKE_idtype_idcode_is_localizable BKE_idtype_idcode_is_linkable

View File

@@ -212,6 +212,32 @@ void BKE_main_relations_tag_set(struct Main *bmain,
struct GSet *BKE_main_gset_create(struct Main *bmain, struct GSet *gset);
/*
* Temporary runtime API to allow re-using local (already appended) IDs instead of appending a new
* copy again.
*/
struct GHash *BKE_main_library_weak_reference_create(struct Main *bmain) ATTR_NONNULL();
void BKE_main_library_weak_reference_destroy(struct GHash *library_weak_reference_mapping)
ATTR_NONNULL();
struct ID *BKE_main_library_weak_reference_search_item(
struct GHash *library_weak_reference_mapping,
const char *library_filepath,
const char *library_id_name) ATTR_NONNULL();
void BKE_main_library_weak_reference_add_item(struct GHash *library_weak_reference_mapping,
const char *library_filepath,
const char *library_id_name,
struct ID *new_id) ATTR_NONNULL();
void BKE_main_library_weak_reference_update_item(struct GHash *library_weak_reference_mapping,
const char *library_filepath,
const char *library_id_name,
struct ID *old_id,
struct ID *new_id) ATTR_NONNULL();
void BKE_main_library_weak_reference_remove_item(struct GHash *library_weak_reference_mapping,
const char *library_filepath,
const char *library_id_name,
struct ID *old_id) ATTR_NONNULL();
/* *** Generic utils to loop over whole Main database. *** */
#define FOREACH_MAIN_LISTBASE_ID_BEGIN(_lb, _id) \

View File

@@ -315,7 +315,7 @@ IDTypeInfo IDType_ID_AR = {
.name = "Armature",
.name_plural = "armatures",
.translation_context = BLT_I18NCONTEXT_ID_ARMATURE,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = armature_init_data,
.copy_data = armature_copy_data,

View File

@@ -586,6 +586,11 @@ void BKE_bpath_traverse_id(
return;
}
if (id->library_weak_reference != NULL) {
rewrite_path_fixed(
id->library_weak_reference->library_filepath, visit_cb, absbase, bpath_user_data);
}
switch (GS(id->name)) {
case ID_IM: {
Image *ima;

View File

@@ -133,7 +133,7 @@ IDTypeInfo IDType_ID_CF = {
.name = "CacheFile",
.name_plural = "cache_files",
.translation_context = BLT_I18NCONTEXT_ID_CACHEFILE,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = cache_file_init_data,
.copy_data = cache_file_copy_data,

View File

@@ -182,7 +182,7 @@ IDTypeInfo IDType_ID_CA = {
.name = "Camera",
.name_plural = "cameras",
.translation_context = BLT_I18NCONTEXT_ID_CAMERA,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = camera_init_data,
.copy_data = camera_copy_data,

View File

@@ -311,7 +311,7 @@ IDTypeInfo IDType_ID_CU = {
.name = "Curve",
.name_plural = "curves",
.translation_context = BLT_I18NCONTEXT_ID_CURVE,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = curve_init_data,
.copy_data = curve_copy_data,

View File

@@ -160,7 +160,7 @@ IDTypeInfo IDType_ID_VF = {
.name = "Font",
.name_plural = "fonts",
.translation_context = BLT_I18NCONTEXT_ID_VFONT,
.flags = IDTYPE_FLAGS_NO_ANIMDATA,
.flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = vfont_init_data,
.copy_data = vfont_copy_data,

View File

@@ -181,7 +181,7 @@ IDTypeInfo IDType_ID_HA = {
.name = "Hair",
.name_plural = "hairs",
.translation_context = BLT_I18NCONTEXT_ID_HAIR,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = hair_init_data,
.copy_data = hair_copy_data,

View File

@@ -254,6 +254,24 @@ bool BKE_idtype_idcode_is_only_appendable(const short idcode)
return false;
}
/**
* Check if an ID type can try to reuse and existing matching local one when being appended again.
*
* \param idcode: The IDType code to check.
* \return Boolean, false when it cannot be re-used, true otherwise.
*/
bool BKE_idtype_idcode_append_is_reusable(const short idcode)
{
const IDTypeInfo *id_type = BKE_idtype_get_info_from_idcode(idcode);
BLI_assert(id_type != NULL);
if (id_type != NULL && (id_type->flags & IDTYPE_FLAGS_APPEND_IS_REUSABLE) != 0) {
/* All appendable ID types should also always be linkable. */
BLI_assert((id_type->flags & IDTYPE_FLAGS_NO_LIBLINKING) == 0);
return true;
}
return false;
}
/**
* Convert an \a idcode into an \a idfilter (e.g. ID_OB -> FILTER_ID_OB).
*/

View File

@@ -345,7 +345,7 @@ IDTypeInfo IDType_ID_IM = {
.name = "Image",
.name_plural = "images",
.translation_context = BLT_I18NCONTEXT_ID_IMAGE,
.flags = IDTYPE_FLAGS_NO_ANIMDATA,
.flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = image_init_data,
.copy_data = image_copy_data,

View File

@@ -196,7 +196,7 @@ IDTypeInfo IDType_ID_LT = {
.name = "Lattice",
.name_plural = "lattices",
.translation_context = BLT_I18NCONTEXT_ID_LATTICE,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = lattice_init_data,
.copy_data = lattice_copy_data,

View File

@@ -1301,6 +1301,9 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori
new_id->properties = IDP_CopyProperty_ex(id->properties, copy_data_flag);
}
/* This is never duplicated, only one existing ID should have a given weak ref to library/ID. */
new_id->library_weak_reference = NULL;
if ((orig_flag & LIB_ID_COPY_NO_LIB_OVERRIDE) == 0) {
if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
/* We do not want to copy existing override rules here, as they would break the proper
@@ -2440,6 +2443,10 @@ void BKE_id_blend_write(BlendWriter *writer, ID *id)
BKE_asset_metadata_write(writer, id->asset_data);
}
if (id->library_weak_reference != NULL) {
BLO_write_struct(writer, LibraryWeakReference, id->library_weak_reference);
}
/* ID_WM's id->properties are considered runtime only, and never written in .blend file. */
if (id->properties && !ELEM(GS(id->name), ID_WM)) {
IDP_BlendWrite(writer, id->properties);

View File

@@ -69,6 +69,10 @@ void BKE_libblock_free_data(ID *id, const bool do_id_user)
BKE_asset_metadata_free(&id->asset_data);
}
if (id->library_weak_reference != NULL) {
MEM_freeN(id->library_weak_reference);
}
BKE_animdata_free(id, do_id_user);
}

View File

@@ -193,7 +193,7 @@ IDTypeInfo IDType_ID_LA = {
.name = "Light",
.name_plural = "lights",
.translation_context = BLT_I18NCONTEXT_ID_LIGHT,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = light_init_data,
.copy_data = light_copy_data,

View File

@@ -91,7 +91,7 @@ IDTypeInfo IDType_ID_LP = {
.name = "LightProbe",
.name_plural = "lightprobes",
.translation_context = BLT_I18NCONTEXT_ID_LIGHTPROBE,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = lightprobe_init_data,
.copy_data = NULL,

View File

@@ -751,7 +751,7 @@ IDTypeInfo IDType_ID_LS = {
.name = "FreestyleLineStyle",
.name_plural = "linestyles",
.translation_context = BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = linestyle_init_data,
.copy_data = linestyle_copy_data,

View File

@@ -35,6 +35,7 @@
#include "DNA_ID.h"
#include "BKE_global.h"
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
#include "BKE_lib_query.h"
#include "BKE_main.h"
@@ -358,6 +359,207 @@ GSet *BKE_main_gset_create(Main *bmain, GSet *gset)
return gset;
}
/* Utils for ID's library weak reference API. */
typedef struct LibWeakRefKey {
char filepath[FILE_MAX];
char id_name[MAX_ID_NAME];
} LibWeakRefKey;
static LibWeakRefKey *lib_weak_key_create(LibWeakRefKey *key,
const char *lib_path,
const char *id_name)
{
if (key == NULL) {
key = MEM_mallocN(sizeof(*key), __func__);
}
BLI_strncpy(key->filepath, lib_path, sizeof(key->filepath));
BLI_strncpy(key->id_name, id_name, sizeof(key->id_name));
return key;
}
static uint lib_weak_key_hash(const void *ptr)
{
const LibWeakRefKey *string_pair = ptr;
uint hash = BLI_ghashutil_strhash_p_murmur(string_pair->filepath);
return hash ^ BLI_ghashutil_strhash_p_murmur(string_pair->id_name);
}
static bool lib_weak_key_cmp(const void *a, const void *b)
{
const LibWeakRefKey *string_pair_a = a;
const LibWeakRefKey *string_pair_b = b;
return !(STREQ(string_pair_a->filepath, string_pair_b->filepath) &&
STREQ(string_pair_a->id_name, string_pair_b->id_name));
}
/**
* Generate a mapping between 'library path' of an ID (as a pair (relative blend file path, id
* name)), and a current local ID, if any.
*
* This uses the information stored in `ID.library_weak_reference`.
*/
GHash *BKE_main_library_weak_reference_create(Main *bmain)
{
GHash *library_weak_reference_mapping = BLI_ghash_new(
lib_weak_key_hash, lib_weak_key_cmp, __func__);
ListBase *lb;
FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
ID *id_iter = lb->first;
if (id_iter == NULL) {
continue;
}
if (!BKE_idtype_idcode_append_is_reusable(GS(id_iter->name))) {
continue;
}
BLI_assert(BKE_idtype_idcode_is_linkable(GS(id_iter->name)));
FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id_iter) {
if (id_iter->library_weak_reference == NULL) {
continue;
}
LibWeakRefKey *key = lib_weak_key_create(NULL,
id_iter->library_weak_reference->library_filepath,
id_iter->library_weak_reference->library_id_name);
BLI_ghash_insert(library_weak_reference_mapping, key, id_iter);
}
FOREACH_MAIN_LISTBASE_ID_END;
}
FOREACH_MAIN_LISTBASE_END;
return library_weak_reference_mapping;
}
/**
* Destroy the data generated by #BKE_main_library_weak_reference_create.
*/
void BKE_main_library_weak_reference_destroy(GHash *library_weak_reference_mapping)
{
BLI_ghash_free(library_weak_reference_mapping, MEM_freeN, NULL);
}
/**
* Search for a local ID matching the given linked ID reference.
*
* \param library_weak_reference_mapping: the mapping data generated by
* #BKE_main_library_weak_reference_create.
* \param library_relative_path: the path of a blend file library (relative to current working
* one).
* \param library_id_name: the full ID name, including the leading two chars encoding the ID
* type.
*/
ID *BKE_main_library_weak_reference_search_item(GHash *library_weak_reference_mapping,
const char *library_filepath,
const char *library_id_name)
{
LibWeakRefKey key;
lib_weak_key_create(&key, library_filepath, library_id_name);
return (ID *)BLI_ghash_lookup(library_weak_reference_mapping, &key);
}
/**
* Add the given ID weak library reference to given local ID and the runtime mapping.
*
* \param library_weak_reference_mapping: the mapping data generated by
* #BKE_main_library_weak_reference_create.
* \param library_relative_path: the path of a blend file library (relative to current working
* one).
* \param library_id_name: the full ID name, including the leading two chars encoding the ID type.
* \param new_id: New local ID matching given weak reference.
*/
void BKE_main_library_weak_reference_add_item(GHash *library_weak_reference_mapping,
const char *library_filepath,
const char *library_id_name,
ID *new_id)
{
BLI_assert(GS(library_id_name) == GS(new_id->name));
BLI_assert(new_id->library_weak_reference == NULL);
BLI_assert(BKE_idtype_idcode_append_is_reusable(GS(new_id->name)));
new_id->library_weak_reference = MEM_mallocN(sizeof(*(new_id->library_weak_reference)),
__func__);
LibWeakRefKey *key = lib_weak_key_create(NULL, library_filepath, library_id_name);
void **id_p;
const bool already_exist_in_mapping = BLI_ghash_ensure_p(
library_weak_reference_mapping, key, &id_p);
BLI_assert(!already_exist_in_mapping);
BLI_strncpy(new_id->library_weak_reference->library_filepath,
library_filepath,
sizeof(new_id->library_weak_reference->library_filepath));
BLI_strncpy(new_id->library_weak_reference->library_id_name,
library_id_name,
sizeof(new_id->library_weak_reference->library_id_name));
*id_p = new_id;
}
/**
* Update the status of the given ID weak library reference in current local IDs and the runtime
* mapping.
*
* This effectively transfers the 'ownership' of the given weak reference from `old_id` to
* `new_id`.
*
* \param library_weak_reference_mapping: the mapping data generated by
* #BKE_main_library_weak_reference_create.
* \param library_relative_path: the path of a blend file library (relative to current working
* one).
* \param library_id_name: the full ID name, including the leading two chars encoding the ID type.
* \param old_id: Existing local ID matching given weak reference.
* \param new_id: New local ID matching given weak reference.
*/
void BKE_main_library_weak_reference_update_item(GHash *library_weak_reference_mapping,
const char *library_filepath,
const char *library_id_name,
ID *old_id,
ID *new_id)
{
BLI_assert(GS(library_id_name) == GS(old_id->name));
BLI_assert(GS(library_id_name) == GS(new_id->name));
BLI_assert(old_id->library_weak_reference != NULL);
BLI_assert(new_id->library_weak_reference == NULL);
BLI_assert(STREQ(old_id->library_weak_reference->library_filepath, library_filepath));
BLI_assert(STREQ(old_id->library_weak_reference->library_id_name, library_id_name));
LibWeakRefKey key;
lib_weak_key_create(&key, library_filepath, library_id_name);
void **id_p = BLI_ghash_lookup_p(library_weak_reference_mapping, &key);
BLI_assert(id_p != NULL && *id_p == old_id);
new_id->library_weak_reference = old_id->library_weak_reference;
old_id->library_weak_reference = NULL;
*id_p = new_id;
}
/**
* Remove the given ID weak library reference from the given local ID and the runtime mapping.
*
* \param library_weak_reference_mapping: the mapping data generated by
* #BKE_main_library_weak_reference_create.
* \param library_relative_path: the path of a blend file library (relative to current working
* one).
* \param library_id_name: the full ID name, including the leading two chars encoding the ID type.
* \param old_id: Existing local ID matching given weak reference.
*/
void BKE_main_library_weak_reference_remove_item(GHash *library_weak_reference_mapping,
const char *library_filepath,
const char *library_id_name,
ID *old_id)
{
BLI_assert(GS(library_id_name) == GS(old_id->name));
BLI_assert(old_id->library_weak_reference != NULL);
LibWeakRefKey key;
lib_weak_key_create(&key, library_filepath, library_id_name);
BLI_assert(BLI_ghash_lookup(library_weak_reference_mapping, &key) == old_id);
BLI_ghash_remove(library_weak_reference_mapping, &key, MEM_freeN, NULL);
MEM_SAFE_FREE(old_id->library_weak_reference);
}
/**
* Generates a raw .blend file thumbnail data from given image.
*

View File

@@ -254,7 +254,7 @@ IDTypeInfo IDType_ID_MSK = {
.name = "Mask",
.name_plural = "masks",
.translation_context = BLT_I18NCONTEXT_ID_MASK,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = NULL,
.copy_data = mask_copy_data,

View File

@@ -259,7 +259,7 @@ IDTypeInfo IDType_ID_MA = {
.name = "Material",
.name_plural = "materials",
.translation_context = BLT_I18NCONTEXT_ID_MATERIAL,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = material_init_data,
.copy_data = material_copy_data,

View File

@@ -188,7 +188,7 @@ IDTypeInfo IDType_ID_MB = {
.name = "Metaball",
.name_plural = "metaballs",
.translation_context = BLT_I18NCONTEXT_ID_METABALL,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = metaball_init_data,
.copy_data = metaball_copy_data,

View File

@@ -360,7 +360,7 @@ IDTypeInfo IDType_ID_ME = {
.name = "Mesh",
.name_plural = "meshes",
.translation_context = BLT_I18NCONTEXT_ID_MESH,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = mesh_init_data,
.copy_data = mesh_copy_data,

View File

@@ -346,7 +346,7 @@ IDTypeInfo IDType_ID_MC = {
.name = "MovieClip",
.name_plural = "movieclips",
.translation_context = BLT_I18NCONTEXT_ID_MOVIECLIP,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = movie_clip_init_data,
.copy_data = movie_clip_copy_data,

View File

@@ -992,7 +992,7 @@ IDTypeInfo IDType_ID_NT = {
/* name */ "NodeTree",
/* name_plural */ "node_groups",
/* translation_context */ BLT_I18NCONTEXT_ID_NODETREE,
/* flags */ 0,
/* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* init_data */ ntree_init_data,
/* copy_data */ ntree_copy_data,

View File

@@ -174,7 +174,7 @@ IDTypeInfo IDType_ID_PT = {
/* name */ "PointCloud",
/* name_plural */ "pointclouds",
/* translation_context */ BLT_I18NCONTEXT_ID_POINTCLOUD,
/* flags */ 0,
/* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* init_data */ pointcloud_init_data,
/* copy_data */ pointcloud_copy_data,

View File

@@ -152,7 +152,7 @@ IDTypeInfo IDType_ID_SIM = {
/* name */ "Simulation",
/* name_plural */ "simulations",
/* translation_context */ BLT_I18NCONTEXT_ID_SIMULATION,
/* flags */ 0,
/* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* init_data */ simulation_init_data,
/* copy_data */ simulation_copy_data,

View File

@@ -204,7 +204,7 @@ IDTypeInfo IDType_ID_SO = {
.name = "Sound",
.name_plural = "sounds",
.translation_context = BLT_I18NCONTEXT_ID_SOUND,
.flags = IDTYPE_FLAGS_NO_ANIMDATA,
.flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* A fuzzy case, think NULLified content is OK here... */
.init_data = NULL,

View File

@@ -98,7 +98,7 @@ IDTypeInfo IDType_ID_SPK = {
.name = "Speaker",
.name_plural = "speakers",
.translation_context = BLT_I18NCONTEXT_ID_SPEAKER,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = speaker_init_data,
.copy_data = NULL,

View File

@@ -241,7 +241,7 @@ IDTypeInfo IDType_ID_TXT = {
.name = "Text",
.name_plural = "texts",
.translation_context = BLT_I18NCONTEXT_ID_TEXT,
.flags = IDTYPE_FLAGS_NO_ANIMDATA,
.flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = text_init_data,
.copy_data = text_copy_data,

View File

@@ -210,7 +210,7 @@ IDTypeInfo IDType_ID_TE = {
.name = "Texture",
.name_plural = "textures",
.translation_context = BLT_I18NCONTEXT_ID_TEXTURE,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = texture_init_data,
.copy_data = texture_copy_data,

View File

@@ -642,7 +642,7 @@ IDTypeInfo IDType_ID_VO = {
/* name */ "Volume",
/* name_plural */ "volumes",
/* translation_context */ BLT_I18NCONTEXT_ID_VOLUME,
/* flags */ 0,
/* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* init_data */ volume_init_data,
/* copy_data */ volume_copy_data,

View File

@@ -190,7 +190,7 @@ IDTypeInfo IDType_ID_WO = {
.name = "World",
.name_plural = "worlds",
.translation_context = BLT_I18NCONTEXT_ID_WORLD,
.flags = 0,
.flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE,
.init_data = world_init_data,
.copy_data = world_copy_data,

View File

@@ -2195,6 +2195,13 @@ static void direct_link_id_common(
/* Initialize with provided tag. */
id->tag = tag;
if (ID_IS_LINKED(id)) {
id->library_weak_reference = NULL;
}
else {
BLO_read_data_address(reader, &id->library_weak_reference);
}
if (tag & LIB_TAG_ID_LINK_PLACEHOLDER) {
/* For placeholder we only need to set the tag and properly initialize generic ID fields above,
* no further data to read. */

View File

@@ -392,7 +392,14 @@ typedef struct ID {
* that references this ID (the bones of an armature or the modifiers of an object for e.g.).
*/
void *py_instance;
void *_pad1;
/**
* Weak reference to an ID in a given library file, used to allow re-using already appended data
* in some cases, instead of appending it again.
*
* May be NULL.
*/
struct LibraryWeakReference *library_weak_reference;
} ID;
/**
@@ -426,6 +433,26 @@ typedef struct Library {
short versionfile, subversionfile;
} Library;
/**
* A weak library/ID reference for local data that has been appended, to allow re-using that local
* data instead of creating a new copy of it in future appends.
*
* NOTE: This is by design a week reference, in other words code should be totally fine and perform
* a regular append if it cannot find a valid matching local ID.
*
* NOTE: There should always be only one single ID in current Main matching a given linked
* reference.
*/
typedef struct LibraryWeakReference {
/** Expected to match a `Library.filepath`. */
char library_filepath[1024];
/** MAX_ID_NAME. May be different from the current local ID name. */
char library_id_name[66];
char _pad[2];
} LibraryWeakReference;
/* for PreviewImage->flag */
enum ePreviewImage_Flag {
PRV_CHANGED = (1 << 0),

View File

@@ -1936,6 +1936,15 @@ static void rna_def_ID(BlenderRNA *brna)
RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_COMPARISON);
RNA_def_property_ui_text(prop, "Library", "Library file the data-block is linked from");
prop = RNA_def_pointer(srna,
"library_weak_reference",
"LibraryWeakReference",
"Library Weak Reference",
"Weak reference to a data-block in another library .blend file (used to "
"re-use already appended data instead of appending new copies)");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_COMPARISON);
prop = RNA_def_property(srna, "asset_data", PROP_POINTER, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_COMPARISON);
@@ -2143,6 +2152,31 @@ static void rna_def_library(BlenderRNA *brna)
RNA_def_function_ui_description(func, "Reload this library and all its linked data-blocks");
}
static void rna_def_library_weak_reference(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "LibraryWeakReference", NULL);
RNA_def_struct_ui_text(
srna,
"LibraryWeakReference",
"Read-only external reference to a linked data-block and its library file");
prop = RNA_def_property(srna, "filepath", PROP_STRING, PROP_FILEPATH);
RNA_def_property_string_sdna(prop, NULL, "library_filepath");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "File Path", "Path to the library .blend file");
prop = RNA_def_property(srna, "id_name", PROP_STRING, PROP_FILEPATH);
RNA_def_property_string_sdna(prop, NULL, "library_id_name");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(
prop,
"ID name",
"Full ID name in the library .blend file (including the two leading 'id type' chars)");
}
/**
* \attention This is separate from the above. It allows for RNA functions to
* return an IDProperty *. See MovieClip.metadata for a usage example.
@@ -2175,6 +2209,7 @@ void RNA_def_ID(BlenderRNA *brna)
rna_def_ID_properties(brna);
rna_def_ID_materials(brna);
rna_def_library(brna);
rna_def_library_weak_reference(brna);
rna_def_idproperty_wrap_ptr(brna);
}

View File

@@ -190,6 +190,9 @@ typedef struct WMLinkAppendData {
/** Allows to easily find an existing items from an ID pointer. Used by append code. */
GHash *new_id_to_item;
/** Runtime info used by append code to manage re-use of already appended matching IDs. */
GHash *library_weak_reference_mapping;
/* Internal 'private' data */
MemArena *memarena;
} WMLinkAppendData;
@@ -230,6 +233,8 @@ static void wm_link_append_data_free(WMLinkAppendData *lapp_data)
BLI_ghash_free(lapp_data->new_id_to_item, NULL, NULL);
}
BLI_assert(lapp_data->library_weak_reference_mapping == NULL);
BLI_memarena_free(lapp_data->memarena);
}
@@ -637,6 +642,9 @@ static void wm_append_do(WMLinkAppendData *lapp_data,
BLI_ghash_insert(lapp_data->new_id_to_item, id, item);
}
const bool do_reuse_existing_id = false;
lapp_data->library_weak_reference_mapping = BKE_main_library_weak_reference_create(bmain);
/* NOTE: Since we append items for IDs not already listed (i.e. implicitly linked indirect
* dependencies), this list will grow and we will process those IDs later, leading to a flatten
* recursive processing of all the linked dependencies. */
@@ -650,7 +658,14 @@ static void wm_append_do(WMLinkAppendData *lapp_data,
/* Clear tag previously used to mark IDs needing post-processing (instantiation of loose
* objects etc.). */
id->tag &= ~LIB_TAG_DOIT;
BLI_assert((id->tag & LIB_TAG_DOIT) == 0);
ID *existing_local_id = BKE_idtype_idcode_append_is_reusable(GS(id->name)) ?
BKE_main_library_weak_reference_search_item(
lapp_data->library_weak_reference_mapping,
id->lib->filepath,
id->name) :
NULL;
if (item->append_action != WM_APPEND_ACT_UNSET) {
/* Already set, pass. */
@@ -659,6 +674,14 @@ static void wm_append_do(WMLinkAppendData *lapp_data,
CLOG_INFO(&LOG, 3, "Appended ID '%s' is proxified, keeping it linked...", id->name);
item->append_action = WM_APPEND_ACT_KEEP_LINKED;
}
/* Only re-use existing local ID for indirectly linked data, the ID explicitely selected by the
* user we always fully append. */
else if (do_reuse_existing_id && existing_local_id != NULL &&
(item->append_tag & WM_APPEND_TAG_INDIRECT) != 0) {
CLOG_INFO(&LOG, 3, "Appended ID '%s' as a matching local one, re-using it...", id->name);
item->append_action = WM_APPEND_ACT_REUSE_LOCAL;
item->customdata = existing_local_id;
}
else if (id->tag & LIB_TAG_PRE_EXISTING) {
CLOG_INFO(&LOG, 3, "Appended ID '%s' was already linked, need to copy it...", id->name);
item->append_action = WM_APPEND_ACT_COPY_LOCAL;
@@ -678,6 +701,16 @@ static void wm_append_do(WMLinkAppendData *lapp_data,
BKE_library_foreach_ID_link(
bmain, id, foreach_libblock_append_callback, &cb_data, IDWALK_NOP);
}
/* If we found a matching existing local id but are not re-using it, we need to properly clear
* its weak reference to linked data. */
if (existing_local_id != NULL &&
!ELEM(item->append_action, WM_APPEND_ACT_KEEP_LINKED, WM_APPEND_ACT_REUSE_LOCAL)) {
BKE_main_library_weak_reference_remove_item(lapp_data->library_weak_reference_mapping,
id->lib->filepath,
id->name,
existing_local_id);
}
}
/* Effectively perform required operation on every linked ID. */
@@ -689,6 +722,11 @@ static void wm_append_do(WMLinkAppendData *lapp_data,
}
ID *local_appended_new_id = NULL;
char lib_filepath[FILE_MAX];
BLI_strncpy(lib_filepath, id->lib->filepath, sizeof(lib_filepath));
char lib_id_name[MAX_ID_NAME];
BLI_strncpy(lib_id_name, id->name, sizeof(lib_id_name));
switch (item->append_action) {
case WM_APPEND_ACT_COPY_LOCAL: {
BKE_lib_id_make_local(
@@ -721,6 +759,13 @@ static void wm_append_do(WMLinkAppendData *lapp_data,
}
if (local_appended_new_id != NULL) {
if (BKE_idtype_idcode_append_is_reusable(GS(local_appended_new_id->name))) {
BKE_main_library_weak_reference_add_item(lapp_data->library_weak_reference_mapping,
lib_filepath,
lib_id_name,
local_appended_new_id);
}
if (GS(local_appended_new_id->name) == ID_OB) {
BKE_rigidbody_ensure_local_object(bmain, (Object *)local_appended_new_id);
}
@@ -733,6 +778,9 @@ static void wm_append_do(WMLinkAppendData *lapp_data,
}
}
BKE_main_library_weak_reference_destroy(lapp_data->library_weak_reference_mapping);
lapp_data->library_weak_reference_mapping = NULL;
/* Remap IDs as needed. */
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
@@ -745,7 +793,7 @@ static void wm_append_do(WMLinkAppendData *lapp_data,
if (id == NULL) {
continue;
}
if (item->append_action == WM_APPEND_ACT_COPY_LOCAL) {
if (ELEM(item->append_action, WM_APPEND_ACT_COPY_LOCAL, WM_APPEND_ACT_REUSE_LOCAL)) {
BLI_assert(ID_IS_LINKED(id));
id = id->newid;
if (id == NULL) {