diff --git a/include/godot_cpp/templates/cowdata.hpp b/include/godot_cpp/templates/cowdata.hpp index 1753687e..18320d80 100644 --- a/include/godot_cpp/templates/cowdata.hpp +++ b/include/godot_cpp/templates/cowdata.hpp @@ -32,13 +32,13 @@ #define GODOT_COWDATA_HPP #include -#include #include #include #include #include #include +#include namespace godot { @@ -48,6 +48,9 @@ class Vector; template class VMap; +template +class CharStringT; + // Silence a false positive warning (see GH-52119). #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push @@ -62,6 +65,9 @@ class CowData { template friend class VMap; + template + friend class CharStringT; + private: mutable T *_ptr = nullptr; diff --git a/include/godot_cpp/variant/char_string.hpp b/include/godot_cpp/variant/char_string.hpp index fa58bbdf..f8f51696 100644 --- a/include/godot_cpp/variant/char_string.hpp +++ b/include/godot_cpp/variant/char_string.hpp @@ -31,82 +31,99 @@ #ifndef GODOT_CHAR_STRING_HPP #define GODOT_CHAR_STRING_HPP +#include + #include #include namespace godot { -class CharString { - friend class String; +template +class CharStringT; - const char *_data = nullptr; - int _length = 0; +template +class CharProxy { + template + friend class CharStringT; - CharString(const char *str, int length); + const int _index; + CowData &_cowdata; + static inline const T _null = 0; + + _FORCE_INLINE_ CharProxy(const int &p_index, CowData &p_cowdata) : + _index(p_index), + _cowdata(p_cowdata) {} public: - int length() const; - const char *get_data() const; + _FORCE_INLINE_ CharProxy(const CharProxy &p_other) : + _index(p_other._index), + _cowdata(p_other._cowdata) {} - CharString(CharString &&p_str); - void operator=(CharString &&p_str); - CharString() {} - ~CharString(); + _FORCE_INLINE_ operator T() const { + if (unlikely(_index == _cowdata.size())) { + return _null; + } + + return _cowdata.get(_index); + } + + _FORCE_INLINE_ const T *operator&() const { + return _cowdata.ptr() + _index; + } + + _FORCE_INLINE_ void operator=(const T &p_other) const { + _cowdata.set(_index, p_other); + } + + _FORCE_INLINE_ void operator=(const CharProxy &p_other) const { + _cowdata.set(_index, p_other.operator T()); + } }; -class Char16String { +template +class CharStringT { friend class String; - const char16_t *_data = nullptr; - int _length = 0; - - Char16String(const char16_t *str, int length); + CowData _cowdata; + static inline const T _null = 0; public: - int length() const; - const char16_t *get_data() const; + _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); } + _FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); } + _FORCE_INLINE_ int size() const { return _cowdata.size(); } + Error resize(int p_size) { return _cowdata.resize(p_size); } - Char16String(Char16String &&p_str); - void operator=(Char16String &&p_str); - Char16String() {} - ~Char16String(); + _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 { + if (unlikely(p_index == _cowdata.size())) { + return _null; + } + + return _cowdata.get(p_index); + } + _FORCE_INLINE_ CharProxy operator[](int p_index) { return CharProxy(p_index, _cowdata); } + + _FORCE_INLINE_ CharStringT() {} + _FORCE_INLINE_ CharStringT(const CharStringT &p_str) { _cowdata._ref(p_str._cowdata); } + _FORCE_INLINE_ void operator=(const CharStringT &p_str) { _cowdata._ref(p_str._cowdata); } + _FORCE_INLINE_ CharStringT(const T *p_cstr) { copy_from(p_cstr); } + + void operator=(const T *p_cstr); + bool operator<(const CharStringT &p_right) const; + CharStringT &operator+=(T p_char); + int length() const { return size() ? size() - 1 : 0; } + const T *get_data() const; + operator const T *() const { return get_data(); }; + +protected: + void copy_from(const T *p_cstr); }; -class Char32String { - friend class String; - - const char32_t *_data = nullptr; - int _length = 0; - - Char32String(const char32_t *str, int length); - -public: - int length() const; - const char32_t *get_data() const; - - Char32String(Char32String &&p_str); - void operator=(Char32String &&p_str); - Char32String() {} - ~Char32String(); -}; - -class CharWideString { - friend class String; - - const wchar_t *_data = nullptr; - int _length = 0; - - CharWideString(const wchar_t *str, int length); - -public: - int length() const; - const wchar_t *get_data() const; - - CharWideString(CharWideString &&p_str); - void operator=(CharWideString &&p_str); - CharWideString() {} - ~CharWideString(); -}; +typedef CharStringT CharString; +typedef CharStringT Char16String; +typedef CharStringT Char32String; +typedef CharStringT CharWideString; } // namespace godot diff --git a/src/variant/char_string.cpp b/src/variant/char_string.cpp index 0c8cd0fb..856037c4 100644 --- a/src/variant/char_string.cpp +++ b/src/variant/char_string.cpp @@ -38,117 +38,121 @@ #include #include +#include namespace godot { -int CharString::length() const { - return _length; -} +template +_FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) { + while (true) { + const char32_t l = *l_ptr; + const char32_t r = *r_ptr; -const char *CharString::get_data() const { - return _data; -} + if (l == 0 && r == 0) { + return false; + } else if (l == 0) { + return true; + } else if (r == 0) { + return false; + } else if (l < r) { + return true; + } else if (l > r) { + return false; + } -CharString::CharString(CharString &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -void CharString::operator=(CharString &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -CharString::CharString(const char *str, int length) : - _data(str), _length(length) {} - -CharString::~CharString() { - if (_data != nullptr) { - memdelete_arr(_data); + l_ptr++; + r_ptr++; } } -int Char16String::length() const { - return _length; +template +bool CharStringT::operator<(const CharStringT &p_right) const { + if (length() == 0) { + return p_right.length() != 0; + } + + return is_str_less(get_data(), p_right.get_data()); } -const char16_t *Char16String::get_data() const { - return _data; +template +CharStringT &CharStringT::operator+=(T p_char) { + const int lhs_len = length(); + resize(lhs_len + 2); + + T *dst = ptrw(); + dst[lhs_len] = p_char; + dst[lhs_len + 1] = 0; + + return *this; } -Char16String::Char16String(Char16String &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); +template +void CharStringT::operator=(const T *p_cstr) { + copy_from(p_cstr); } -void Char16String::operator=(Char16String &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -Char16String::Char16String(const char16_t *str, int length) : - _data(str), _length(length) {} - -Char16String::~Char16String() { - if (_data != nullptr) { - memdelete_arr(_data); +template <> +const char *CharStringT::get_data() const { + if (size()) { + return &operator[](0); + } else { + return ""; } } -int Char32String::length() const { - return _length; -} - -const char32_t *Char32String::get_data() const { - return _data; -} - -Char32String::Char32String(Char32String &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -void Char32String::operator=(Char32String &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -Char32String::Char32String(const char32_t *str, int length) : - _data(str), _length(length) {} - -Char32String::~Char32String() { - if (_data != nullptr) { - memdelete_arr(_data); +template <> +const char16_t *CharStringT::get_data() const { + if (size()) { + return &operator[](0); + } else { + return u""; } } -int CharWideString::length() const { - return _length; -} - -const wchar_t *CharWideString::get_data() const { - return _data; -} - -CharWideString::CharWideString(CharWideString &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -void CharWideString::operator=(CharWideString &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -CharWideString::CharWideString(const wchar_t *str, int length) : - _data(str), _length(length) {} - -CharWideString::~CharWideString() { - if (_data != nullptr) { - memdelete_arr(_data); +template <> +const char32_t *CharStringT::get_data() const { + if (size()) { + return &operator[](0); + } else { + return U""; } } +template <> +const wchar_t *CharStringT::get_data() const { + if (size()) { + return &operator[](0); + } else { + return L""; + } +} + +template +void CharStringT::copy_from(const T *p_cstr) { + if (!p_cstr) { + resize(0); + return; + } + + size_t len = std::char_traits::length(p_cstr); + + if (len == 0) { + resize(0); + return; + } + + Error err = resize(++len); // include terminating null char + + ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string."); + + memcpy(ptrw(), p_cstr, len); +} + +template class CharStringT; +template class CharStringT; +template class CharStringT; +template class CharStringT; + // Custom String functions that are not part of bound API. // It's easier to have them written in C++ directly than in a Python script that generates them. @@ -228,56 +232,61 @@ String rtoss(double p_val) { CharString String::utf8() const { int length = internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), nullptr, 0); int size = length + 1; - char *cstr = memnew_arr(char, size); - internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), cstr, length); + CharString str; + str.resize(size); + internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), str.ptrw(), length); - cstr[length] = '\0'; + str[length] = '\0'; - return CharString(cstr, length); + return str; } CharString String::ascii() const { int length = internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), nullptr, 0); int size = length + 1; - char *cstr = memnew_arr(char, size); - internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), cstr, length); + CharString str; + str.resize(size); + internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), str.ptrw(), length); - cstr[length] = '\0'; + str[length] = '\0'; - return CharString(cstr, length); + return str; } Char16String String::utf16() const { int length = internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), nullptr, 0); int size = length + 1; - char16_t *cstr = memnew_arr(char16_t, size); - internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), cstr, length); + Char16String str; + str.resize(size); + internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), str.ptrw(), length); - cstr[length] = '\0'; + str[length] = '\0'; - return Char16String(cstr, length); + return str; } Char32String String::utf32() const { int length = internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), nullptr, 0); int size = length + 1; - char32_t *cstr = memnew_arr(char32_t, size); - internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), cstr, length); + Char32String str; + str.resize(size); + internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), str.ptrw(), length); - cstr[length] = '\0'; + str[length] = '\0'; - return Char32String(cstr, length); + return str; } CharWideString String::wide_string() const { int length = internal::gdextension_interface_string_to_wide_chars(_native_ptr(), nullptr, 0); int size = length + 1; - wchar_t *cstr = memnew_arr(wchar_t, size); - internal::gdextension_interface_string_to_wide_chars(_native_ptr(), cstr, length); + CharWideString str; + str.resize(size); + internal::gdextension_interface_string_to_wide_chars(_native_ptr(), str.ptrw(), length); - cstr[length] = '\0'; + str[length] = '\0'; - return CharWideString(cstr, length); + return str; } String &String::operator=(const char *p_str) { diff --git a/test/project/main.gd b/test/project/main.gd index cdd8696b..cedd5124 100644 --- a/test/project/main.gd +++ b/test/project/main.gd @@ -82,6 +82,10 @@ func _ready(): # UtilityFunctions::str() assert_equal(example.test_str_utility(), "Hello, World! The answer is 42") + # Test converting string to char* and doing comparison. + assert_equal(example.test_string_is_fourty_two("blah"), false) + assert_equal(example.test_string_is_fourty_two("fourty two"), true) + # PackedArray iterators assert_equal(example.test_vector_ops(), 105) diff --git a/test/src/example.cpp b/test/src/example.cpp index 34ee355a..fb47dd8d 100644 --- a/test/src/example.cpp +++ b/test/src/example.cpp @@ -138,6 +138,7 @@ void Example::_bind_methods() { ClassDB::bind_method(D_METHOD("test_node_argument"), &Example::test_node_argument); ClassDB::bind_method(D_METHOD("test_string_ops"), &Example::test_string_ops); ClassDB::bind_method(D_METHOD("test_str_utility"), &Example::test_str_utility); + ClassDB::bind_method(D_METHOD("test_string_is_fourty_two"), &Example::test_string_is_fourty_two); ClassDB::bind_method(D_METHOD("test_vector_ops"), &Example::test_vector_ops); ClassDB::bind_method(D_METHOD("test_bitfield", "flags"), &Example::test_bitfield); @@ -299,6 +300,10 @@ String Example::test_str_utility() const { return UtilityFunctions::str("Hello, ", "World", "! The answer is ", 42); } +bool Example::test_string_is_fourty_two(const String &p_string) const { + return strcmp(p_string.utf8().ptr(), "fourty two") == 0; +} + int Example::test_vector_ops() const { PackedInt32Array arr; arr.push_back(10); diff --git a/test/src/example.h b/test/src/example.h index a3300305..a84efedc 100644 --- a/test/src/example.h +++ b/test/src/example.h @@ -117,6 +117,7 @@ public: Example *test_node_argument(Example *p_node) const; String test_string_ops() const; String test_str_utility() const; + bool test_string_is_fourty_two(const String &p_str) const; int test_vector_ops() const; BitField test_bitfield(BitField flags);