diff --git a/source/blender/blenkernel/BKE_main_namemap.hh b/source/blender/blenkernel/BKE_main_namemap.hh index 0c27e407559..d770f08dc14 100644 --- a/source/blender/blenkernel/BKE_main_namemap.hh +++ b/source/blender/blenkernel/BKE_main_namemap.hh @@ -19,12 +19,14 @@ */ #include "BLI_compiler_attrs.h" +#include "BLI_string_ref.hh" struct ID; +struct Library; struct Main; struct UniqueName_Map; -UniqueName_Map *BKE_main_namemap_create() ATTR_WARN_UNUSED_RESULT; +/** If given pointer is not null, destroy the namemap and set the pointer to null. */ void BKE_main_namemap_destroy(UniqueName_Map **r_name_map) ATTR_NONNULL(); /** @@ -34,27 +36,45 @@ void BKE_main_namemap_destroy(UniqueName_Map **r_name_map) ATTR_NONNULL(); * readfile code). * - In all of the libraries IDs (for linked IDs). */ -void BKE_main_namemap_clear(Main *bmain) ATTR_NONNULL(); +void BKE_main_namemap_clear(Main &bmain); /** - * Ensures the given name is unique within the given ID type. + * Check if the given name is already in use in the whole Main data-base (local and all linked + * data). + * + * \return true if the name is already in use. + */ +bool BKE_main_global_namemap_contain_name(Main &bmain, short id_type, blender::StringRef name); +/** + * Same as #BKE_main_global_namemap_contain_name, but only search in the local or related library + * namemap. + */ +bool BKE_main_namemap_contain_name(Main &bmain, + Library *lib, + short id_type, + blender::StringRef name); + +/** + * Ensures the given name is unique within the given ID type, in the whole Main data-base (local + * and all linked data). * * In case of name collisions, the name will be adjusted to be unique. * - * \param do_unique_in_bmain: if `true`, ensure that the final name is unique in the whole Main - * (for the given ID type), not only in the set of IDs from the same library. - * * \return true if the name had to be adjusted for uniqueness. */ -bool BKE_main_namemap_get_name(Main *bmain, ID *id, char *name, const bool do_unique_in_bmain) - ATTR_NONNULL(); +bool BKE_main_global_namemap_get_unique_name(Main &bmain, ID &id, char *r_name); +/** + * Same as #BKE_main_global_namemap_get_name, but only make the name unique in the local or related + * library namemap. + */ +bool BKE_main_namemap_get_unique_name(Main &bmain, ID &id, char *r_name); /** * Remove a given name from usage. * * Call this whenever deleting or renaming an object. */ -void BKE_main_namemap_remove_name(Main *bmain, ID *id, const char *name) ATTR_NONNULL(); +void BKE_main_namemap_remove_id(Main &bmain, ID &id); /** * Check that all ID names in given `bmain` are unique (per ID type and library), and that existing @@ -62,7 +82,7 @@ void BKE_main_namemap_remove_name(Main *bmain, ID *id, const char *name) ATTR_NO * * This is typically called within an assert, or in tests. */ -bool BKE_main_namemap_validate(Main *bmain) ATTR_NONNULL(); +bool BKE_main_namemap_validate(Main &bmain); /** * Same as #BKE_main_namemap_validate, but also fixes any issue by re-generating all name maps, @@ -70,4 +90,4 @@ bool BKE_main_namemap_validate(Main *bmain) ATTR_NONNULL(); * * This is typically only used in `do_versions` code to fix broken files. */ -bool BKE_main_namemap_validate_and_fix(Main *bmain) ATTR_NONNULL(); +bool BKE_main_namemap_validate_and_fix(Main &bmain); diff --git a/source/blender/blenkernel/intern/blendfile.cc b/source/blender/blenkernel/intern/blendfile.cc index 6573f94e2ec..ecff2148e84 100644 --- a/source/blender/blenkernel/intern/blendfile.cc +++ b/source/blender/blenkernel/intern/blendfile.cc @@ -353,7 +353,7 @@ static bool reuse_bmain_move_id(ReuseOldBMainData *reuse_data, /* Move from one list to another, and ensure name is valid. */ BLI_remlink_safe(old_lb, id); - BKE_main_namemap_remove_name(old_bmain, id, id->name + 2); + BKE_main_namemap_remove_id(*old_bmain, *id); id->lib = lib; BLI_addtail(new_lb, id); @@ -570,8 +570,8 @@ static void swap_old_bmain_data_for_blendfile(ReuseOldBMainData *reuse_data, con /* TODO: Could add per-IDType control over name-maps clearing, if this becomes a performances * concern. */ - BKE_main_namemap_clear(old_bmain); - BKE_main_namemap_clear(new_bmain); + BKE_main_namemap_clear(*old_bmain); + BKE_main_namemap_clear(*new_bmain); /* Original 'new' IDs have been moved into the old listbase and will be discarded (deleted). * Original 'old' IDs have been moved into the new listbase and are being reused (kept). @@ -940,7 +940,7 @@ static void setup_app_data(bContext *C, clean_paths(bfd->main); } - BLI_assert(BKE_main_namemap_validate(bfd->main)); + BLI_assert(BKE_main_namemap_validate(*bfd->main)); /* Temporary data to handle swapping around IDs between old and new mains, * and accumulate the required remapping accordingly. */ @@ -1047,7 +1047,7 @@ static void setup_app_data(bContext *C, } } - BLI_assert(BKE_main_namemap_validate(bfd->main)); + BLI_assert(BKE_main_namemap_validate(*bfd->main)); /* Apply remapping of ID pointers caused by re-using part of the data from the 'old' main into * the new one. */ @@ -1086,7 +1086,7 @@ static void setup_app_data(bContext *C, wm_data_consistency_ensure(CTX_wm_manager(C), curscene, cur_view_layer); } - BLI_assert(BKE_main_namemap_validate(bfd->main)); + BLI_assert(BKE_main_namemap_validate(*bfd->main)); if (mode != LOAD_UI) { if (win) { @@ -1113,7 +1113,7 @@ static void setup_app_data(bContext *C, } CTX_data_scene_set(C, curscene); - BLI_assert(BKE_main_namemap_validate(bfd->main)); + BLI_assert(BKE_main_namemap_validate(*bfd->main)); /* This frees the `old_bmain`. */ BKE_blender_globals_main_replace(bfd->main); @@ -1121,7 +1121,7 @@ static void setup_app_data(bContext *C, bfd->main = nullptr; CTX_data_main_set(C, bmain); - BLI_assert(BKE_main_namemap_validate(bmain)); + BLI_assert(BKE_main_namemap_validate(*bmain)); /* These context data should remain valid if old UI is being re-used. */ if (mode == LOAD_UI) { @@ -1208,7 +1208,7 @@ static void setup_app_data(bContext *C, * and safer to fully redo reference-counting. This is a relatively cheap process anyway. */ BKE_main_id_refcount_recompute(bmain, false); - BLI_assert(BKE_main_namemap_validate(bmain)); + BLI_assert(BKE_main_namemap_validate(*bmain)); if (mode != LOAD_UNDO && liboverride::is_auto_resync_enabled()) { reports->duration.lib_overrides_resync = BLI_time_now_seconds(); diff --git a/source/blender/blenkernel/intern/blendfile_link_append.cc b/source/blender/blenkernel/intern/blendfile_link_append.cc index 601123d219d..1f1414d3329 100644 --- a/source/blender/blenkernel/intern/blendfile_link_append.cc +++ b/source/blender/blenkernel/intern/blendfile_link_append.cc @@ -1678,7 +1678,7 @@ void BKE_blendfile_override(BlendfileLinkAppendContext *lapp_context, } } - BKE_main_namemap_clear(bmain); + BKE_main_namemap_clear(*bmain); } /** \} */ diff --git a/source/blender/blenkernel/intern/lib_id.cc b/source/blender/blenkernel/intern/lib_id.cc index 3be47c8bd72..90f71ba5c18 100644 --- a/source/blender/blenkernel/intern/lib_id.cc +++ b/source/blender/blenkernel/intern/lib_id.cc @@ -211,7 +211,7 @@ void BKE_lib_id_clear_library_data(Main *bmain, ID *id, const int flags) (id->flag & ID_FLAG_EMBEDDED_DATA) == 0; if (id_in_mainlist) { - BKE_main_namemap_remove_name(bmain, id, BKE_id_name(*id)); + BKE_main_namemap_remove_id(*bmain, *id); } lib_id_library_local_paths(bmain, nullptr, id->lib, id); @@ -874,7 +874,7 @@ void BKE_id_move_to_same_lib(Main &bmain, ID &id, const ID &owner_id) return; } - BKE_main_namemap_remove_name(&bmain, &id, BKE_id_name(id)); + BKE_main_namemap_remove_id(bmain, id); id.lib = owner_id.lib; id.tag |= ID_TAG_INDIRECT; @@ -1150,7 +1150,7 @@ void BKE_libblock_management_main_remove(Main *bmain, void *idv) ListBase *lb = which_libbase(bmain, GS(id->name)); BKE_main_lock(bmain); BLI_remlink(lb, id); - BKE_main_namemap_remove_name(bmain, id, BKE_id_name(*id)); + BKE_main_namemap_remove_id(*bmain, *id); id->tag |= ID_TAG_NO_MAIN; bmain->is_memfile_undo_written = false; BKE_main_unlock(bmain); @@ -1898,7 +1898,7 @@ IDNewNameResult BKE_id_new_name_validate(Main &bmain, STRNCPY(orig_name, name); } - const bool had_name_collision = BKE_main_namemap_get_name(&bmain, &id, name, false); + const bool had_name_collision = BKE_main_namemap_get_unique_name(bmain, id, name); if (had_name_collision && ELEM(mode, IDNewNameMode::RenameExistingAlways, IDNewNameMode::RenameExistingSameRoot)) @@ -2335,7 +2335,7 @@ IDNewNameResult BKE_libblock_rename(Main &bmain, if (STREQ(BKE_id_name(id), name.c_str())) { return {IDNewNameResult::Action::UNCHANGED, nullptr}; } - BKE_main_namemap_remove_name(&bmain, &id, BKE_id_name(id)); + BKE_main_namemap_remove_id(bmain, id); ListBase &lb = *which_libbase(&bmain, GS(id.name)); IDNewNameResult result = BKE_id_new_name_validate(bmain, lb, id, name.c_str(), mode, true); if (!ELEM(result.action, diff --git a/source/blender/blenkernel/intern/lib_id_delete.cc b/source/blender/blenkernel/intern/lib_id_delete.cc index e6f3b43ff2a..6e33aeb08d6 100644 --- a/source/blender/blenkernel/intern/lib_id_delete.cc +++ b/source/blender/blenkernel/intern/lib_id_delete.cc @@ -168,7 +168,7 @@ static int id_free(Main *bmain, void *idv, int flag, const bool use_flag_from_id ListBase *lb = which_libbase(bmain, type); BLI_remlink(lb, id); if ((flag & LIB_ID_FREE_NO_NAMEMAP_REMOVE) == 0) { - BKE_main_namemap_remove_name(bmain, id, id->name + 2); + BKE_main_namemap_remove_id(*bmain, *id); } } @@ -293,7 +293,7 @@ static size_t id_delete(Main *bmain, (ID_IS_LINKED(id_iter) && ids_to_delete.contains(&id_iter->lib->id))) { BLI_remlink(lb, id_iter); - BKE_main_namemap_remove_name(bmain, id_iter, id_iter->name + 2); + BKE_main_namemap_remove_id(*bmain, *id_iter); ids_to_delete.add(id_iter); id_remapper.add(id_iter, nullptr); /* Do not tag as no_main now, we want to unlink it first (lower-level ID management @@ -306,7 +306,7 @@ static size_t id_delete(Main *bmain, Key *shape_key = BKE_key_from_id(id_iter); if (shape_key && !ids_to_delete.contains(&shape_key->id)) { BLI_remlink(&bmain->shapekeys, &shape_key->id); - BKE_main_namemap_remove_name(bmain, &shape_key->id, shape_key->id.name + 2); + BKE_main_namemap_remove_id(*bmain, shape_key->id); ids_to_delete.add(&shape_key->id); id_remapper.add(&shape_key->id, nullptr); } diff --git a/source/blender/blenkernel/intern/lib_id_test.cc b/source/blender/blenkernel/intern/lib_id_test.cc index 51c2af72936..1a06301dbd6 100644 --- a/source/blender/blenkernel/intern/lib_id_test.cc +++ b/source/blender/blenkernel/intern/lib_id_test.cc @@ -5,6 +5,7 @@ #include "BLI_listbase.h" #include "BLI_string.h" +#include "BLI_string_ref.hh" #include "BKE_idtype.hh" #include "BKE_lib_id.hh" @@ -62,9 +63,9 @@ static void change_lib(Main *bmain, ID *id, Library *lib) if (id->lib == lib) { return; } - BKE_main_namemap_remove_name(bmain, id, id->name + 2); + BKE_main_namemap_remove_id(*bmain, *id); id->lib = lib; - BKE_main_namemap_get_name(bmain, id, id->name + 2, false); + BKE_main_namemap_get_unique_name(*bmain, *id, id->name + 2); } static IDNewNameResult change_name(Main *bmain, ID *id, const char *name, const IDNewNameMode mode) @@ -103,7 +104,7 @@ TEST(lib_id_main_sort, linked_ids_1) EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_c, id_a, id_b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -118,7 +119,7 @@ TEST(lib_id_main_unique_name, local_ids_rename_existing_never) ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); test_lib_id_main_sort_check_order({id_a, id_b, id_c}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); IDNewNameResult result; @@ -134,7 +135,7 @@ TEST(lib_id_main_unique_name, local_ids_rename_existing_never) EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_a, id_c, id_b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); /* Rename to same root name. */ result = change_name(ctx.bmain, id_c, "OB_A", IDNewNameMode::RenameExistingNever); @@ -148,27 +149,27 @@ TEST(lib_id_main_unique_name, local_ids_rename_existing_never) EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_a, id_c, id_b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); - /* Test lower-level #BKE_main_namemap_get_name itself. */ + /* Test lower-level #BKE_main_namemap_get_unique_name itself. */ /* Name already in use, needs additional numeric suffix. */ char future_name[MAX_ID_NAME - 2]; STRNCPY(future_name, "OB_B"); - EXPECT_TRUE(BKE_main_namemap_get_name(ctx.bmain, id_c, future_name, false)); + EXPECT_TRUE(BKE_main_namemap_get_unique_name(*ctx.bmain, *id_c, future_name)); EXPECT_STREQ(future_name, "OB_B.001"); /* Name not already in use, no need to alter it. */ STRNCPY(future_name, "OB_BBBB"); - EXPECT_FALSE(BKE_main_namemap_get_name(ctx.bmain, id_c, future_name, false)); + EXPECT_FALSE(BKE_main_namemap_get_unique_name(*ctx.bmain, *id_c, future_name)); EXPECT_STREQ(future_name, "OB_BBBB"); /* Name too long, needs to be truncated. */ STRNCPY(future_name, "OB_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); change_name(ctx.bmain, id_a, future_name, IDNewNameMode::RenameExistingNever); EXPECT_STREQ(id_a->name + 2, future_name); EXPECT_STREQ(future_name, "OB_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); - EXPECT_TRUE(BKE_main_namemap_get_name(ctx.bmain, id_c, future_name, false)); - EXPECT_STREQ(future_name, "OB_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); + EXPECT_TRUE(BKE_main_namemap_get_unique_name(*ctx.bmain, *id_c, future_name)); + EXPECT_STREQ(future_name, "OB_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); } TEST(lib_id_main_unique_name, local_ids_rename_existing_always) @@ -181,7 +182,7 @@ TEST(lib_id_main_unique_name, local_ids_rename_existing_always) ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); test_lib_id_main_sort_check_order({id_a, id_b, id_c}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); IDNewNameResult result; @@ -196,7 +197,7 @@ TEST(lib_id_main_unique_name, local_ids_rename_existing_always) EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_c, id_a, id_b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); /* Rename to same root name. */ result = change_name(ctx.bmain, id_a, "OB_A", IDNewNameMode::RenameExistingAlways); @@ -209,7 +210,7 @@ TEST(lib_id_main_unique_name, local_ids_rename_existing_always) EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_a, id_c, id_b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -224,7 +225,7 @@ TEST(lib_id_main_unique_name, local_ids_rename_existing_same_root) ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); test_lib_id_main_sort_check_order({id_a, id_b, id_c}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); IDNewNameResult result; @@ -240,7 +241,7 @@ TEST(lib_id_main_unique_name, local_ids_rename_existing_same_root) EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_a, id_c, id_b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); /* Rename to same root name. */ result = change_name(ctx.bmain, id_c, "OB_A", IDNewNameMode::RenameExistingSameRoot); @@ -253,7 +254,7 @@ TEST(lib_id_main_unique_name, local_ids_rename_existing_same_root) EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_c, id_a, id_b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -269,7 +270,7 @@ TEST(lib_id_main_unique_name, linked_ids_1) ID *id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_A")); ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); change_lib(ctx.bmain, id_a, lib_a); id_sort_by_name(&ctx.bmain->objects, id_a, nullptr); @@ -283,7 +284,7 @@ TEST(lib_id_main_unique_name, linked_ids_1) EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_c, id_a, id_b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); change_lib(ctx.bmain, id_b, lib_b); id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); @@ -294,17 +295,17 @@ TEST(lib_id_main_unique_name, linked_ids_1) EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_c, id_a, id_b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } static void change_name_global(Main *bmain, ID *id, const char *name) { - BKE_main_namemap_remove_name(bmain, id, id->name + 2); + BKE_main_namemap_remove_id(*bmain, *id); BLI_strncpy(id->name + 2, name, MAX_NAME); - BKE_main_namemap_get_name(bmain, id, id->name + 2, true); + BKE_main_global_namemap_get_unique_name(*bmain, *id, id->name + 2); id_sort_by_name(&bmain->objects, id, nullptr); } @@ -320,7 +321,7 @@ TEST(lib_id_main_global_unique_name, linked_ids_1) ID *id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_A")); ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); change_lib(ctx.bmain, id_a, lib_a); id_sort_by_name(&ctx.bmain->objects, id_a, nullptr); @@ -335,7 +336,7 @@ TEST(lib_id_main_global_unique_name, linked_ids_1) EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_c, id_a, id_b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); change_lib(ctx.bmain, id_b, lib_a); id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); @@ -351,7 +352,7 @@ TEST(lib_id_main_global_unique_name, linked_ids_1) EXPECT_TRUE(ctx.bmain->objects.last == id_a); test_lib_id_main_sort_check_order({id_c, id_b, id_a}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); change_name(ctx.bmain, id_b, "OB_C", IDNewNameMode::RenameExistingNever); EXPECT_STREQ(id_b->name + 2, "OB_C"); @@ -361,7 +362,7 @@ TEST(lib_id_main_global_unique_name, linked_ids_1) EXPECT_TRUE(ctx.bmain->objects.last == id_a); test_lib_id_main_sort_check_order({id_c, id_b, id_a}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); } TEST(lib_id_main_unique_name, ids_sorted_by_default) @@ -374,7 +375,7 @@ TEST(lib_id_main_unique_name, ids_sorted_by_default) ID *id_yes = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Yes")); test_lib_id_main_sort_check_order({id_bar, id_baz, id_foo, id_yes}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -406,7 +407,7 @@ TEST(lib_id_main_unique_name, ids_sorted_by_default_with_libraries) test_lib_id_main_sort_check_order({id_bar, id_baz, id_foo, id_yes, id_l1a, id_l1c, id_l2b}); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -426,7 +427,7 @@ TEST(lib_id_main_unique_name, name_too_long_handling) EXPECT_STREQ(id_b->name + 2, "Another_Long_Name_That_Does_Not_Fit_And_Has_A_Number_Suffix.123"); EXPECT_STREQ(id_c->name + 2, "Name_That_Has_Too_Long_Number_Suffix.1234567890"); /* Unchanged */ - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -462,7 +463,7 @@ TEST(lib_id_main_unique_name, create_equivalent_numeric_suffixes) EXPECT_STREQ(id_j->name + 2, "Foo..001"); EXPECT_STREQ(id_k->name + 2, "Foo..000"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); /* Now create their exact duplicates again, and check what happens. */ id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.123")); @@ -489,7 +490,56 @@ TEST(lib_id_main_unique_name, create_equivalent_numeric_suffixes) EXPECT_STREQ(id_j->name + 2, "Foo..003"); EXPECT_STREQ(id_k->name + 2, "Foo..004"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); + + EXPECT_EQ(ctx.bmain->name_map_global, nullptr); +} + +TEST(lib_id_main_unique_name, re_create_equivalent_numeric_suffixes) +{ + LibIDMainSortTestContext ctx; + + /* Create names where many of their numeric suffixes are + * the same number, yet the names are different and thus + * should be allowed as-is. */ + ID *id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.123")); + ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.001")); + ID *id_c = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.01")); + ID *id_d = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.1")); + ID *id_e = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + + EXPECT_STREQ(BKE_id_name(*id_a), "Foo.123"); + EXPECT_STREQ(BKE_id_name(*id_b), "Foo.001"); + EXPECT_STREQ(BKE_id_name(*id_c), "Foo.01"); + EXPECT_STREQ(BKE_id_name(*id_d), "Foo.1"); + EXPECT_STREQ(BKE_id_name(*id_e), "Foo"); + + /* Deleting 'Foo.1' will _not_ mark number `1` as available, since its internal multi-usages + * counter will still be at `2`, for the 'Foo.01' and 'Foo.001' IDs still present. + * + * So the number `1` is not available, and since `123` is also used, the next free value is `2`. + */ + BKE_id_delete(ctx.bmain, id_d); + id_d = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.123")); + EXPECT_STREQ(BKE_id_name(*id_d), "Foo.002"); + + /* However, while deleting 'Foo.001' will _not_ mark number `1` as available, it _will_ remove + * the exact name from the full names map. + * + * So adding again 'Foo.001' will succeed and not modify the name at all. */ + BKE_id_delete(ctx.bmain, id_b); + id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.001")); + EXPECT_STREQ(BKE_id_name(*id_b), "Foo.001"); + + /* Finally, removing the last two users of number `1` makes it available again. */ + BKE_id_delete(ctx.bmain, id_b); + BKE_id_delete(ctx.bmain, id_c); + id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + EXPECT_STREQ(BKE_id_name(*id_b), "Foo.001"); + id_c = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.01")); + EXPECT_STREQ(BKE_id_name(*id_c), "Foo.01"); + + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -508,7 +558,7 @@ TEST(lib_id_main_unique_name, zero_suffix_is_never_assigned) EXPECT_STREQ(id_001->name + 2, "Foo.001"); EXPECT_STREQ(id_003->name + 2, "Foo.003"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -524,12 +574,12 @@ TEST(lib_id_main_unique_name, remove_after_dup_get_original_name) EXPECT_STREQ(id_b->name + 2, "Foo.001"); BKE_id_free(ctx.bmain, id_a); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); EXPECT_STREQ(id_a->name + 2, "Foo"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -550,14 +600,14 @@ TEST(lib_id_main_unique_name, name_number_suffix_assignment) EXPECT_STREQ(ids[1]->name + 2, "Foo.001"); EXPECT_STREQ(ids[total_object_count / 2 - 1]->name + 2, "Foo.599"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); /* Free some of the objects. */ BKE_id_free(ctx.bmain, ids[10]); BKE_id_free(ctx.bmain, ids[20]); BKE_id_free(ctx.bmain, ids[30]); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); /* Create objects again; they should get suffixes that were just freed up. */ ID *id_010 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); @@ -573,7 +623,7 @@ TEST(lib_id_main_unique_name, name_number_suffix_assignment) ID *id_600 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); EXPECT_STREQ(id_600->name + 2, "Foo.600"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); /* Max possible numeric suffix. */ ID *id_max = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.999999999")); @@ -582,26 +632,43 @@ TEST(lib_id_main_unique_name, name_number_suffix_assignment) ID *id_max1 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.999999999")); EXPECT_STREQ(id_max1->name + 2, "Foo.601"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); /* Now create the rest of objects, to use all the suffixes up to 1k. * Once all the ones up to 1k are used, the logic will fall back to * "use largest number seen + 1", but the largest one is already the max - * possible. So it will shorten the name part and restart the counter, - * i.e. "Fo.001". */ + * possible. So it will modify the name part and restart the counter, + * i.e. "Foo_001.001". */ for (int i = total_object_count / 2; i < total_object_count; ++i) { ids[i] = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); } - /* At this point creating "Foo" based objects will fall always - * result in shortened name to "Fo". */ - ID *id_fo178 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); - EXPECT_STREQ(id_fo178->name + 2, "Fo.178"); - ID *id_fo179 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.2000")); - EXPECT_STREQ(id_fo179->name + 2, "Fo.179"); - ID *id_fo180 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.999999999")); - EXPECT_STREQ(id_fo180->name + 2, "Fo.180"); + /* At this point creating "Foo" based objects will always result in names extended with a 3 or + * more digits numeric suffix, e.g. "Foo_001.001". + * + * NOTE: The random 3-digits suffix added to the base name is expected to be stable, as the + * requested base name remains the same. This is why the added numeric suffixes can be predicted + * here. */ + ID *id_foo_001_178 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + EXPECT_STREQ(BKE_id_name(*id_foo_001_178), "Foo_001.178"); + ID *id_foo_001_179 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.2000")); + EXPECT_STREQ(BKE_id_name(*id_foo_001_179), "Foo_001.179"); + ID *id_foo_001_180 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.999999999")); + EXPECT_STREQ(BKE_id_name(*id_foo_001_180), "Foo_001.180"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + /* Longer names will be shortened, when no more numeric suffixes are available. */ + for (int i = 0; i < total_object_count; ++i) { + ids[i] = static_cast(BKE_id_new(ctx.bmain, ID_OB, "ALongerName")); + } + /* Max possible numeric suffix. */ + id_max = static_cast(BKE_id_new(ctx.bmain, ID_OB, "ALongerName.999999999")); + EXPECT_STREQ(BKE_id_name(*id_max), "ALongerName.999999999"); + + ID *id_alongernam = static_cast(BKE_id_new(ctx.bmain, ID_OB, "ALongerName")); + EXPECT_STREQ(BKE_id_name(*id_alongernam), "ALongerNam"); + ID *id_alongernam001 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "ALongerName")); + EXPECT_STREQ(BKE_id_name(*id_alongernam001), "ALongerNam.001"); + + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -618,7 +685,7 @@ TEST(lib_id_main_unique_name, renames_with_duplicates) EXPECT_STREQ(id_b->name + 2, "Foo.001"); EXPECT_STREQ(id_c->name + 2, "Bar"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); BKE_libblock_rename(*ctx.bmain, *id_a, "Foo.002"); EXPECT_STREQ(id_a->name + 2, "Foo.002"); @@ -629,7 +696,7 @@ TEST(lib_id_main_unique_name, renames_with_duplicates) BKE_libblock_rename(*ctx.bmain, *id_b, "Bar"); EXPECT_STREQ(id_b->name + 2, "Bar"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -646,7 +713,7 @@ TEST(lib_id_main_unique_name, names_are_unique_per_id_type) EXPECT_STREQ(id_b->name + 2, "Foo"); /* Different types (OB & CA) can have the same name. */ EXPECT_STREQ(id_c->name + 2, "Foo.001"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } @@ -663,7 +730,7 @@ TEST(lib_id_main_unique_name, name_huge_number_suffix) ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "SuperLong.1234567890")); EXPECT_STREQ(id_b->name + 2, "SuperLong.001"); - EXPECT_TRUE(BKE_main_namemap_validate(ctx.bmain)); + EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain)); EXPECT_EQ(ctx.bmain->name_map_global, nullptr); } diff --git a/source/blender/blenkernel/intern/lib_override.cc b/source/blender/blenkernel/intern/lib_override.cc index b5b94e8c78a..290fe63f0ec 100644 --- a/source/blender/blenkernel/intern/lib_override.cc +++ b/source/blender/blenkernel/intern/lib_override.cc @@ -288,8 +288,9 @@ static ID *lib_override_library_create_from(Main *bmain, * While in normal cases this would not be an issue, when files start to get heavily broken and * not sound, such conflicts can become a source of problems. */ if (!STREQ(local_id->name + 2, reference_id->name + 2)) { + BKE_main_namemap_remove_id(*bmain, *local_id); BLI_strncpy(local_id->name + 2, reference_id->name + 2, MAX_ID_NAME - 2); - BKE_main_namemap_get_name(bmain, local_id, local_id->name + 2, true); + BKE_main_global_namemap_get_unique_name(*bmain, *local_id, local_id->name + 2); id_sort_by_name(which_libbase(bmain, GS(local_id->name)), local_id, nullptr); } @@ -3784,7 +3785,7 @@ void BKE_lib_override_library_main_resync(Main *bmain, lib_override_cleanup_after_resync(bmain); - BLI_assert(BKE_main_namemap_validate(bmain)); + BLI_assert(BKE_main_namemap_validate(*bmain)); } void BKE_lib_override_library_delete(Main *bmain, ID *id_root) @@ -5055,7 +5056,7 @@ void BKE_lib_override_library_update(Main *bmain, ID *local) } /* Remove the pair (idname, lib) of this temp id from the name map. */ - BKE_main_namemap_remove_name(bmain, tmp_id, tmp_id->name + 2); + BKE_main_namemap_remove_id(*bmain, *tmp_id); tmp_id->lib = local->lib; @@ -5071,7 +5072,7 @@ void BKE_lib_override_library_update(Main *bmain, ID *local) Key *tmp_key = BKE_key_from_id(tmp_id); if (local_key != nullptr && tmp_key != nullptr) { tmp_key->id.flag |= (local_key->id.flag & ID_FLAG_EMBEDDED_DATA_LIB_OVERRIDE); - BKE_main_namemap_remove_name(bmain, &tmp_key->id, tmp_key->id.name + 2); + BKE_main_namemap_remove_id(*bmain, tmp_key->id); tmp_key->id.lib = local_key->id.lib; STRNCPY(tmp_key->id.name, local_key->id.name); } @@ -5151,7 +5152,7 @@ void BKE_lib_override_library_main_update(Main *bmain) * since those always use G_MAIN when they need access to a Main database. */ Main *orig_gmain = BKE_blender_globals_main_swap(bmain); - BLI_assert(BKE_main_namemap_validate(bmain)); + BLI_assert(BKE_main_namemap_validate(*bmain)); FOREACH_MAIN_ID_BEGIN (bmain, id) { if (id->override_library != nullptr) { @@ -5160,7 +5161,7 @@ void BKE_lib_override_library_main_update(Main *bmain) } FOREACH_MAIN_ID_END; - BLI_assert(BKE_main_namemap_validate(bmain)); + BLI_assert(BKE_main_namemap_validate(*bmain)); Main *tmp_gmain = BKE_blender_globals_main_swap(orig_gmain); BLI_assert(tmp_gmain == bmain); diff --git a/source/blender/blenkernel/intern/library.cc b/source/blender/blenkernel/intern/library.cc index dca57d3dc6a..f710b1f886a 100644 --- a/source/blender/blenkernel/intern/library.cc +++ b/source/blender/blenkernel/intern/library.cc @@ -44,9 +44,7 @@ using namespace blender::bke::library; static void library_runtime_reset(Library *lib) { - if (lib->runtime->name_map) { - BKE_main_namemap_destroy(&lib->runtime->name_map); - } + BKE_main_namemap_destroy(&lib->runtime->name_map); } static void library_init_data(ID *id) diff --git a/source/blender/blenkernel/intern/main.cc b/source/blender/blenkernel/intern/main.cc index 375aa68b1a1..e0af55113dd 100644 --- a/source/blender/blenkernel/intern/main.cc +++ b/source/blender/blenkernel/intern/main.cc @@ -157,12 +157,8 @@ void BKE_main_clear(Main &bmain) } /* NOTE: `name_map` in libraries are freed together with the library IDs above. */ - if (bmain.name_map) { - BKE_main_namemap_destroy(&bmain.name_map); - } - if (bmain.name_map_global) { - BKE_main_namemap_destroy(&bmain.name_map_global); - } + BKE_main_namemap_destroy(&bmain.name_map); + BKE_main_namemap_destroy(&bmain.name_map_global); } void BKE_main_destroy(Main &bmain) @@ -448,9 +444,9 @@ void BKE_main_merge(Main *bmain_dst, Main **r_bmain_src, MainMergeReport &report /* Remapping above may have made some IDs local. So namemap needs to be cleared, and moved IDs * need to be re-sorted. */ - BKE_main_namemap_clear(bmain_dst); + BKE_main_namemap_clear(*bmain_dst); - BLI_assert(BKE_main_namemap_validate(bmain_dst)); + BLI_assert(BKE_main_namemap_validate(*bmain_dst)); BKE_main_free(bmain_src); *r_bmain_src = nullptr; diff --git a/source/blender/blenkernel/intern/main_namemap.cc b/source/blender/blenkernel/intern/main_namemap.cc index 9a366d69871..afb1c793c89 100644 --- a/source/blender/blenkernel/intern/main_namemap.cc +++ b/source/blender/blenkernel/intern/main_namemap.cc @@ -13,12 +13,11 @@ #include "BKE_main_namemap.hh" #include "BLI_assert.h" -#include "BLI_bitmap.h" -#include "BLI_ghash.h" +#include "BLI_bit_span_ops.hh" +#include "BLI_bit_vector.hh" #include "BLI_listbase.h" #include "BLI_map.hh" #include "BLI_math_base.hh" -#include "BLI_set.hh" #include "BLI_string.h" #include "BLI_string_utf8.h" #include "BLI_string_utils.hh" @@ -29,6 +28,8 @@ #include "CLG_log.h" +#include + static CLG_LogRef LOG = {"bke.main_namemap"}; // #define DEBUG_PRINT_MEMORY_USAGE @@ -36,328 +37,505 @@ static CLG_LogRef LOG = {"bke.main_namemap"}; using namespace blender; /* Assumes and ensure that the suffix number can never go beyond 1 billion. */ -#define MAX_NUMBER 1000000000 -/* We do not want to get "name.000", so minimal number is 1. */ -#define MIN_NUMBER 1 - -/** - * Helper building final ID name from given base_name and number. - * - * If everything goes well and we do generate a valid final ID name in given name, we return - * true. In case the final name would overflow the allowed ID name length, or given number is - * bigger than maximum allowed value, we truncate further the base_name (and given name, which is - * assumed to have the same 'base_name' part), and return false. - */ -static bool id_name_final_build(char *name, char *base_name, size_t base_name_len, int number) -{ - char number_str[11]; /* Dot + nine digits + null terminator. */ - size_t number_str_len = SNPRINTF_RLEN(number_str, ".%.3d", number); - - /* If the number would lead to an overflow of the maximum ID name length, we need to truncate - * the base name part and do all the number checks again. */ - if (base_name_len + number_str_len >= MAX_NAME || number >= MAX_NUMBER) { - if (base_name_len + number_str_len >= MAX_NAME) { - base_name_len = MAX_NAME - number_str_len - 1; - } - else { - base_name_len--; - } - base_name[base_name_len] = '\0'; - - /* Code above may have generated invalid utf-8 string, due to raw truncation. - * Ensure we get a valid one now. */ - base_name_len -= size_t(BLI_str_utf8_invalid_strip(base_name, base_name_len)); - - /* Also truncate orig name, and start the whole check again. */ - name[base_name_len] = '\0'; - return false; - } - - /* We have our final number, we can put it in name and exit the function. */ - BLI_strncpy(name + base_name_len, number_str, number_str_len + 1); - return true; -} - -/* Key used in set/map lookups: just a string name. */ -struct UniqueName_Key { - char name[MAX_NAME]; - uint64_t hash() const - { - return BLI_ghashutil_strhash_n(name, MAX_NAME); - } - bool operator==(const UniqueName_Key &o) const - { - return !BLI_ghashutil_strcmp(name, o.name); - } -}; +constexpr int MAX_NUMBER = 999999999; +/* Value representing that there is no available number. Must be negative value. */ +constexpr int NO_AVAILABLE_NUMBER = -1; /* Tracking of used numeric suffixes. For each base name: * - * - Exactly track which of the lowest 1024 suffixes are in use, + * - Exactly track which of the lowest 1023 suffixes are in use, * whenever there is a name collision we pick the lowest "unused" - * one. This is done with a bit map. - * - Above 1024, do not track them exactly, just track the maximum + * one. This is done with a bit vector. + * - Above 1023, do not track them exactly, just track the maximum * suffix value seen so far. Upon collision, assign number that is * one larger. */ struct UniqueName_Value { - static constexpr uint max_exact_tracking = 1024; - BLI_BITMAP_DECLARE(mask, max_exact_tracking); - int max_value = 0; + static constexpr int max_exact_tracking = 1023; + std::optional max_value_in_use = {}; + BitVector<> mask = {}; + /* Only created when required. Used to manage cases where the same numeric value is used by + * several unique full names ('Foo.1' and 'Foo.001' e.g.). + * + * The key is the suffix numeric value. + * The value is the number of how many different full names using that same base name and numeric + * suffix value are currently registered. + * + * This code will never generate such cases for local maps, but it needs to support users doing + * so explicitely. + * + * For global maps, this is a much more common case, as duplicates of ID names across libraries + * are fairly common. */ + std::optional> numbers_multi_usages = std::nullopt; - void mark_used(int number) + void mark_used(const int number) { - if (number >= 0 && number < max_exact_tracking) { - BLI_BITMAP_ENABLE(mask, number); - } - if (number < MAX_NUMBER) { - math::max_inplace(max_value, number); - } - } - - void mark_unused(int number) - { - if (number >= 0 && number < max_exact_tracking) { - BLI_BITMAP_DISABLE(mask, number); - } - if (number > 0 && number == max_value) { - --max_value; - } - } - - bool use_if_unused(int number) - { - if (number >= 0 && number < max_exact_tracking) { - if (!BLI_BITMAP_TEST_BOOL(mask, number)) { - BLI_BITMAP_ENABLE(mask, number); - math::max_inplace(max_value, number); - return true; + BLI_assert(number >= 0); + if (number >= 0 && number <= max_exact_tracking) { + if (this->mask.size() <= number) { + this->mask.resize(number + 1); + } + if (this->mask[number]) { + if (!this->numbers_multi_usages.has_value()) { + this->numbers_multi_usages.emplace(); + } + int &multi_usages_num = this->numbers_multi_usages->lookup_or_add(number, 1); + BLI_assert(multi_usages_num >= 1); + multi_usages_num++; + } + else { + this->mask[number].set(true); + } + } + if (number <= MAX_NUMBER) { + if (this->max_value_in_use) { + math::max_inplace(this->max_value_in_use.value(), number); + } + else { + this->max_value_in_use = number; } } - return false; } - int use_smallest_unused() + void mark_unused(const int number) { - /* Find the smallest available one <1k. - * However we never want to pick zero ("none") suffix, even if it is - * available, e.g. if Foo.001 was used and we want to create another - * Foo.001, we should return Foo.002 and not Foo. - * So while searching, mark #0 as "used" to make sure we don't find it, - * and restore the value afterwards. */ + BLI_assert(number >= 0); + if (number >= 0 && number <= max_exact_tracking) { + BLI_assert_msg(number < this->mask.size(), + "Trying to unregister a number suffix higher than current size of the bit " + "vector, should never happen."); - BLI_bitmap prev_first = mask[0]; - mask[0] |= 1; - int result = BLI_bitmap_find_first_unset(mask, max_exact_tracking); - if (result >= 0) { - BLI_BITMAP_ENABLE(mask, result); - math::max_inplace(max_value, result); + if (this->numbers_multi_usages.has_value() && this->numbers_multi_usages->contains(number)) { + int &multi_usages_num = this->numbers_multi_usages->lookup(number); + BLI_assert(multi_usages_num > 1); + multi_usages_num--; + if (multi_usages_num == 1) { + this->numbers_multi_usages->remove_contained(number); + } + /* Do not unset the matching bit, nor handle #max_value_in_use, since there are more IDs + * with the same base name and numeric suffix value. */ + return; + } + + this->mask[number].set(false); } - mask[0] |= prev_first & 1; /* Restore previous value of #0 bit. */ - return result; + if (number == this->max_value_in_use.value_or(NO_AVAILABLE_NUMBER)) { + if (number > 0) { + this->max_value_in_use.value()--; + } + else { + this->max_value_in_use.reset(); + } + } + } + + /** + * Return the smallest non-null known free number. + * + * \note This is only returns the true smallest unused number for values <= #max_exact_tracking. + * Otherwise, it simply returns the highest known value in use + 1. + */ + int get_smallest_unused() + { + if (this->mask.size() < 2) { + /* No numbered entry was ever registered for this name yet, so first non-null value is + * unused. */ + return 1; + } + /* Never pick the `0` value (e.g. if 'Foo.001' is used and another 'Foo.001' is requested, + * return 'Foo.002' and not 'Foo'). So only search on mask[1:] range. */ + BitSpan search_mask(this->mask.data(), IndexRange(1, this->mask.size() - 1)); + std::optional result = find_first_0_index(search_mask); + if (result) { + return int(*result + 1); + } + if (this->mask.size() <= max_exact_tracking) { + /* No need to increase size of the mask here, this will be done by calls to #mark_used once + * the final name with its final number has been defined. */ + return int(this->mask.size()); + } + if (this->max_value_in_use) { + if (this->max_value_in_use.value() + 1 <= MAX_NUMBER) { + return this->max_value_in_use.value() + 1; + } + return NO_AVAILABLE_NUMBER; + } + return 1; } }; /* Tracking of names for a single ID type. */ struct UniqueName_TypeMap { - /* Set of full names that are in use. */ - Set full_names; + /* Map of full names that are in use, and the amount of times they are used. + * For local maps, the number values must all always be `1`. + * For global maps, a same name can be used by several IDs from different libraries. */ + Map full_names; /* For each base name (i.e. without numeric suffix), track the * numeric suffixes that are in use. */ - Map base_name_to_num_suffix; + Map> base_name_to_num_suffix; }; struct UniqueName_Map { - UniqueName_TypeMap type_maps[INDEX_ID_MAX]; + std::array type_maps; + /* Whether this map covers all IDs in its Main, or only the local (or a specific library) ones. + */ + bool is_global; - UniqueName_TypeMap *find_by_type(short id_type) + UniqueName_Map(const bool is_global) : type_maps{}, is_global(is_global) {} + + UniqueName_TypeMap &find_by_type(const short id_type) { int index = BKE_idtype_idcode_to_index(id_type); - return index >= 0 ? &type_maps[index] : nullptr; + return type_maps[size_t(index)]; + } + + void populate(Main &bmain, Library *library, ID *ignore_id) + { + for (UniqueName_TypeMap &type_map : this->type_maps) { + type_map.full_names.clear(); + type_map.base_name_to_num_suffix.clear(); + } + ID *id; + FOREACH_MAIN_ID_BEGIN (&bmain, id) { + if ((id == ignore_id) || (!this->is_global && (id->lib != library))) { + continue; + } + UniqueName_TypeMap &type_map = this->find_by_type(GS(id->name)); + + /* Insert the full name into the map. */ + if (this->is_global) { + /* Global namemap is expected to have several IDs using the same name (from different + * libraries). */ + int &count = type_map.full_names.lookup_or_add_as(blender::StringRef{BKE_id_name(*id)}, 0); + count++; + if (count > 1) { + /* Name is already used at least once, just increase usercount. */ + continue; + } + } + else { + /* For non-global namemaps, there should only be one usage for each name, adding the + * fullname as key should always return `true`. */ + if (!type_map.full_names.add(BKE_id_name(*id), 1)) { + /* Do not assert, this code is also used by #BKE_main_namemap_validate_and_fix, where + * duplicates are expected. */ +#if 0 + BLI_assert_msg(false, + "The key (name) already exists in the namemap, should only happen when " + "`do_global` is true."); +#endif + continue; + } + } + + /* Get the name and number parts ("name.number"). */ + int number = 0; + const std::string name_base = BLI_string_split_name_number(BKE_id_name(*id), '.', number); + + /* Get and update the entry for this base name. */ + std::unique_ptr &val = type_map.base_name_to_num_suffix.lookup_or_add_as( + name_base, std::make_unique(UniqueName_Value{})); + val->mark_used(number); + } + FOREACH_MAIN_ID_END; + } + + /* Add the given name to the given #type_map, returns `true` if added, `false` if it was already + * in the map. */ + void add_name(UniqueName_TypeMap &type_map, + StringRef name_full, + StringRef name_base, + const int number) + { + BLI_assert(name_full.size() < MAX_NAME); + + if (this->is_global) { + /* By definition adding to global map is always sucessful. */ + int &count = type_map.full_names.lookup_or_add_as(name_full, 0); + if (!count) { + std::unique_ptr &val = type_map.base_name_to_num_suffix.lookup_or_add_as( + name_base, std::make_unique(UniqueName_Value{})); + val->mark_used(number); + } + count++; + return; + } + + if (!type_map.full_names.add(name_full, 1)) { + BLI_assert_msg( + false, "Adding a name to Main namemaps that was already in it, should never happen."); + return; + } + + std::unique_ptr &val = type_map.base_name_to_num_suffix.lookup_or_add_as( + name_base, std::make_unique(UniqueName_Value{})); + val->mark_used(number); + } + void add_name(const short id_type, StringRef name_full, StringRef name_base, const int number) + { + this->add_name(this->find_by_type(id_type), name_full, name_base, number); + } + + /* Remove a full name_full from the specified #type_map. Trying to remove an unknow + * (unregistered) name_full is an error. */ + void remove_full_name(UniqueName_TypeMap &type_map, blender::StringRef name_full) + { + BLI_assert(name_full.size() < MAX_NAME); + + if (this->is_global) { + /* By definition adding to global map is always sucessful. */ + int *count = type_map.full_names.lookup_ptr(name_full); + if (!count) { + BLI_assert_msg( + false, "Removing a name from Main namemaps that was not in it, should never happen."); + return; + } + if (*count > 1) { + (*count)--; + } + else { + BLI_assert(*count == 1); + type_map.full_names.remove_contained(name_full); + } + } + else if (!type_map.full_names.remove(name_full)) { + BLI_assert_msg( + false, "Removing a name from Main namemaps that was not in it, should never happen."); + return; + } + + int number = 0; + const std::string name_base = BLI_string_split_name_number(name_full, '.', number); + std::unique_ptr *val = type_map.base_name_to_num_suffix.lookup_ptr( + name_base); + if (val == nullptr) { + BLI_assert_unreachable(); + return; + } + val->get()->mark_unused(number); + if (!val->get()->max_value_in_use) { + /* This was the only base name usage, remove the whole key. */ + type_map.base_name_to_num_suffix.remove(name_base); + } + } + void remove_full_name(const short id_type, blender::StringRef name_full) + { + this->remove_full_name(this->find_by_type(id_type), name_full); } }; -UniqueName_Map *BKE_main_namemap_create() -{ - UniqueName_Map *map = MEM_new(__func__); - return map; -} - void BKE_main_namemap_destroy(UniqueName_Map **r_name_map) { -#ifdef DEBUG_PRINT_MEMORY_USAGE - if (*r_name_map) { - int64_t size_sets = 0; - int64_t size_maps = 0; - for (const UniqueName_TypeMap &type_map : (*r_name_map)->type_maps) { - size_sets += type_map.full_names.size_in_bytes(); - size_maps += type_map.base_name_to_num_suffix.size_in_bytes(); - } - printf("NameMap memory usage: sets %.1fKB, maps %.1fKB\n", - size_sets / 1024.0, - size_maps / 1024.0); + if (*r_name_map == nullptr) { + return; } +#ifdef DEBUG_PRINT_MEMORY_USAGE + int64_t size_full_names = 0; + int64_t size_base_names = 0; + for (const UniqueName_TypeMap &type_map : (*r_name_map)->type_maps) { + size_full_names += type_map.full_names.size_in_bytes(); + size_base_names += type_map.base_name_to_num_suffix.size_in_bytes(); + } + printf("NameMap memory usage: full_names %.1fKB, base_names %.1fKB\n", + size_full_names / 1024.0, + size_base_names / 1024.0); #endif MEM_SAFE_DELETE(*r_name_map); } -void BKE_main_namemap_clear(Main *bmain) +void BKE_main_namemap_clear(Main &bmain) { - for (Main *bmain_iter = bmain; bmain_iter != nullptr; bmain_iter = bmain_iter->next) { - if (bmain_iter->name_map != nullptr) { - BKE_main_namemap_destroy(&bmain_iter->name_map); - } - if (bmain_iter->name_map_global != nullptr) { - BKE_main_namemap_destroy(&bmain_iter->name_map_global); - } + for (Main *bmain_iter = &bmain; bmain_iter != nullptr; bmain_iter = bmain_iter->next) { + BKE_main_namemap_destroy(&bmain_iter->name_map); + BKE_main_namemap_destroy(&bmain_iter->name_map_global); for (Library *lib_iter = static_cast(bmain_iter->libraries.first); lib_iter != nullptr; lib_iter = static_cast(lib_iter->id.next)) { - if (lib_iter->runtime->name_map != nullptr) { - BKE_main_namemap_destroy(&lib_iter->runtime->name_map); - } + BKE_main_namemap_destroy(&lib_iter->runtime->name_map); } } } -/* `do_global` will generate a namemap for all IDs in current Main, regardless of their library. - * Note that duplicates (e.g.local ID and linked ID with same name) will only generate a single - * entry in the map then. */ -static void main_namemap_populate( - UniqueName_Map *name_map, Main *bmain, Library *library, ID *ignore_id, const bool do_global) -{ - BLI_assert_msg(name_map != nullptr, "name_map should not be null"); - for (UniqueName_TypeMap &type_map : name_map->type_maps) { - type_map.base_name_to_num_suffix.clear(); - } - ID *id; - FOREACH_MAIN_ID_BEGIN (bmain, id) { - if ((id == ignore_id) || (!do_global && (id->lib != library))) { - continue; - } - UniqueName_TypeMap *type_map = name_map->find_by_type(GS(id->name)); - BLI_assert(type_map != nullptr); - - /* Insert the full name into the set. */ - UniqueName_Key key; - STRNCPY(key.name, id->name + 2); - if (!type_map->full_names.add(key)) { - /* Do not assert, this code is also used by #BKE_main_namemap_validate_and_fix, where - * duplicates are expected. */ -#if 0 - BLI_assert_msg(do_global, - "The key (name) already exists in the namemap, should only happen when " - "`do_global` is true."); -#endif - continue; - } - - /* Get the name and number parts ("name.number"). */ - int number = MIN_NUMBER; - BLI_string_split_name_number(id->name + 2, '.', key.name, &number); - - /* Get and update the entry for this base name. */ - UniqueName_Value &val = type_map->base_name_to_num_suffix.lookup_or_add_default(key); - val.mark_used(number); - } - FOREACH_MAIN_ID_END; -} - -/* Get the name map object used for the given Main/ID. +/* Get the global (local and linked IDs) name map object used for the given Main/ID. * Lazily creates and populates the contents of the name map, if ensure_created is true. - * NOTE: if the contents are populated, the name of the given ID itself is not added. */ -static UniqueName_Map *get_namemap_for(Main *bmain, - ID *id, - const bool ensure_created, - const bool do_global) + * NOTE: if the contents are populated, the name of the given #ignore_id ID (if any) is not added. + */ +static UniqueName_Map *get_global_namemap_for(Main &bmain, + ID *ignore_id, + const bool ensure_created) { - if (do_global) { - if (ensure_created && bmain->name_map_global == nullptr) { - bmain->name_map_global = BKE_main_namemap_create(); - main_namemap_populate(bmain->name_map_global, bmain, id->lib, id, true); - } - return bmain->name_map_global; + if (ensure_created && bmain.name_map_global == nullptr) { + bmain.name_map_global = MEM_new(__func__, true); + bmain.name_map_global->populate(bmain, nullptr, ignore_id); } - - if (id->lib != nullptr) { - if (ensure_created && id->lib->runtime->name_map == nullptr) { - id->lib->runtime->name_map = BKE_main_namemap_create(); - main_namemap_populate(id->lib->runtime->name_map, bmain, id->lib, id, false); - } - return id->lib->runtime->name_map; - } - if (ensure_created && bmain->name_map == nullptr) { - bmain->name_map = BKE_main_namemap_create(); - main_namemap_populate(bmain->name_map, bmain, id->lib, id, false); - } - return bmain->name_map; + return bmain.name_map_global; } -/* Tries to add given name to the given name_map, returns `true` if added, `false` if it was - * already in the namemap. */ -static bool namemap_add_name(UniqueName_Map *name_map, ID *id, const char *name, const int number) +/* Get the local or library-specific (when #lib is not null) name map object used for the given + * Main/ID. Lazily creates and populates the contents of the name map, if ensure_created is true. + * NOTE: if the contents are populated, the name of the given #ignore_id ID (if any) is not added. + */ +static UniqueName_Map *get_namemap_for(Main &bmain, + Library *lib, + ID *ignore_id, + const bool ensure_created) { - BLI_assert(strlen(name) < MAX_NAME); - UniqueName_TypeMap *type_map = name_map->find_by_type(GS(id->name)); - BLI_assert(type_map != nullptr); - - UniqueName_Key key; - /* Remove full name from the set. */ - STRNCPY(key.name, name); - if (!type_map->full_names.add(key)) { - /* Name already in this namemap, nothing else to do. */ - return false; + if (lib != nullptr) { + if (ensure_created && lib->runtime->name_map == nullptr) { + lib->runtime->name_map = MEM_new(__func__, false); + lib->runtime->name_map->populate(bmain, lib, ignore_id); + } + return lib->runtime->name_map; } - - UniqueName_Value &val = type_map->base_name_to_num_suffix.lookup_or_add(key, {}); - val.mark_used(number); - return true; + if (ensure_created && bmain.name_map == nullptr) { + bmain.name_map = MEM_new(__func__, false); + bmain.name_map->populate(bmain, lib, ignore_id); + } + return bmain.name_map; } -bool BKE_main_namemap_get_name(Main *bmain, ID *id, char *name, const bool do_unique_in_bmain) +bool BKE_main_global_namemap_contain_name(Main &bmain, const short id_type, StringRef name) { -#ifndef __GNUC__ /* GCC warns with `nonull-compare`. */ - BLI_assert(bmain != nullptr); - BLI_assert(id != nullptr); -#endif - UniqueName_Map *name_map = get_namemap_for(bmain, id, true, do_unique_in_bmain); - UniqueName_Map *name_map_other = get_namemap_for(bmain, id, false, !do_unique_in_bmain); + UniqueName_Map *name_map = get_global_namemap_for(bmain, nullptr, true); BLI_assert(name_map != nullptr); - BLI_assert(strlen(name) < MAX_NAME); - UniqueName_TypeMap *type_map = name_map->find_by_type(GS(id->name)); - BLI_assert(type_map != nullptr); + BLI_assert(name.size() < MAX_NAME); + UniqueName_TypeMap &type_map = name_map->find_by_type(id_type); + + return type_map.full_names.contains(name); +} + +bool BKE_main_namemap_contain_name(Main &bmain, Library *lib, const short id_type, StringRef name) +{ + UniqueName_Map *name_map = get_namemap_for(bmain, lib, nullptr, true); + BLI_assert(name_map != nullptr); + BLI_assert(name.size() < MAX_NAME); + UniqueName_TypeMap &type_map = name_map->find_by_type(id_type); + + return type_map.full_names.contains(name); +} + +/** + * Helper building final ID name from given base_name and number. + * + * Return `true` in case of success (a valid final ID name can be generated from given #base_name + * and #number parameters). + * + * Return `false` in case either the given number is invalid (#NO_AVAILABLE_NUMBER), or the + * generated final name would be too long. #r_name_final is then set with a new, edited base name: + * - Shortened by one (utf-8) char in case of too long name. + * - Extended by a pseudo-random number in case the base name is short already (should only happen + * when #number is #NO_AVAILABLE_NUMBER). + */ +static bool id_name_final_build(UniqueName_TypeMap &type_map, + const StringRefNull base_name, + const int number, + std::string &r_name_final) +{ + /* In case no number value is available, current base name cannot be used to generate a final + * full name. */ + if (number != NO_AVAILABLE_NUMBER) { + BLI_assert(number >= 0 && number <= MAX_NUMBER); + r_name_final = fmt::format("{}.{:03}", base_name, number); + /* Most common case, there is a valid number suffix value and it fits in the #MAX_NAME lenght + * limit. + */ + if (r_name_final.size() < MAX_NAME) { + return true; + } + } + + /* The base name cannot be used as-is, it needs to be modified, and a new number suffix must be + * generated for it. */ + r_name_final = base_name; + + /* If the base name is long enough, shorten it by one (utf-8) char, until a base name with + * available number suffixes is found. */ + while (r_name_final.size() > 8) { + char base_name_modified[MAX_NAME]; + + BLI_strncpy(base_name_modified, r_name_final.c_str(), r_name_final.size() + 1); + base_name_modified[r_name_final.size() - 1] = '\0'; + /* Raw truncation of an utf8 string may generate invalid utf-8 charcode at the end. + * Ensure we get a valid one now. */ + BLI_str_utf8_invalid_strip(base_name_modified, r_name_final.size() - 1); + + r_name_final = base_name_modified; + std::unique_ptr *val = type_map.base_name_to_num_suffix.lookup_ptr( + r_name_final); + if (!val || val->get()->max_value_in_use.value_or(0) < MAX_NUMBER) { + return false; + } + } + /* Else, extend the name with an increasing three-or-more-digits number, as it's better to add + * gibberish at the end of a short name, rather than shorten it further. */ + uint64_t suffix = 1; + const StringRef new_base_name = r_name_final; + r_name_final = fmt::format("{}_{:03}", r_name_final, suffix); + while (r_name_final.size() < MAX_NAME - 12) { + std::unique_ptr *val = type_map.base_name_to_num_suffix.lookup_ptr( + r_name_final); + if (!val || val->get()->max_value_in_use.value_or(0) < MAX_NUMBER) { + return false; + } + suffix++; + r_name_final = fmt::format("{}_{:03}", new_base_name, suffix); + } + + /* WARNING: This code is absolute last defence, essentially theoritical case at this point. + * It is not expected to ever be reached in practice. It is also virtually impossible to actually + * test that case, given the enormous amount of IDs that would need to be created before it is + * reached. */ + CLOG_ERROR(&LOG, + "Impossible to find an available name for '%s' base name, even by editing that base " + "name. This should never happen in real-life scenarii. Now trying to brute-force " + "generate random names until a free one is found.", + base_name.c_str()); + BLI_assert(new_base_name.size() <= 8); + while (true) { + r_name_final = fmt::format("{}_{}", new_base_name, uint32_t(get_default_hash(r_name_final))); + std::unique_ptr *val = type_map.base_name_to_num_suffix.lookup_ptr( + r_name_final); + if (!val || val->get()->max_value_in_use.value_or(0) < MAX_NUMBER) { + return false; + } + } +} + +static bool namemap_get_name(Main &bmain, + ID &id, + std::string &r_name_full, + const bool do_unique_in_bmain) +{ + UniqueName_Map *name_map = do_unique_in_bmain ? get_global_namemap_for(bmain, &id, true) : + get_namemap_for(bmain, id.lib, &id, true); + UniqueName_Map *name_map_other = do_unique_in_bmain ? + get_namemap_for(bmain, id.lib, &id, false) : + get_global_namemap_for(bmain, &id, false); + BLI_assert(name_map != nullptr); + BLI_assert(r_name_full.size() < MAX_NAME); + UniqueName_TypeMap &type_map = name_map->find_by_type(GS(id.name)); bool is_name_changed = false; - UniqueName_Key key; while (true) { - /* Check if the full original name has a duplicate. */ - STRNCPY(key.name, name); - const bool has_dup = type_map->full_names.contains(key); - /* Get the name and number parts ("name.number"). */ - int number = MIN_NUMBER; - size_t base_name_len = BLI_string_split_name_number(name, '.', key.name, &number); + int number = 0; + const std::string name_base = BLI_string_split_name_number(r_name_full, '.', number); + std::unique_ptr &val = type_map.base_name_to_num_suffix.lookup_or_add_as( + name_base, std::make_unique(UniqueName_Value{})); - bool added_new = false; - UniqueName_Value &val = type_map->base_name_to_num_suffix.lookup_or_add_cb(key, [&]() { - added_new = true; - return UniqueName_Value(); - }); - if (added_new || !has_dup) { - /* This base name is not used at all yet, or the full original - * name has no duplicates. The latter could happen if splitting - * by number would produce the same values, for different name - * strings (e.g. Foo.001 and Foo.1). */ - val.mark_used(number); - - if (!has_dup) { - STRNCPY(key.name, name); - type_map->full_names.add(key); - } + /* If the full original name is unused, and its number suffix is unused, or is above the max + * managed value, the name can be used directly. + * + * NOTE: The second part of the check is designed to prevent issues with different names with + * the same name base, and the same numeric value as suffix, but written differently. + * E.g. `Mesh.001` and `Mesh.1` would both "use" the numeric suffix for base name `Mesh`. + * Removing `Mesh.1` would then mark `001` suffix as available, which would be incorrect. */ + if (!type_map.full_names.contains(r_name_full)) { + name_map->add_name(type_map, r_name_full, name_base, number); if (name_map_other != nullptr) { - namemap_add_name(name_map_other, id, name, number); + name_map_other->add_name(GS(id.name), r_name_full, name_base, number); } return is_name_changed; } @@ -367,125 +545,95 @@ bool BKE_main_namemap_get_name(Main *bmain, ID *id, char *name, const bool do_un * already been modified one way or another. */ is_name_changed = true; - /* The base name is already used. But our number suffix might not be used yet. */ - int number_to_use = -1; - if (val.use_if_unused(number)) { - /* Our particular number suffix is not used yet: use it. */ - number_to_use = number; - } - else { - /* Find lowest free under 1k and use it. */ - number_to_use = val.use_smallest_unused(); - - /* Did not find one under 1k. */ - if (number_to_use == -1) { - if (number >= MIN_NUMBER && number > val.max_value) { - val.max_value = number; - number_to_use = number; - } - else { - val.max_value++; - number_to_use = val.max_value; - } - } - } + /* The base name and current number suffix are already used. + * Request the lowest available valid number suffix (will return #NO_AVAILABLE_NUMBER if none + * are available for the current base name). */ + const int number_to_use = val->get_smallest_unused(); /* Try to build final name from the current base name and the number. - * Note that this can fail due to too long base name, or a too large number, - * in which case it will shorten the base name, and we'll start again. */ - BLI_assert(number_to_use >= MIN_NUMBER); - if (id_name_final_build(name, key.name, base_name_len, number_to_use)) { - /* All good, add final name to the set. */ - STRNCPY(key.name, name); - type_map->full_names.add(key); - if (name_map_other != nullptr) { - namemap_add_name(name_map_other, id, name, number); - } - break; + * Note that this will fail if the suffix number is #NO_AVAILABLE_NUMBER, or if the base name + * and suffix number number would give a too long name. In such cases, this call will modify + * the base name and put it into r_name_full, and a new iteration to find a suitable suffix + * number and valid full name is needed. */ + if (!id_name_final_build(type_map, name_base, number_to_use, r_name_full)) { + continue; } - } - return is_name_changed; + /* All good, add final name to the set. */ + name_map->add_name(type_map, r_name_full, name_base, number_to_use); + if (name_map_other != nullptr) { + name_map_other->add_name(GS(id.name), r_name_full, name_base, number_to_use); + } + return is_name_changed; + } } -static void namemap_remove_name(UniqueName_Map *name_map, ID *id, const char *name) +bool BKE_main_namemap_get_unique_name(Main &bmain, ID &id, char *r_name) { - BLI_assert(strlen(name) < MAX_NAME); - UniqueName_TypeMap *type_map = name_map->find_by_type(GS(id->name)); - BLI_assert(type_map != nullptr); - - UniqueName_Key key; - /* Remove full name from the set. */ - STRNCPY(key.name, name); - type_map->full_names.remove(key); - - int number = MIN_NUMBER; - BLI_string_split_name_number(name, '.', key.name, &number); - UniqueName_Value *val = type_map->base_name_to_num_suffix.lookup_ptr(key); - if (val == nullptr) { - return; - } - if (number == 0 && val->max_value == 0) { - /* This was the only base name usage, remove whole key. */ - type_map->base_name_to_num_suffix.remove(key); - return; - } - val->mark_unused(number); + std::string r_name_full = r_name; + BLI_assert(r_name_full.size() < MAX_NAME); + const bool is_name_modified = namemap_get_name(bmain, id, r_name_full, false); + BLI_assert(r_name_full.size() < MAX_NAME); + BLI_strncpy(r_name, r_name_full.c_str(), MAX_NAME); + return is_name_modified; +} +bool BKE_main_global_namemap_get_unique_name(Main &bmain, ID &id, char *r_name) +{ + std::string r_name_full = r_name; + BLI_assert(r_name_full.size() < MAX_NAME); + const bool is_name_modified = namemap_get_name(bmain, id, r_name_full, true); + BLI_assert(r_name_full.size() < MAX_NAME); + BLI_strncpy(r_name, r_name_full.c_str(), MAX_NAME); + return is_name_modified; } -void BKE_main_namemap_remove_name(Main *bmain, ID *id, const char *name) +void BKE_main_namemap_remove_id(Main &bmain, ID &id) { -#ifndef __GNUC__ /* GCC warns with `nonull-compare`. */ - BLI_assert(bmain != nullptr); - BLI_assert(id != nullptr); - BLI_assert(name != nullptr); -#endif - /* Name is empty or not initialized yet, nothing to remove. */ - if (name[0] == '\0') { + StringRef name = BKE_id_name(id); + if (name.is_empty()) { + /* Name is empty or not initialized yet, nothing to remove. */ return; } + const short id_code = GS(id.name); - UniqueName_Map *name_map_local = get_namemap_for(bmain, id, false, false); - if (name_map_local != nullptr) { - namemap_remove_name(name_map_local, id, name); + UniqueName_Map *name_map_local = get_namemap_for(bmain, id.lib, nullptr, false); + if (name_map_local) { + name_map_local->remove_full_name(id_code, name); } - UniqueName_Map *name_map_global = get_namemap_for(bmain, id, false, true); - if (name_map_global != nullptr) { - namemap_remove_name(name_map_global, id, name); + UniqueName_Map *name_map_global = get_global_namemap_for(bmain, nullptr, false); + if (name_map_global) { + name_map_global->remove_full_name(id_code, name); } } struct Uniqueness_Key { - char name[MAX_ID_NAME]; + std::string name; Library *lib; uint64_t hash() const { - return BLI_ghashutil_combine_hash(BLI_ghashutil_strhash_n(name, MAX_ID_NAME), - BLI_ghashutil_ptrhash(lib)); + return blender::get_default_hash(name, lib); } - bool operator==(const Uniqueness_Key &o) const + friend bool operator==(const Uniqueness_Key &a, const Uniqueness_Key &b) { - return lib == o.lib && !BLI_ghashutil_strcmp(name, o.name); + return a.lib == b.lib && a.name == b.name; } }; -static bool main_namemap_validate_and_fix(Main *bmain, const bool do_fix) +static bool main_namemap_validate_and_fix(Main &bmain, const bool do_fix) { Set id_names_libs; Set id_validated; bool is_valid = true; ListBase *lb_iter; - FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb_iter) { + FOREACH_MAIN_LISTBASE_BEGIN (&bmain, lb_iter) { LISTBASE_FOREACH_MUTABLE (ID *, id_iter, lb_iter) { if (id_validated.contains(id_iter)) { /* Do not re-check an already validated ID. */ continue; } - Uniqueness_Key key; - STRNCPY(key.name, id_iter->name); - key.lib = id_iter->lib; + Uniqueness_Key key = {id_iter->name, id_iter->lib}; if (!id_names_libs.add(key)) { is_valid = false; if (do_fix) { @@ -497,13 +645,13 @@ static bool main_namemap_validate_and_fix(Main *bmain, const bool do_fix) * to the validated set if it can now be added to `id_names_libs`, and will prevent * further checking (which would fail again, since the new ID name/lib key has already * been added to `id_names_libs`). */ - BKE_id_new_name_validate(*bmain, - *which_libbase(bmain, GS(id_iter->name)), + BKE_id_new_name_validate(bmain, + *which_libbase(&bmain, GS(id_iter->name)), *id_iter, nullptr, IDNewNameMode::RenameExistingNever, true); - STRNCPY(key.name, id_iter->name); + key.name = id_iter->name; if (!id_names_libs.add(key)) { /* This is a serious error, very likely a bug, keep it as CLOG_ERROR even when doing * fixes. */ @@ -524,17 +672,15 @@ static bool main_namemap_validate_and_fix(Main *bmain, const bool do_fix) } } - UniqueName_Map *name_map = get_namemap_for(bmain, id_iter, false, false); + UniqueName_Map *name_map = get_namemap_for(bmain, id_iter->lib, id_iter, false); if (name_map == nullptr) { continue; } - UniqueName_TypeMap *type_map = name_map->find_by_type(GS(id_iter->name)); - BLI_assert(type_map != nullptr); + UniqueName_TypeMap &type_map = name_map->find_by_type(GS(id_iter->name)); - UniqueName_Key key_namemap; /* Remove full name from the set. */ - STRNCPY(key_namemap.name, id_iter->name + 2); - if (!type_map->full_names.contains(key_namemap)) { + const std::string id_name = BKE_id_name(*id_iter); + if (!type_map.full_names.contains(id_name)) { is_valid = false; if (do_fix) { CLOG_WARN( @@ -558,44 +704,39 @@ static bool main_namemap_validate_and_fix(Main *bmain, const bool do_fix) FOREACH_MAIN_LISTBASE_END; Library *lib = nullptr; - UniqueName_Map *name_map = bmain->name_map; + UniqueName_Map *name_map = bmain.name_map; do { - if (name_map != nullptr) { + if (name_map) { int i = 0; for (short idcode = BKE_idtype_idcode_iter_step(&i); idcode != 0; idcode = BKE_idtype_idcode_iter_step(&i)) { - UniqueName_TypeMap *type_map = name_map->find_by_type(idcode); - if (type_map != nullptr) { - for (const UniqueName_Key &id_name : type_map->full_names) { - Uniqueness_Key key; - *(reinterpret_cast(key.name)) = idcode; - BLI_strncpy(key.name + 2, id_name.name, MAX_NAME); - key.lib = lib; - if (!id_names_libs.contains(key)) { - is_valid = false; - if (do_fix) { - CLOG_WARN( - &LOG, - "ID name '%s' (from library '%s') is listed in the namemap, but does not " - "exists in current Main", - key.name, - lib != nullptr ? lib->filepath : ""); - } - else { - CLOG_ERROR( - &LOG, - "ID name '%s' (from library '%s') is listed in the namemap, but does not " - "exists in current Main", - key.name, - lib != nullptr ? lib->filepath : ""); - } + UniqueName_TypeMap &type_map = name_map->find_by_type(idcode); + for (const std::string &id_name : type_map.full_names.keys()) { + Uniqueness_Key key = { + fmt::format("{}{}", StringRef{reinterpret_cast(&idcode), 2}, id_name), lib}; + if (!id_names_libs.contains(key)) { + is_valid = false; + if (do_fix) { + CLOG_WARN(&LOG, + "ID name '%s' (from library '%s') is listed in the namemap, but does not " + "exists in current Main", + key.name.c_str(), + lib != nullptr ? lib->filepath : ""); + } + else { + CLOG_ERROR(&LOG, + "ID name '%s' (from library '%s') is listed in the namemap, but does not " + "exists in current Main", + key.name.c_str(), + lib != nullptr ? lib->filepath : ""); } } } } } - lib = static_cast((lib == nullptr) ? bmain->libraries.first : lib->id.next); + + lib = static_cast((lib == nullptr) ? bmain.libraries.first : lib->id.next); name_map = (lib != nullptr) ? lib->runtime->name_map : nullptr; } while (lib != nullptr); @@ -609,14 +750,14 @@ static bool main_namemap_validate_and_fix(Main *bmain, const bool do_fix) return is_valid; } -bool BKE_main_namemap_validate_and_fix(Main *bmain) +bool BKE_main_namemap_validate_and_fix(Main &bmain) { const bool is_valid = main_namemap_validate_and_fix(bmain, true); BLI_assert(main_namemap_validate_and_fix(bmain, false)); return is_valid; } -bool BKE_main_namemap_validate(Main *bmain) +bool BKE_main_namemap_validate(Main &bmain) { return main_namemap_validate_and_fix(bmain, false); } diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index 70e53834285..02dc34def83 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -363,7 +363,7 @@ void blo_join_main(ListBase *mainlist) } /* Will no longer be valid after joining. */ - BKE_main_namemap_clear(mainl); + BKE_main_namemap_clear(*mainl); while ((tojoin = mainl->next)) { BLI_assert(((tojoin->curlib->runtime->tag & LIBRARY_IS_ASSET_EDIT_FILE) != 0) == @@ -413,7 +413,7 @@ void blo_split_main(ListBase *mainlist, Main *main) } /* Will no longer be valid after splitting. */ - BKE_main_namemap_clear(main); + BKE_main_namemap_clear(*main); /* (Library.temp_index -> Main), lookup table */ const uint lib_main_array_len = BLI_listbase_count(&main->libraries); @@ -3442,7 +3442,7 @@ static void after_liblink_merged_bmain_process(Main *bmain, BlendFileReadReport /* We only expect a merged Main here, not a split one. */ BLI_assert((bmain->prev == nullptr) && (bmain->next == nullptr)); - if (!BKE_main_namemap_validate_and_fix(bmain)) { + if (!BKE_main_namemap_validate_and_fix(*bmain)) { BKE_report( reports ? reports->reports : nullptr, RPT_ERROR, diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 18652ca1048..145d666b781 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -4022,7 +4022,7 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain) } } - BKE_main_namemap_validate_and_fix(bmain); + BKE_main_namemap_validate_and_fix(*bmain); } if (!MAIN_VERSION_FILE_ATLEAST(bmain, 304, 1)) { diff --git a/source/blender/blenloader/intern/versioning_common.cc b/source/blender/blenloader/intern/versioning_common.cc index b79bb055614..bfa700c3300 100644 --- a/source/blender/blenloader/intern/versioning_common.cc +++ b/source/blender/blenloader/intern/versioning_common.cc @@ -664,7 +664,7 @@ void do_versions_after_setup(Main *new_bmain, BKE_lib_override_library_main_proxy_convert(new_bmain, reports); /* Currently liboverride code can generate invalid namemap. This is a known issue, requires * #107847 to be properly fixed. */ - BKE_main_namemap_validate_and_fix(new_bmain); + BKE_main_namemap_validate_and_fix(*new_bmain); } if (!blendfile_or_libraries_versions_atleast(new_bmain, 302, 3)) { diff --git a/source/blender/blenloader/intern/writefile.cc b/source/blender/blenloader/intern/writefile.cc index e5b7edc8f44..ce9048c3eea 100644 --- a/source/blender/blenloader/intern/writefile.cc +++ b/source/blender/blenloader/intern/writefile.cc @@ -1585,7 +1585,7 @@ static void write_file_main_validate_pre(Main *bmain, ReportList *reports) } BLO_main_validate_shapekeys(bmain, reports); - if (!BKE_main_namemap_validate_and_fix(bmain)) { + if (!BKE_main_namemap_validate_and_fix(*bmain)) { BKE_report(reports, RPT_ERROR, "Critical data corruption: Conflicts and/or otherwise invalid data-blocks names " diff --git a/source/blender/windowmanager/intern/wm_files.cc b/source/blender/windowmanager/intern/wm_files.cc index 83e750a0a01..e023478e3de 100644 --- a/source/blender/windowmanager/intern/wm_files.cc +++ b/source/blender/windowmanager/intern/wm_files.cc @@ -1163,7 +1163,7 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports) wm_read_callback_post_wrapper(C, filepath, success); - BLI_assert(BKE_main_namemap_validate(CTX_data_main(C))); + BLI_assert(BKE_main_namemap_validate(*CTX_data_main(C))); return success; }