This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenkernel/intern/main_namemap.cc
Bastien Montagne 9ac81ed6ab Fix corrupted blend files after issues from new name_map code.
Add a version of #BKE_main_namemap_validate that also fixes the issues,
and call it in a do_version to fix recent .blend files saved after the
regression introduced in rB7f8d05131a77.

This is mandatory to fix some production files here at the studio, among
other things.
2022-07-27 15:33:29 +02:00

501 lines
16 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_main_namemap.h"
#include "BLI_assert.h"
#include "BLI_bitmap.h"
#include "BLI_ghash.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_base.hh"
#include "BLI_set.hh"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.h"
#include "DNA_ID.h"
#include "MEM_guardedalloc.h"
#include "CLG_log.h"
static CLG_LogRef LOG = {"bke.main_namemap"};
//#define DEBUG_PRINT_MEMORY_USAGE
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 = BLI_snprintf_rlen(number_str, ARRAY_SIZE(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);
}
};
/* Tracking of used numeric suffixes. For each base name:
*
* - Exactly track which of the lowest 1024 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
* suffix value seen so far. Upon collision, assign number that is
* one larger.
*/
struct UniqueName_Value {
static constexpr unsigned max_exact_tracking = 1024;
BLI_BITMAP_DECLARE(mask, max_exact_tracking);
int max_value = 0;
void mark_used(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;
}
}
return false;
}
int use_smallest_unused()
{
/* 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_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);
}
mask[0] |= prev_first & 1; /* Restore previous value of #0 bit. */
return result;
}
};
/* Tracking of names for a single ID type. */
struct UniqueName_TypeMap {
/* Set of full names that are in use. */
Set<UniqueName_Key> 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;
};
struct UniqueName_Map {
UniqueName_TypeMap type_maps[INDEX_ID_MAX];
UniqueName_TypeMap *find_by_type(short id_type)
{
int index = BKE_idtype_idcode_to_index(id_type);
return index >= 0 ? &type_maps[index] : nullptr;
}
};
struct UniqueName_Map *BKE_main_namemap_create()
{
struct UniqueName_Map *map = MEM_new<UniqueName_Map>(__func__);
return map;
}
void BKE_main_namemap_destroy(struct UniqueName_Map **r_name_map)
{
#ifdef DEBUG_PRINT_MEMORY_USAGE
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);
#endif
MEM_delete<UniqueName_Map>(*r_name_map);
*r_name_map = nullptr;
}
static void main_namemap_populate(UniqueName_Map *name_map, struct Main *bmain, ID *ignore_id)
{
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();
}
Library *library = ignore_id->lib;
ID *id;
FOREACH_MAIN_ID_BEGIN (bmain, id) {
if ((id == ignore_id) || (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;
BLI_strncpy(key.name, id->name + 2, MAX_NAME);
type_map->full_names.add(key);
/* Get the name and number parts ("name.number"). */
int number = MIN_NUMBER;
BLI_split_name_num(key.name, &number, id->name + 2, '.');
/* 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.
* 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, bool ensure_created)
{
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);
}
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);
}
return bmain->name_map;
}
bool BKE_main_namemap_get_name(struct Main *bmain, struct ID *id, char *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);
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);
bool is_name_changed = false;
UniqueName_Key key;
while (true) {
/* Check if the full original name has a duplicate. */
BLI_strncpy(key.name, name, MAX_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_split_name_num(key.name, &number, name, '.');
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) {
BLI_strncpy(key.name, name, MAX_NAME);
type_map->full_names.add(key);
}
return is_name_changed;
}
/* 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;
}
}
}
/* 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. */
BLI_strncpy(key.name, name, MAX_NAME);
type_map->full_names.add(key);
break;
}
/* Name had to be truncated, or number too large: mark
* the output name as definitely changed, and proceed with the
* truncated name again. */
is_name_changed = true;
}
return is_name_changed;
}
void BKE_main_namemap_remove_name(struct Main *bmain, struct ID *id, const char *name)
{
#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') {
return;
}
struct UniqueName_Map *name_map = get_namemap_for(bmain, id, false);
if (name_map == nullptr) {
return;
}
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. */
BLI_strncpy(key.name, name, MAX_NAME);
type_map->full_names.remove(key);
int number = MIN_NUMBER;
BLI_split_name_num(key.name, &number, name, '.');
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);
}
struct Uniqueness_Key {
char name[MAX_ID_NAME];
Library *lib;
uint64_t hash() const
{
return BLI_ghashutil_combine_hash(BLI_ghashutil_strhash_n(name, MAX_ID_NAME),
BLI_ghashutil_ptrhash(lib));
}
bool operator==(const Uniqueness_Key &o) const
{
return lib == o.lib && !BLI_ghashutil_strcmp(name, o.name);
}
};
static bool main_namemap_validate_and_fix(Main *bmain, const bool do_fix)
{
Set<Uniqueness_Key> id_names_libs;
bool is_valid = true;
ListBase *lb_iter;
FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb_iter) {
LISTBASE_FOREACH_MUTABLE (ID *, id_iter, lb_iter) {
Uniqueness_Key key;
BLI_strncpy(key.name, id_iter->name, MAX_ID_NAME);
key.lib = id_iter->lib;
if (!id_names_libs.add(key)) {
is_valid = false;
CLOG_ERROR(&LOG,
"ID name '%s' (from library '%s') is found more than once",
id_iter->name,
id_iter->lib != nullptr ? id_iter->lib->filepath : "<None>");
if (do_fix) {
/* NOTE: this may imply moving this ID in its listbase, however re-checking it later is
* not really an issue. */
BKE_id_new_name_validate(
bmain, which_libbase(bmain, GS(id_iter->name)), id_iter, nullptr, true);
BLI_strncpy(key.name, id_iter->name, MAX_ID_NAME);
if (!id_names_libs.add(key)) {
CLOG_ERROR(&LOG,
"\tID has been renamed to '%s', but it still seems to be already in use",
id_iter->name);
}
else {
CLOG_WARN(&LOG, "\tID has been renamed to '%s'", id_iter->name);
}
}
}
UniqueName_Map *name_map = get_namemap_for(bmain, 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_Key key_namemap;
/* Remove full name from the set. */
BLI_strncpy(key_namemap.name, id_iter->name + 2, MAX_NAME);
if (!type_map->full_names.contains(key_namemap)) {
is_valid = false;
CLOG_ERROR(&LOG,
"ID name '%s' (from library '%s') exists in current Main, but is not listed in "
"the namemap",
id_iter->name,
id_iter->lib != nullptr ? id_iter->lib->filepath : "<None>");
}
}
}
FOREACH_MAIN_LISTBASE_END;
Library *lib = nullptr;
UniqueName_Map *name_map = bmain->name_map;
do {
if (name_map != nullptr) {
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;
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>");
}
}
}
}
}
lib = static_cast<Library *>((lib == nullptr) ? bmain->libraries.first : lib->id.next);
name_map = (lib != nullptr) ? lib->runtime.name_map : nullptr;
} while (lib != nullptr);
if (is_valid || !do_fix) {
return is_valid;
}
/* Clear all existing namemaps. */
lib = nullptr;
UniqueName_Map **name_map_p = &bmain->name_map;
do {
BLI_assert(name_map_p != nullptr);
if (*name_map_p != nullptr) {
BKE_main_namemap_destroy(name_map_p);
}
lib = static_cast<Library *>((lib == nullptr) ? bmain->libraries.first : lib->id.next);
name_map_p = (lib != nullptr) ? &lib->runtime.name_map : nullptr;
} while (lib != nullptr);
return is_valid;
}
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)
{
return main_namemap_validate_and_fix(bmain, false);
}