diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index cd5adffcefe5..06b22a34953c 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -39,6 +39,7 @@ #include "core/io/marshalls.h" #include "core/os/os.h" #include "core/os/time.h" +#include "core/string/char_buffer.h" Ref FileAccess::create(AccessType p_access) { ERR_FAIL_INDEX_V(p_access, ACCESS_MAX, nullptr); @@ -415,54 +416,8 @@ String FileAccess::get_token() const { return String::utf8(token.get_data()); } -class CharBuffer { - Vector vector; - char stack_buffer[256]; - - char *buffer = nullptr; - int64_t capacity = 0; - int64_t written = 0; - - bool grow() { - if (vector.resize(next_power_of_2((uint64_t)1 + (uint64_t)written)) != OK) { - return false; - } - - if (buffer == stack_buffer) { // first chunk? - - for (int64_t i = 0; i < written; i++) { - vector.write[i] = stack_buffer[i]; - } - } - - buffer = vector.ptrw(); - capacity = vector.size(); - ERR_FAIL_COND_V(written >= capacity, false); - - return true; - } - -public: - _FORCE_INLINE_ CharBuffer() : - buffer(stack_buffer), - capacity(std::size(stack_buffer)) { - } - - _FORCE_INLINE_ void push_back(char c) { - if (written >= capacity) { - ERR_FAIL_COND(!grow()); - } - - buffer[written++] = c; - } - - _FORCE_INLINE_ const char *get_data() const { - return buffer; - } -}; - String FileAccess::get_line() const { - CharBuffer line; + CharBuffer line; uint8_t c = get_8(); @@ -480,16 +435,15 @@ String FileAccess::get_line() const { get_8(); } } - line.push_back(0); - return String::utf8(line.get_data()); + return String::utf8(line.get_terminated_buffer()); } else { - line.push_back(char(c)); + line += char(c); } c = get_8(); } - line.push_back(0); - return String::utf8(line.get_data()); + + return String::utf8(line.get_terminated_buffer()); } Vector FileAccess::get_csv_line(const String &p_delim) const { diff --git a/core/io/json.cpp b/core/io/json.cpp index 872d7e67452a..8fec249bcb5d 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -32,6 +32,7 @@ #include "core/config/engine.h" #include "core/object/script_language.h" +#include "core/string/char_buffer.h" #include "core/variant/container_type_validate.h" const char *JSON::tk_name[TK_MAX] = { @@ -224,7 +225,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to } case '"': { index++; - String str; + CharBuffer str; while (true) { if (p_str[index] == 0) { r_err_str = "Unterminated string"; @@ -359,7 +360,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to } r_token.type = TK_STRING; - r_token.value = str; + r_token.value = str.finalize(); return OK; } break; @@ -379,7 +380,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to return OK; } else if (is_ascii_alphabet_char(p_str[index])) { - String id; + CharBuffer id; while (is_ascii_alphabet_char(p_str[index])) { id += p_str[index]; @@ -387,7 +388,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to } r_token.type = TK_IDENTIFIER; - r_token.value = id; + r_token.value = id.finalize(); return OK; } else { r_err_str = "Unexpected character"; diff --git a/core/string/char_buffer.h b/core/string/char_buffer.h new file mode 100644 index 000000000000..2b7956d3d2e4 --- /dev/null +++ b/core/string/char_buffer.h @@ -0,0 +1,94 @@ +/**************************************************************************/ +/* char_buffer.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/string/ustring.h" + +template +class CharBuffer { + T _fixed_buffer[FIXED_BUFFER_SIZE]; + T *_active_buffer = _fixed_buffer; + + using StringType = std::conditional_t, String, CharStringT>; + StringType _string; + + uint32_t _length = 0; + uint32_t _capacity = FIXED_BUFFER_SIZE; + + // Size should include room for null terminator. + void _reserve(uint32_t p_size) { + if (p_size <= _capacity) { + return; + } + + bool copy = _active_buffer == _fixed_buffer && _length > 0; + _capacity = next_power_of_2(p_size); + _string.reserve_exact(_capacity); + _active_buffer = const_cast(_string.ptr()); + if (copy) { + memcpy(_active_buffer, _fixed_buffer, _length * sizeof(T)); + } + } + +public: + _FORCE_INLINE_ void operator+=(T p_char) { append(p_char); } + CharBuffer &append(T p_char) { + _reserve(_length + 2); + _active_buffer[_length++] = p_char; + return *this; + } + + // Returns pointer to null-terminated string without resetting state. + const T *get_terminated_buffer() { + _active_buffer[_length] = '\0'; + return _active_buffer; + } + + // Returns string and resets state. + StringType finalize() { + StringType result; + + if (_active_buffer == _fixed_buffer) { + result = get_terminated_buffer(); + } else { + _active_buffer[_length] = '\0'; + result = std::move(_string); + result.resize_uninitialized(_length + 1); + + _string = StringType(); + _active_buffer = _fixed_buffer; + _capacity = FIXED_BUFFER_SIZE; + } + + _length = 0; + return result; + } +}; diff --git a/core/string/string_buffer.h b/core/string/string_buffer.h deleted file mode 100644 index e707a3ad48bb..000000000000 --- a/core/string/string_buffer.h +++ /dev/null @@ -1,163 +0,0 @@ -/**************************************************************************/ -/* string_buffer.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#pragma once - -#include "core/string/print_string.h" -#include "core/string/ustring.h" - -template -class StringBuffer { - char32_t short_buffer[SHORT_BUFFER_SIZE]; - String buffer; - int string_length = 0; - - _FORCE_INLINE_ char32_t *current_buffer_ptr() { - return static_cast(buffer).is_empty() ? short_buffer : buffer.ptrw(); - } - -public: - StringBuffer &append(char32_t p_char); - StringBuffer &append(const String &p_string); - StringBuffer &append(const char *p_str); - StringBuffer &append(const char32_t *p_str, int p_clip_to_len = -1); - - _FORCE_INLINE_ void operator+=(char32_t p_char) { - append(p_char); - } - - _FORCE_INLINE_ void operator+=(const String &p_string) { - append(p_string); - } - - _FORCE_INLINE_ void operator+=(const char *p_str) { - append(p_str); - } - - _FORCE_INLINE_ void operator+=(const char32_t *p_str) { - append(p_str); - } - - StringBuffer &reserve(int p_size); - - int length() const; - - String as_string(); - - double as_double(); - int64_t as_int(); - - _FORCE_INLINE_ operator String() { - return as_string(); - } -}; - -template -StringBuffer &StringBuffer::append(char32_t p_char) { - reserve(string_length + 2); - current_buffer_ptr()[string_length++] = p_char; - return *this; -} - -template -StringBuffer &StringBuffer::append(const String &p_string) { - return append(p_string.get_data()); -} - -template -StringBuffer &StringBuffer::append(const char *p_str) { - int len = strlen(p_str); - reserve(string_length + len + 1); - - char32_t *buf = current_buffer_ptr(); - for (const char *c_ptr = p_str; *c_ptr; ++c_ptr) { - buf[string_length++] = *c_ptr; - } - return *this; -} - -template -StringBuffer &StringBuffer::append(const char32_t *p_str, int p_clip_to_len) { - int len = 0; - while ((p_clip_to_len < 0 || len < p_clip_to_len) && p_str[len]) { - ++len; - } - reserve(string_length + len + 1); - memcpy(&(current_buffer_ptr()[string_length]), p_str, len * sizeof(char32_t)); - string_length += len; - - return *this; -} - -template -StringBuffer &StringBuffer::reserve(int p_size) { - if (p_size <= SHORT_BUFFER_SIZE || p_size <= buffer.size()) { - if (p_size < length()) { - WARN_VERBOSE("reserve() called with a capacity smaller than the current size. This is likely a mistake."); - } - return *this; - } - - bool need_copy = string_length > 0 && buffer.is_empty(); - buffer.resize_uninitialized(next_power_of_2((uint32_t)p_size)); - if (need_copy) { - memcpy(buffer.ptrw(), short_buffer, string_length * sizeof(char32_t)); - } - - return *this; -} - -template -int StringBuffer::length() const { - return string_length; -} - -template -String StringBuffer::as_string() { - current_buffer_ptr()[string_length] = '\0'; - if (buffer.is_empty()) { - return String(short_buffer); - } else { - buffer.resize_uninitialized(string_length + 1); - return buffer; - } -} - -template -double StringBuffer::as_double() { - current_buffer_ptr()[string_length] = '\0'; - return String::to_float(current_buffer_ptr()); -} - -template -int64_t StringBuffer::as_int() { - current_buffer_ptr()[string_length] = '\0'; - return String::to_int(current_buffer_ptr()); -} diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 11148f30ab18..7df299ca3288 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -36,6 +36,7 @@ #include "core/object/object.h" #include "core/os/memory.h" #include "core/os/os.h" +#include "core/string/char_buffer.h" #include "core/string/print_string.h" #include "core/string/string_name.h" #include "core/string/translation_server.h" @@ -4543,25 +4544,24 @@ bool String::is_valid_string() const { String String::uri_encode() const { const CharString temp = utf8(); - String res; + CharBuffer res; for (int i = 0; i < temp.length(); ++i) { uint8_t ord = uint8_t(temp[i]); if (ord == '.' || ord == '-' || ord == '~' || is_ascii_identifier_char(ord)) { res += ord; } else { - char p[4] = { '%', 0, 0, 0 }; - p[1] = hex_char_table_upper[ord >> 4]; - p[2] = hex_char_table_upper[ord & 0xF]; - res += p; + res += '%'; + res += hex_char_table_upper[ord >> 4]; + res += hex_char_table_upper[ord & 0xF]; } } - return res; + return res.finalize(); } String String::uri_decode() const { CharString src = utf8(); - CharString res; + CharBuffer res; for (int i = 0; i < src.length(); ++i) { if (src[i] == '%' && i + 2 < src.length()) { char ord1 = src[i + 1]; @@ -4581,12 +4581,12 @@ String String::uri_decode() const { res += src[i]; } } - return String::utf8(res); + return String::utf8(res.get_terminated_buffer()); } String String::uri_file_decode() const { CharString src = utf8(); - CharString res; + CharBuffer res; for (int i = 0; i < src.length(); ++i) { if (src[i] == '%' && i + 2 < src.length()) { char ord1 = src[i + 1]; @@ -4604,7 +4604,7 @@ String String::uri_file_decode() const { res += src[i]; } } - return String::utf8(res); + return String::utf8(res.get_terminated_buffer()); } String String::c_unescape() const { diff --git a/core/string/ustring.h b/core/string/ustring.h index 4f28d2db2ea4..902cee91a4bb 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -190,6 +190,16 @@ class [[nodiscard]] CharStringT { /// New characters are not initialized, and should be set by the caller. _FORCE_INLINE_ Error resize_uninitialized(int64_t p_size) { return _cowdata.template resize(p_size); } + Error reserve(int64_t p_size) { + ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER); + return _cowdata.reserve(p_size); + } + + Error reserve_exact(int64_t p_size) { + ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER); + return _cowdata.reserve_exact(p_size); + } + _FORCE_INLINE_ T get(int p_index) const { return _cowdata.get(p_index); } _FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); } _FORCE_INLINE_ const T &operator[](int p_index) const { @@ -322,6 +332,11 @@ class [[nodiscard]] String { return _cowdata.reserve(p_size); } + Error reserve_exact(int64_t p_size) { + ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER); + return _cowdata.reserve_exact(p_size); + } + _FORCE_INLINE_ const char32_t &operator[](int p_index) const { if (unlikely(p_index == _cowdata.size())) { return _null; diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index ccb73872fe87..622b26096089 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -34,7 +34,7 @@ #include "core/io/resource_loader.h" #include "core/io/resource_uid.h" #include "core/object/script_language.h" -#include "core/string/string_buffer.h" +#include "core/string/char_buffer.h" char32_t VariantParser::Stream::get_char() { // is within buffer? @@ -239,7 +239,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri return OK; } case '#': { - StringBuffer<> color_str; + CharBuffer color_str; color_str += '#'; while (true) { char32_t ch = p_stream->get_char(); @@ -255,7 +255,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri } } - r_token.value = Color::html(color_str.as_string()); + r_token.value = Color::html(color_str.finalize()); r_token.type = TK_COLOR; return OK; } @@ -416,7 +416,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri if (cchar <= 32) { break; } - StringBuffer<> token_text; + CharBuffer token_text; if (cchar == '-') { token_text += '-'; cchar = p_stream->get_char(); @@ -485,9 +485,9 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri r_token.type = TK_NUMBER; if (is_float) { - r_token.value = token_text.as_double(); + r_token.value = String::to_float(token_text.get_terminated_buffer()); } else { - r_token.value = token_text.as_int(); + r_token.value = String::to_int(token_text.get_terminated_buffer()); } return OK; } else if (is_ascii_alphabet_char(cchar) || is_underscore(cchar)) { @@ -502,7 +502,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri p_stream->saved = cchar; r_token.type = TK_IDENTIFIER; - r_token.value = token_text.as_string(); + r_token.value = token_text.finalize(); return OK; } else { r_err_str = "Unexpected character";