Refactor: Core: ID's 'namemap' used to generate unique ID names.
Note: This commit is essentially non-behavioral change, expect in some
fairly rare edge cases.
This commit does a few things:
* Move the whole BKE_main_namemap code to modern C++.
* Split API calls to work with the global namemap, or the local ones.
* Simplify and make the code easier to follow and understand.
* Reduce 'default' memory usage by using growing BitVector for numeric
suffix management, instead of a fixed 1K items.
* Fix inconsistent handling of 'same base name and numeric suffix,
different name' issues (e.g. 'Foo.1' and 'Foo.001'), see
`re_create_equivalent_numeric_suffixes` new unittest.
* Fix completely broken handling of `global` namemaps. This was
(probably!) OK so far because of their currently very limited
use-cases.
It also adds a few minor improvements to existing behavior (essentially
in exotic rare edge cases):
* Names that get too long are now only shortened by one char at a time,
trying to modify the requested base name as little as possible.
* Names that are short, but for which all the manageable numeric suffixes
are already in use, are extended with an (increasing) number, instead
of being shortened.
This work also allowed to detect a few (apparently harmless?) bugs in
existing code, which have been fixed already in 4.4 and main, or in this
commit as well when they depend on changes in namemap code itself.
About performances: This commit introduces a minor slow-down. Some tests
heavily relying on this code (like `bl_id_management` and `blendkernel`
e.g.) get slightly slower (resp. about 1% and 5%). This seems to come
mostly from the added complexity to handle correctly multiple different
names with the same base and numeric suffix value ('Foo.1' and
'Foo.001', but also in the global namemap context where IDs from
different libraries can have the same name).
Pull Request: https://projects.blender.org/blender/blender/pulls/135199
This commit is contained in:
committed by
Bastien Montagne
parent
5372346978
commit
16e552298c
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1678,7 +1678,7 @@ void BKE_blendfile_override(BlendfileLinkAppendContext *lapp_context,
|
||||
}
|
||||
}
|
||||
|
||||
BKE_main_namemap_clear(bmain);
|
||||
BKE_main_namemap_clear(*bmain);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<ID *>(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<ID *>(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<ID *>(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<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
|
||||
ID *id_b = static_cast<ID *>(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<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
|
||||
ID *id_b = static_cast<ID *>(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<ID *>(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<ID *>(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<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.123"));
|
||||
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.001"));
|
||||
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.01"));
|
||||
ID *id_d = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.1"));
|
||||
ID *id_e = static_cast<ID *>(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<ID *>(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<ID *>(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<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
|
||||
EXPECT_STREQ(BKE_id_name(*id_b), "Foo.001");
|
||||
id_c = static_cast<ID *>(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<ID *>(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<ID *>(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<ID *>(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<ID *>(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<ID *>(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<ID *>(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<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
|
||||
EXPECT_STREQ(id_fo178->name + 2, "Fo.178");
|
||||
ID *id_fo179 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.2000"));
|
||||
EXPECT_STREQ(id_fo179->name + 2, "Fo.179");
|
||||
ID *id_fo180 = static_cast<ID *>(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<ID *>(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<ID *>(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<ID *>(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<ID *>(BKE_id_new(ctx.bmain, ID_OB, "ALongerName"));
|
||||
}
|
||||
/* Max possible numeric suffix. */
|
||||
id_max = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "ALongerName.999999999"));
|
||||
EXPECT_STREQ(BKE_id_name(*id_max), "ALongerName.999999999");
|
||||
|
||||
ID *id_alongernam = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "ALongerName"));
|
||||
EXPECT_STREQ(BKE_id_name(*id_alongernam), "ALongerNam");
|
||||
ID *id_alongernam001 = static_cast<ID *>(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<ID *>(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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 <fmt/format.h>
|
||||
|
||||
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<int> 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<Map<int, int>> 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<int64_t> 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<UniqueName_Key> 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<std::string, int> full_names;
|
||||
/* For each base name (i.e. without numeric suffix), track the
|
||||
* numeric suffixes that are in use. */
|
||||
Map<UniqueName_Key, UniqueName_Value> base_name_to_num_suffix;
|
||||
Map<std::string, std::unique_ptr<UniqueName_Value>> base_name_to_num_suffix;
|
||||
};
|
||||
|
||||
struct UniqueName_Map {
|
||||
UniqueName_TypeMap type_maps[INDEX_ID_MAX];
|
||||
std::array<UniqueName_TypeMap, INDEX_ID_MAX> 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<UniqueName_Value> &val = type_map.base_name_to_num_suffix.lookup_or_add_as(
|
||||
name_base, std::make_unique<UniqueName_Value>(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<UniqueName_Value> &val = type_map.base_name_to_num_suffix.lookup_or_add_as(
|
||||
name_base, std::make_unique<UniqueName_Value>(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<UniqueName_Value> &val = type_map.base_name_to_num_suffix.lookup_or_add_as(
|
||||
name_base, std::make_unique<UniqueName_Value>(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<UniqueName_Value> *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<UniqueName_Map>(__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<Library *>(bmain_iter->libraries.first);
|
||||
lib_iter != nullptr;
|
||||
lib_iter = static_cast<Library *>(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<UniqueName_Map>(__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<UniqueName_Map>(__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<UniqueName_Map>(__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<UniqueName_Value> *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<UniqueName_Value> *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<UniqueName_Value> *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<UniqueName_Value> &val = type_map.base_name_to_num_suffix.lookup_or_add_as(
|
||||
name_base, std::make_unique<UniqueName_Value>(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<Uniqueness_Key> id_names_libs;
|
||||
Set<ID *> 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<short *>(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 : "<None>");
|
||||
}
|
||||
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 : "<None>");
|
||||
}
|
||||
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<char *>(&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 : "<None>");
|
||||
}
|
||||
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 : "<None>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lib = static_cast<Library *>((lib == nullptr) ? bmain->libraries.first : lib->id.next);
|
||||
|
||||
lib = static_cast<Library *>((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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user