diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index e73b02c3a..af0688d94 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -4669,15 +4669,66 @@ String String::property_name_encode() const { } // Changes made to the set of invalid characters must also be reflected in the String documentation. -const String String::invalid_node_name_characters = ". : @ / \" %"; + +static const char32_t invalid_node_name_characters[] = { '.', ':', '@', '/', '\"', '%', 0 }; + +String String::get_invalid_node_name_characters() { + // Do not use this function for critical validation. + String r; + const char32_t *c = invalid_node_name_characters; + while (*c) { + if (c != invalid_node_name_characters) { + r += " "; + } + r += String::chr(*c); + c++; + } + return r; +} String String::validate_node_name() const { - Vector chars = String::invalid_node_name_characters.split(" "); - String name = this->replace(chars[0], ""); - for (int i = 1; i < chars.size(); i++) { - name = name.replace(chars[i], ""); + // This is a critical validation in node addition, so it must be optimized. + const char32_t *cn = ptr(); + if (cn == nullptr) { + return String(); } - return name; + + bool valid = true; + uint32_t idx = 0; + while (cn[idx]) { + const char32_t *c = invalid_node_name_characters; + while (*c) { + if (cn[idx] == *c) { + valid = false; + break; + } + c++; + } + if (!valid) { + break; + } + idx++; + } + + if (valid) { + return *this; + } + + String validated = *this; + char32_t *nn = validated.ptrw(); + while (nn[idx]) { + const char32_t *c = invalid_node_name_characters; + while (*c) { + if (nn[idx] == *c) { + nn[idx] = '_'; + break; + } + c++; + } + idx++; + } + + return validated; } static _FORCE_INLINE_ bool _is_valid_identifier_bit(int p_index, char32_t p_char) { @@ -5604,6 +5655,23 @@ double String::to_double(const CharType *p_str, const CharType **r_end) { return built_in_strtod(p_str, (CharType **)r_end); } +uint32_t String::num_characters(int64_t p_int) { + int r = 1; + if (p_int < 0) { + r += 1; + if (p_int == INT64_MIN) { + p_int = INT64_MAX; + } else { + p_int = -p_int; + } + } + while (p_int >= 10) { + p_int /= 10; + r++; + } + return r; +} + bool String::_base_is_subsequence_of(const String &p_string, bool case_insensitive) const { int len = length(); if (len == 0) { diff --git a/core/string/ustring.h b/core/string/ustring.h index 2424ddb35..7dca8f0ce 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -387,6 +387,8 @@ public: static double to_double(const wchar_t *p_str, const wchar_t **r_end = nullptr); static double to_double(const CharType *p_str, const CharType **r_end = nullptr); + static uint32_t num_characters(int64_t p_int); + String capitalize() const; String camelcase_to_underscore(bool lowercase = true) const; @@ -500,7 +502,7 @@ public: String property_name_encode() const; // node functions - static const String invalid_node_name_characters; + static String get_invalid_node_name_characters(); String validate_node_name() const; String validate_identifier() const; diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 88488b272..4c974f08b 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -856,7 +856,7 @@ void SceneTreeEditor::_renamed() { String raw_new_name = which->get_text(0); String new_name = raw_new_name.validate_node_name(); if (new_name != raw_new_name) { - error->set_text(TTR("Invalid node name, the following characters are not allowed:") + "\n" + String::invalid_node_name_characters); + error->set_text(TTR("Invalid node name, the following characters are not allowed:") + "\n" + String::get_invalid_node_name_characters()); error->popup_centered_minsize(); if (new_name.empty()) { diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 8a72057e3..249b1e235 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1346,8 +1346,31 @@ void Node::_validate_child_name(Node *p_child, bool p_force_human_readable) { if (!unique) { ERR_FAIL_COND(!node_hrcr_count.ref()); - String name = "@" + String(p_child->get_name()) + "@" + itos(node_hrcr_count.get()); - p_child->data.name = name; + + // Optimized version of the code below: + // String name = "@" + String(p_child->get_name()) + "@" + itos(node_hrcr_count.get()); + uint32_t c = node_hrcr_count.get(); + String cn = p_child->get_class_name().operator String(); + const char32_t *cn_ptr = cn.ptr(); + uint32_t cn_length = cn.length(); + uint32_t c_chars = String::num_characters(c); + uint32_t len = 2 + cn_length + c_chars; + char32_t *str = (char32_t *)alloca(sizeof(char32_t) * (len + 1)); + uint32_t idx = 0; + str[idx++] = '@'; + for (uint32_t i = 0; i < cn_length; i++) { + str[idx++] = cn_ptr[i]; + } + str[idx++] = '@'; + idx += c_chars; + ERR_FAIL_COND(idx != len); + str[idx] = 0; + while (c) { + str[--idx] = '0' + (c % 10); + c /= 10; + } + + p_child->data.name = String(str); } } }