From 794c2828af60af02a38381c2a9a81f9046381074 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 17 Sep 2021 16:22:29 +0200 Subject: 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 --- source/blender/blenkernel/BKE_idtype.h | 6 +- source/blender/blenkernel/BKE_main.h | 26 +++ source/blender/blenkernel/intern/armature.c | 2 +- source/blender/blenkernel/intern/bpath.c | 5 + source/blender/blenkernel/intern/cachefile.c | 2 +- source/blender/blenkernel/intern/camera.c | 2 +- source/blender/blenkernel/intern/curve.c | 2 +- source/blender/blenkernel/intern/font.c | 2 +- source/blender/blenkernel/intern/hair.c | 2 +- source/blender/blenkernel/intern/idtype.c | 18 ++ source/blender/blenkernel/intern/image.c | 2 +- source/blender/blenkernel/intern/lattice.c | 2 +- source/blender/blenkernel/intern/lib_id.c | 7 + source/blender/blenkernel/intern/lib_id_delete.c | 4 + source/blender/blenkernel/intern/light.c | 2 +- source/blender/blenkernel/intern/lightprobe.c | 2 +- source/blender/blenkernel/intern/linestyle.c | 2 +- source/blender/blenkernel/intern/main.c | 202 +++++++++++++++++++++ source/blender/blenkernel/intern/mask.c | 2 +- source/blender/blenkernel/intern/material.c | 2 +- source/blender/blenkernel/intern/mball.c | 2 +- source/blender/blenkernel/intern/mesh.c | 2 +- source/blender/blenkernel/intern/movieclip.c | 2 +- source/blender/blenkernel/intern/node.cc | 2 +- source/blender/blenkernel/intern/pointcloud.cc | 2 +- source/blender/blenkernel/intern/simulation.cc | 2 +- source/blender/blenkernel/intern/sound.c | 2 +- source/blender/blenkernel/intern/speaker.c | 2 +- source/blender/blenkernel/intern/text.c | 2 +- source/blender/blenkernel/intern/texture.c | 2 +- source/blender/blenkernel/intern/volume.cc | 2 +- source/blender/blenkernel/intern/world.c | 2 +- source/blender/blenloader/intern/readfile.c | 7 + source/blender/makesdna/DNA_ID.h | 29 ++- source/blender/makesrna/intern/rna_ID.c | 35 ++++ .../blender/windowmanager/intern/wm_files_link.c | 52 +++++- 36 files changed, 412 insertions(+), 29 deletions(-) (limited to 'source/blender') diff --git a/source/blender/blenkernel/BKE_idtype.h b/source/blender/blenkernel/BKE_idtype.h index 7136a3fd7af..cd656d94fce 100644 --- a/source/blender/blenkernel/BKE_idtype.h +++ b/source/blender/blenkernel/BKE_idtype.h @@ -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 diff --git a/source/blender/blenkernel/BKE_main.h b/source/blender/blenkernel/BKE_main.h index ae60a5563b5..93d5b5c5aa6 100644 --- a/source/blender/blenkernel/BKE_main.h +++ b/source/blender/blenkernel/BKE_main.h @@ -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) \ diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index 87320c88b1b..a86f436185e 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -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, diff --git a/source/blender/blenkernel/intern/bpath.c b/source/blender/blenkernel/intern/bpath.c index 1684e22dece..371ec14876b 100644 --- a/source/blender/blenkernel/intern/bpath.c +++ b/source/blender/blenkernel/intern/bpath.c @@ -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; diff --git a/source/blender/blenkernel/intern/cachefile.c b/source/blender/blenkernel/intern/cachefile.c index 87b1584d422..e642bbc9e06 100644 --- a/source/blender/blenkernel/intern/cachefile.c +++ b/source/blender/blenkernel/intern/cachefile.c @@ -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, diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c index b77855f8f95..ed1f6fcb40a 100644 --- a/source/blender/blenkernel/intern/camera.c +++ b/source/blender/blenkernel/intern/camera.c @@ -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, diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.c index f22c3b13efc..b0d196b2bb0 100644 --- a/source/blender/blenkernel/intern/curve.c +++ b/source/blender/blenkernel/intern/curve.c @@ -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, diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/font.c index 842a701f525..aa13f86523a 100644 --- a/source/blender/blenkernel/intern/font.c +++ b/source/blender/blenkernel/intern/font.c @@ -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, diff --git a/source/blender/blenkernel/intern/hair.c b/source/blender/blenkernel/intern/hair.c index af7cc0acb57..cf346e9cac7 100644 --- a/source/blender/blenkernel/intern/hair.c +++ b/source/blender/blenkernel/intern/hair.c @@ -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, diff --git a/source/blender/blenkernel/intern/idtype.c b/source/blender/blenkernel/intern/idtype.c index b2efccc53c4..d9dc68b1a4f 100644 --- a/source/blender/blenkernel/intern/idtype.c +++ b/source/blender/blenkernel/intern/idtype.c @@ -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). */ diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index 33f007c6dee..b993d743044 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -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, diff --git a/source/blender/blenkernel/intern/lattice.c b/source/blender/blenkernel/intern/lattice.c index e804f32e5a6..9bca8172e64 100644 --- a/source/blender/blenkernel/intern/lattice.c +++ b/source/blender/blenkernel/intern/lattice.c @@ -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, diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 60b6d7ad66d..18824e73ee5 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -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); diff --git a/source/blender/blenkernel/intern/lib_id_delete.c b/source/blender/blenkernel/intern/lib_id_delete.c index 79717fe5f48..502a1197616 100644 --- a/source/blender/blenkernel/intern/lib_id_delete.c +++ b/source/blender/blenkernel/intern/lib_id_delete.c @@ -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); } diff --git a/source/blender/blenkernel/intern/light.c b/source/blender/blenkernel/intern/light.c index c2b71b85973..a6150028f46 100644 --- a/source/blender/blenkernel/intern/light.c +++ b/source/blender/blenkernel/intern/light.c @@ -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, diff --git a/source/blender/blenkernel/intern/lightprobe.c b/source/blender/blenkernel/intern/lightprobe.c index 15733af8ef0..1f4abf36426 100644 --- a/source/blender/blenkernel/intern/lightprobe.c +++ b/source/blender/blenkernel/intern/lightprobe.c @@ -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, diff --git a/source/blender/blenkernel/intern/linestyle.c b/source/blender/blenkernel/intern/linestyle.c index 19030fca38b..f4e4dd9f1ab 100644 --- a/source/blender/blenkernel/intern/linestyle.c +++ b/source/blender/blenkernel/intern/linestyle.c @@ -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, diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index 981f1d4a623..26dcadcc77b 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -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. * diff --git a/source/blender/blenkernel/intern/mask.c b/source/blender/blenkernel/intern/mask.c index a93fcb6e8e0..1d3ebaac303 100644 --- a/source/blender/blenkernel/intern/mask.c +++ b/source/blender/blenkernel/intern/mask.c @@ -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, diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index 13b5bca5638..5f53d5e1ae8 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -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, diff --git a/source/blender/blenkernel/intern/mball.c b/source/blender/blenkernel/intern/mball.c index 45cf0f17840..6c8664aefed 100644 --- a/source/blender/blenkernel/intern/mball.c +++ b/source/blender/blenkernel/intern/mball.c @@ -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, diff --git a/source/blender/blenkernel/intern/mesh.c b/source/blender/blenkernel/intern/mesh.c index d631993c4e8..ed3766ad6a3 100644 --- a/source/blender/blenkernel/intern/mesh.c +++ b/source/blender/blenkernel/intern/mesh.c @@ -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, diff --git a/source/blender/blenkernel/intern/movieclip.c b/source/blender/blenkernel/intern/movieclip.c index e507252307b..0c2ac841b87 100644 --- a/source/blender/blenkernel/intern/movieclip.c +++ b/source/blender/blenkernel/intern/movieclip.c @@ -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, diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 3c54d88c93a..d56a7bf8fb4 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -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, diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index 837a772607f..1db14dc3dc8 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -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, diff --git a/source/blender/blenkernel/intern/simulation.cc b/source/blender/blenkernel/intern/simulation.cc index 4c97ccdf8b1..1d297b3ced9 100644 --- a/source/blender/blenkernel/intern/simulation.cc +++ b/source/blender/blenkernel/intern/simulation.cc @@ -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, diff --git a/source/blender/blenkernel/intern/sound.c b/source/blender/blenkernel/intern/sound.c index 093e4f42d4a..8feda76cc5b 100644 --- a/source/blender/blenkernel/intern/sound.c +++ b/source/blender/blenkernel/intern/sound.c @@ -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, diff --git a/source/blender/blenkernel/intern/speaker.c b/source/blender/blenkernel/intern/speaker.c index 4b10522c375..b361f31cc30 100644 --- a/source/blender/blenkernel/intern/speaker.c +++ b/source/blender/blenkernel/intern/speaker.c @@ -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, diff --git a/source/blender/blenkernel/intern/text.c b/source/blender/blenkernel/intern/text.c index f67bf68010d..5eb40b6624a 100644 --- a/source/blender/blenkernel/intern/text.c +++ b/source/blender/blenkernel/intern/text.c @@ -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, diff --git a/source/blender/blenkernel/intern/texture.c b/source/blender/blenkernel/intern/texture.c index 228e6fffdf7..d5f7647f07a 100644 --- a/source/blender/blenkernel/intern/texture.c +++ b/source/blender/blenkernel/intern/texture.c @@ -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, diff --git a/source/blender/blenkernel/intern/volume.cc b/source/blender/blenkernel/intern/volume.cc index 69452d6896f..0b9ef5c537d 100644 --- a/source/blender/blenkernel/intern/volume.cc +++ b/source/blender/blenkernel/intern/volume.cc @@ -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, diff --git a/source/blender/blenkernel/intern/world.c b/source/blender/blenkernel/intern/world.c index 4abe1ff0f20..fe03c5b817a 100644 --- a/source/blender/blenkernel/intern/world.c +++ b/source/blender/blenkernel/intern/world.c @@ -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, diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 15653264211..3cda1e613f8 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -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. */ diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 5b7c99f2545..d829d707a71 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -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), diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index eb887e1881b..1113ac0eb45 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -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); } diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c index d0117d9d57a..09567eca17f 100644 --- a/source/blender/windowmanager/intern/wm_files_link.c +++ b/source/blender/windowmanager/intern/wm_files_link.c @@ -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) { -- cgit v1.2.3