Merge pull request #1150 from dsnopek/charstring-full

Attempt to fully implement CharString
pull/1181/head
David Snopek 2023-07-22 09:04:27 -05:00 committed by GitHub
commit 3162be28e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 208 additions and 166 deletions

View File

@ -32,13 +32,13 @@
#define GODOT_COWDATA_HPP #define GODOT_COWDATA_HPP
#include <godot_cpp/classes/global_constants.hpp> #include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/error_macros.hpp> #include <godot_cpp/core/error_macros.hpp>
#include <godot_cpp/core/math.hpp> #include <godot_cpp/core/math.hpp>
#include <godot_cpp/core/memory.hpp> #include <godot_cpp/core/memory.hpp>
#include <godot_cpp/templates/safe_refcount.hpp> #include <godot_cpp/templates/safe_refcount.hpp>
#include <cstring> #include <cstring>
#include <new>
namespace godot { namespace godot {
@ -48,6 +48,9 @@ class Vector;
template <class T, class V> template <class T, class V>
class VMap; class VMap;
template <class T>
class CharStringT;
// Silence a false positive warning (see GH-52119). // Silence a false positive warning (see GH-52119).
#if defined(__GNUC__) && !defined(__clang__) #if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push #pragma GCC diagnostic push
@ -62,6 +65,9 @@ class CowData {
template <class TV, class VV> template <class TV, class VV>
friend class VMap; friend class VMap;
template <class TS>
friend class CharStringT;
private: private:
mutable T *_ptr = nullptr; mutable T *_ptr = nullptr;

View File

@ -31,82 +31,99 @@
#ifndef GODOT_CHAR_STRING_HPP #ifndef GODOT_CHAR_STRING_HPP
#define GODOT_CHAR_STRING_HPP #define GODOT_CHAR_STRING_HPP
#include <godot_cpp/templates/cowdata.hpp>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
namespace godot { namespace godot {
class CharString { template <class T>
friend class String; class CharStringT;
const char *_data = nullptr; template <class T>
int _length = 0; class CharProxy {
template <class TS>
friend class CharStringT;
CharString(const char *str, int length); const int _index;
CowData<T> &_cowdata;
static inline const T _null = 0;
_FORCE_INLINE_ CharProxy(const int &p_index, CowData<T> &p_cowdata) :
_index(p_index),
_cowdata(p_cowdata) {}
public: public:
int length() const; _FORCE_INLINE_ CharProxy(const CharProxy<T> &p_other) :
const char *get_data() const; _index(p_other._index),
_cowdata(p_other._cowdata) {}
CharString(CharString &&p_str); _FORCE_INLINE_ operator T() const {
void operator=(CharString &&p_str); if (unlikely(_index == _cowdata.size())) {
CharString() {} return _null;
~CharString(); }
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<T> &p_other) const {
_cowdata.set(_index, p_other.operator T());
}
}; };
class Char16String { template <class T>
class CharStringT {
friend class String; friend class String;
const char16_t *_data = nullptr; CowData<T> _cowdata;
int _length = 0; static inline const T _null = 0;
Char16String(const char16_t *str, int length);
public: public:
int length() const; _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
const char16_t *get_data() const; _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); _FORCE_INLINE_ T get(int p_index) const { return _cowdata.get(p_index); }
void operator=(Char16String &&p_str); _FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); }
Char16String() {} _FORCE_INLINE_ const T &operator[](int p_index) const {
~Char16String(); if (unlikely(p_index == _cowdata.size())) {
return _null;
}
return _cowdata.get(p_index);
}
_FORCE_INLINE_ CharProxy<T> operator[](int p_index) { return CharProxy<T>(p_index, _cowdata); }
_FORCE_INLINE_ CharStringT() {}
_FORCE_INLINE_ CharStringT(const CharStringT<T> &p_str) { _cowdata._ref(p_str._cowdata); }
_FORCE_INLINE_ void operator=(const CharStringT<T> &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<T> &p_right) const;
CharStringT<T> &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 { typedef CharStringT<char> CharString;
friend class String; typedef CharStringT<char16_t> Char16String;
typedef CharStringT<char32_t> Char32String;
const char32_t *_data = nullptr; typedef CharStringT<wchar_t> CharWideString;
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();
};
} // namespace godot } // namespace godot

View File

@ -38,117 +38,121 @@
#include <godot_cpp/godot.hpp> #include <godot_cpp/godot.hpp>
#include <cmath> #include <cmath>
#include <string>
namespace godot { namespace godot {
int CharString::length() const { template <typename L, typename R>
return _length; _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 { if (l == 0 && r == 0) {
return _data; 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) { l_ptr++;
SWAP(_length, p_str._length); r_ptr++;
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);
} }
} }
int Char16String::length() const { template <class T>
return _length; bool CharStringT<T>::operator<(const CharStringT<T> &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 { template <class T>
return _data; CharStringT<T> &CharStringT<T>::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) { template <class T>
SWAP(_length, p_str._length); void CharStringT<T>::operator=(const T *p_cstr) {
SWAP(_data, p_str._data); copy_from(p_cstr);
} }
void Char16String::operator=(Char16String &&p_str) { template <>
SWAP(_length, p_str._length); const char *CharStringT<char>::get_data() const {
SWAP(_data, p_str._data); if (size()) {
} return &operator[](0);
} else {
Char16String::Char16String(const char16_t *str, int length) : return "";
_data(str), _length(length) {}
Char16String::~Char16String() {
if (_data != nullptr) {
memdelete_arr(_data);
} }
} }
int Char32String::length() const { template <>
return _length; const char16_t *CharStringT<char16_t>::get_data() const {
} if (size()) {
return &operator[](0);
const char32_t *Char32String::get_data() const { } else {
return _data; return u"";
}
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);
} }
} }
int CharWideString::length() const { template <>
return _length; const char32_t *CharStringT<char32_t>::get_data() const {
} if (size()) {
return &operator[](0);
const wchar_t *CharWideString::get_data() const { } else {
return _data; return U"";
}
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 wchar_t *CharStringT<wchar_t>::get_data() const {
if (size()) {
return &operator[](0);
} else {
return L"";
}
}
template <class T>
void CharStringT<T>::copy_from(const T *p_cstr) {
if (!p_cstr) {
resize(0);
return;
}
size_t len = std::char_traits<T>::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<char>;
template class CharStringT<char16_t>;
template class CharStringT<char32_t>;
template class CharStringT<wchar_t>;
// Custom String functions that are not part of bound API. // 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. // 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 { CharString String::utf8() const {
int length = internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), nullptr, 0); int length = internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), nullptr, 0);
int size = length + 1; int size = length + 1;
char *cstr = memnew_arr(char, size); CharString str;
internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), cstr, length); 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 { CharString String::ascii() const {
int length = internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), nullptr, 0); int length = internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), nullptr, 0);
int size = length + 1; int size = length + 1;
char *cstr = memnew_arr(char, size); CharString str;
internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), cstr, length); 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 { Char16String String::utf16() const {
int length = internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), nullptr, 0); int length = internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), nullptr, 0);
int size = length + 1; int size = length + 1;
char16_t *cstr = memnew_arr(char16_t, size); Char16String str;
internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), cstr, length); 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 { Char32String String::utf32() const {
int length = internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), nullptr, 0); int length = internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), nullptr, 0);
int size = length + 1; int size = length + 1;
char32_t *cstr = memnew_arr(char32_t, size); Char32String str;
internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), cstr, length); 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 { CharWideString String::wide_string() const {
int length = internal::gdextension_interface_string_to_wide_chars(_native_ptr(), nullptr, 0); int length = internal::gdextension_interface_string_to_wide_chars(_native_ptr(), nullptr, 0);
int size = length + 1; int size = length + 1;
wchar_t *cstr = memnew_arr(wchar_t, size); CharWideString str;
internal::gdextension_interface_string_to_wide_chars(_native_ptr(), cstr, length); 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) { String &String::operator=(const char *p_str) {

View File

@ -82,6 +82,10 @@ func _ready():
# UtilityFunctions::str() # UtilityFunctions::str()
assert_equal(example.test_str_utility(), "Hello, World! The answer is 42") 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 # PackedArray iterators
assert_equal(example.test_vector_ops(), 105) assert_equal(example.test_vector_ops(), 105)

View File

@ -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_node_argument"), &Example::test_node_argument);
ClassDB::bind_method(D_METHOD("test_string_ops"), &Example::test_string_ops); 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_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_vector_ops"), &Example::test_vector_ops);
ClassDB::bind_method(D_METHOD("test_bitfield", "flags"), &Example::test_bitfield); 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); 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 { int Example::test_vector_ops() const {
PackedInt32Array arr; PackedInt32Array arr;
arr.push_back(10); arr.push_back(10);

View File

@ -117,6 +117,7 @@ public:
Example *test_node_argument(Example *p_node) const; Example *test_node_argument(Example *p_node) const;
String test_string_ops() const; String test_string_ops() const;
String test_str_utility() const; String test_str_utility() const;
bool test_string_is_fourty_two(const String &p_str) const;
int test_vector_ops() const; int test_vector_ops() const;
BitField<Flags> test_bitfield(BitField<Flags> flags); BitField<Flags> test_bitfield(BitField<Flags> flags);