From 1d6442ece3d47f231bb089b785b5fa300ff5f610 Mon Sep 17 00:00:00 2001 From: JoltedJon <14169426+JoltedJon@users.noreply.github.com> Date: Sat, 30 May 2026 17:47:24 -0400 Subject: [PATCH 1/3] Add UTF-16 Strings Add UTF-16 Strings --- engine/native/core/CMakeLists.txt | 2 + engine/native/core/containers/containers.cppm | 3 + engine/native/core/containers/string.cppm | 545 ++++++++++++++++++ .../native/core/definitions/definitions.cppm | 33 +- engine/native/core/definitions/stdtypes.cppm | 7 +- engine/native/core/math/functions.cppm | 13 +- 6 files changed, 584 insertions(+), 19 deletions(-) create mode 100644 engine/native/core/containers/containers.cppm create mode 100644 engine/native/core/containers/string.cppm diff --git a/engine/native/core/CMakeLists.txt b/engine/native/core/CMakeLists.txt index dd4dfb1b..7180d28b 100644 --- a/engine/native/core/CMakeLists.txt +++ b/engine/native/core/CMakeLists.txt @@ -2,7 +2,9 @@ add_modules_library(definitions PIC) add_modules_library(math) add_modules_library(io) add_modules_library(memory) +add_modules_library(containers) target_link_libraries(math PUBLIC definitions bx) target_link_libraries(io PUBLIC definitions stb_image) target_link_libraries(memory PUBLIC definitions math) +target_link_libraries(containers PUBLIC definitions memory math) diff --git a/engine/native/core/containers/containers.cppm b/engine/native/core/containers/containers.cppm new file mode 100644 index 00000000..ca201012 --- /dev/null +++ b/engine/native/core/containers/containers.cppm @@ -0,0 +1,3 @@ +module; +export module core.containers; +export import core.containers.string; diff --git a/engine/native/core/containers/string.cppm b/engine/native/core/containers/string.cppm new file mode 100644 index 00000000..c6f1ee85 --- /dev/null +++ b/engine/native/core/containers/string.cppm @@ -0,0 +1,545 @@ +module; + +#include +#include +#include + +export module core.containers.string; +export import core.stdtypes; +export import core.memory; + +import core.defs; +import core.math.functions; +import core.memory.fixedAllocator; + +using namespace draco::memory; + +export namespace draco::containers { + +// Immutable String +// using utf-16 encoding for compatability with C# +// Signed size as that allows for us to reason about math operations easier +// with the only downside being we can only have strings as large as 2^63 (oh nooo) +struct String { + utf16 const *text; + isize size; + + utf16 const &operator[](isize index) const { + // TODO(Jon) assert(index < size); + return text[index]; + } + + int constexpr StringCompare(String s); + bool constexpr operator==(String s); + bool constexpr operator!=(String s); + bool constexpr operator>(String s); + bool constexpr operator>=(String s); + bool constexpr operator<(String s); + bool constexpr operator<=(String s); + + /// Creates a substring of the String + /// + /// @param pos The start of the substring + /// @param len The length of the substring, if exceeds size of string, will truncate to end of string + /// + /// @return Requested substring + String Substr(isize pos, isize len); + + bool constexpr StartsWith(String prefix); + bool constexpr EndsWith(String suffix); + isize constexpr FindFirst(utf16 c); + isize constexpr FindLast(utf16 c); +}; +String constexpr const EMPTY_STRING = { nullptr, 0 }; + +struct StringIterator { + utf16 const *start; + utf16 const *end; + + constexpr StringIterator(String s) : start{ s.text }, end{ s.text + s.size } {} + + /// Advances to next canonical character + /// + /// @param[out] codepoint The utf-32 codepoint that was advanced over + /// + /// @return true if codepoint is a valid character that can be used. + /// false if codepoint is invalid. Still advances past the character on failure. + /// + bool constexpr Advance(rune *codepoint); +}; + +struct StringBuilder { + Allocator allocator; + utf16 *buffer; + isize size; + isize capacity; + + // Initialization functions, pass in StringBuilder that has been allocated already and these will initialize it with data + // Not constructors since allocations are performed on initialization + + /// Initializes with initial capacity + /// + /// @param allocator Initalized allocator that allows for string builder to grow + /// @param capacity The initial capacity that would like to be requested + memory::Error Create(Allocator allocator, isize capacity = 16); + + /// Initialize with preallocated buffer with initial capacity + /// + /// @param allocator Initalized allocator that allows for string builder to grow + /// @param buffer Preallocated buffer that was created with allocator + /// @param capacity The initial capacity that buffer has before it needs to be reallocated + memory::Error Create(Allocator allocator, utf16 *buffer, isize capacity); + + /// Initialize with preallocated constant sized buffer, size in number of elements + /// + /// @param buffer Preallocated buffer with no access to allocations + /// @param bufferMaxCapacity Max Capacity of preallocated buffer + memory::Error Create(utf16 *buffer, isize bufferMaxCapacity); + + String GetString() const { + return String{ buffer, size }; + } + void Reset() { + size = 0; + } + + /// Set exact capacity, will not shrink capacity + memory::Error Reserve(isize newCapacity); + + /// Changes capacity to allow for at minimum the passed in capacity + /// Will grow capacity according to a growth policy + memory::Error GrowCapacity(isize minCapacity); + + /// Write UTF-16 character literal. + /// example: u'a' + memory::Error Write(utf16 c); + + /// Write rune to string, converting to utf16 + /// Returns Error::Other if rune is outside valid range + memory::Error Write(rune r); + + /// Write utf-16 encoded null-terminated string + /// example: u"Hello World" + memory::Error Write(utf16 const *utf16_c_str); + + /// Copies contents of str into StringBuilder + memory::Error Write(String str); + + /// Writes Integer with specified base + /// Supports base 2, 10 and 16 + template + memory::Error WriteInt(T val, int base = 10); + + // TODO(Jon) Functions + // Format function to write into string with format specifiers +}; + +/* STRING */ + +/// Acquire the length of a utf-16 encoded C string +/// Should not be needed largely but given just incase. +isize StringLength(utf16 const *str) { + // TODO(Jon) This can be enhanced with SIMD checks + utf16 const *start = str; + + // Align string to 8 byte boundary + while ((uintptr)str & 7) { + if (*str == 0) { + return str - start; + } + str++; + } + + u64 const *p = (u64 const *)str; + + for (;;) { + u64 x = *p++; + + /* Test to see if x contains any 0s in the 16 bit chunks + * + * Extended form of this + * (x - 1) & ~x & 0x80; + * + * a = (0x0000 - 0x0001) = 0xFFFF + * b = ~0x0000 = 0xFFFF + * c = a & b = 0xFFFF + * c & 0x8000 = 0x8000; ZERO Found + */ + u64 m = ((x - 0x0001'0001'0001'0001ULL) & ~x & 0x8000'8000'8000'8000ULL); + + if (m) { + str = (utf16 const *)(p - 1); + + for (int i = 0; i < 4; ++i) { + if (str[i] == 0) { + return (str + i) - start; + } + } + } + } +} + +int constexpr String::StringCompare(String s) { + if (text == nullptr) { + return -1; + } + if (s.text == nullptr) { + return 1; + } + + isize min_size = draco::math::min(min_size, min_size); + + int result = 0; + if consteval { + for (isize i = 0; i < min_size; ++i) { + if (text[i] != s.text[i]) { + return text[i] < s.text[i] ? -1 : 1; + } + } + } else { + result = memcmp(text, s.text, min_size * sizeof(utf16)); + } + + if (result == 0) { + return (int)(size - s.size); + } + return result; +} + +bool constexpr String::operator==(String s) { + return StringCompare(s) == 0; +} +bool constexpr String::operator!=(String s) { + return StringCompare(s) != 0; +} +bool constexpr String::operator>(String s) { + return StringCompare(s) > 0; +} +bool constexpr String::operator>=(String s) { + return StringCompare(s) >= 0; +} +bool constexpr String::operator<(String s) { + return StringCompare(s) < 0; +} +bool constexpr String::operator<=(String s) { + return StringCompare(s) <= 0; +} + +String String::Substr(isize pos, isize len) { + if (pos + len >= size) { + return EMPTY_STRING; + } + + return { text + pos, len }; +} + +bool constexpr String::StartsWith(String prefix) { + if (prefix.size > size) { + return false; + } + + return Substr(0, prefix.size) == prefix; +} + +bool constexpr String::EndsWith(String suffix) { + if (suffix.size > size) { + return false; + } + + return Substr(size - suffix.size, suffix.size) == suffix; +} + +isize constexpr String::FindFirst(utf16 c) { + for (isize i = 0; i < size; i++) { + if (text[i] == c) { + return i; + } + } + return size; +} + +isize constexpr String::FindLast(utf16 c) { + // Advantage of using signed size is this is perfectly valid + for (isize i = size - 1; i >= 0; i--) { + if (text[i] == c) { + return i; + } + } + return size; +} + +/* TODO(Jon) Functions + * - Split by character into Array of Strings + * - Find First/Last of substring + * - Trim Whitespace front and back, which involves some Unicode shenanigans. See: https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt + * - To upper/lower case + * - Equals fold, check if two strings are equal ignoring case in a Unicode fashion + */ + +/* STRING ITERATOR */ + +bool constexpr StringIterator::Advance(rune *codepoint) { + if (start >= end) { + return false; + } + utf16 high = *start++; + + // Values from 0xD800 and 0xDFFF denote a surrogate + if (high < 0xD800 || high >= 0xE000) { + *codepoint = high; + return true; + } + + // Malformed String Iterator checks, would mean bug in code + // TODO(Jon) add logging or some other behavior here + + // Misplaced low surrogate value + if (high >= 0xDC00) { + return false; + } + if (start >= end) { + return false; + } + + utf16 low = *start++; + + if (low < 0xDC00 || low > 0xDFFF) { + return false; + } + + high -= 0xD800; + low -= 0xDC00; + + *codepoint = 0x10000 + (((rune)high << 10) | (rune)low); + return true; +} + +/* STRING BUILDER */ + +// Initializes with initial capacity +memory::Error StringBuilder::Create(Allocator allocator, isize capacity) { + // TODO(Jon) Assert capacity is greater than 0 + Slice dst; + memory::Error err = allocator.alloc(&dst, capacity, alignof(utf16)); + if (err != memory::Error::Okay) { + return err; + } + + *this = StringBuilder{ + .allocator = allocator, + .buffer = (utf16 *)dst.data, + .size = 0, + .capacity = capacity + }; + + return Error::Okay; +} + +// Initialize with preallocated buffer with initial capacity +memory::Error StringBuilder::Create(Allocator allocator, utf16 *buffer, isize capacity) { + // TODO(Jon) Assert capacity is greater than 0 + *this = StringBuilder{ + .allocator = allocator, + .buffer = buffer, + .size = 0, + .capacity = capacity + }; + return Error::Okay; +} + +// Initialize with preallocated constant sized buffer, size in number of elements +memory::Error StringBuilder::Create(utf16 *buffer, isize buffer_max_capacity) { + fixed::FixedAllocator fixed_allocator; + Slice slice = { + .data = buffer, + .size = buffer_max_capacity * sizeof(utf16) + }; + fixed::init(&fixed_allocator, slice); + + Allocator allocator; + fixed::asAllocator(&allocator, &fixed_allocator); + + *this = StringBuilder{ + .allocator = allocator, + .buffer = buffer, + .size = 0, + .capacity = buffer_max_capacity + }; + return Error::Okay; +} + +memory::Error StringBuilder::Reserve(isize newCapacity) { + Slice newDst; + Error err = allocator.alloc(&newDst, newCapacity * sizeof(utf16), alignof(utf16)); + if (err != Error::Okay) { + return err; + } + + memcpy(newDst.data, buffer, size * sizeof(utf16)); + + Slice oldDst = { + .data = buffer, + .size = (usize)capacity + }; + allocator.free(oldDst); + + capacity = newCapacity; + buffer = (utf16 *)newDst.data; + + return Error::Okay; +} + +memory::Error StringBuilder::GrowCapacity(isize minCapacity) { + if (capacity >= minCapacity) { + return Error::Okay; + } + + isize newCapacity = capacity + draco::math::max(capacity / 2, minCapacity); + return Reserve(newCapacity); +} + +memory::Error StringBuilder::Write(utf16 c) { + Error err = GrowCapacity(size + 1); + if (err != Error::Okay) { + return err; + } + + buffer[size++] = c; + return Error::Okay; +} + +memory::Error StringBuilder::Write(rune r) { + if (r > 0x10FFFF || (r >= 0xD800 && r <= 0xDFFF)) { + return Error::Other; + } + + Error err = GrowCapacity(size + 2); + if (err != Error::Okay) { + return err; + } + + if (r <= 0xFFFF) { + buffer[size++] = (utf16)r; + return Error::Okay; + } + + r -= 0x10000; + + utf16 high = 0xD800 + (r >> 10); + utf16 low = 0xDC00 + (r & 0x3FF); + + buffer[size++] = high; + buffer[size++] = low; + + return Error::Okay; +} + +memory::Error StringBuilder::Write(utf16 const *str) { + // TODO(Jon) assert not null + isize length = StringLength(str); + Error err = GrowCapacity(length); + if (err != Error::Okay) { + return err; + } + memcpy(buffer + size, str, length * sizeof(utf16)); + size += length; + return Error::Okay; +} + +memory::Error StringBuilder::Write(String str) { + Error err = GrowCapacity(str.size); + if (err != Error::Okay) { + return err; + } + memcpy(buffer + size, str.text, str.size * sizeof(utf16)); + size += str.size; + return Error::Okay; +} + +template +memory::Error StringBuilder::WriteInt(T val, int base) { + // Backwards since the algorithm reverses the string after creating it + static constexpr utf16 binaryNibbles[][4] = { + { u'0', u'0', u'0', u'0' }, + { u'1', u'0', u'0', u'0' }, + { u'0', u'1', u'0', u'0' }, + { u'1', u'1', u'0', u'0' }, + { u'0', u'0', u'1', u'0' }, + { u'1', u'0', u'1', u'0' }, + { u'0', u'1', u'1', u'0' }, + { u'1', u'1', u'1', u'0' }, + { u'0', u'0', u'0', u'1' }, + { u'1', u'0', u'0', u'1' }, + { u'0', u'1', u'0', u'1' }, + { u'1', u'1', u'0', u'1' }, + { u'0', u'0', u'1', u'1' }, + { u'1', u'0', u'1', u'1' }, + { u'0', u'1', u'1', u'1' }, + { u'1', u'1', u'1', u'1' }, + }; + static constexpr utf16 hexadecimalNibbles[] = { + u'0', + u'1', + u'2', + u'3', + u'4', + u'5', + u'6', + u'7', + u'8', + u'9', + u'A', + u'B', + u'C', + u'D', + u'E', + u'F', + }; + + utf16 buff[sizeof(T) * 8]; + isize index = 0; + bool sign = false; + + if constexpr (std::is_signed_v) { + if (val < 0) { + val = -val; + sign = true; + } + } + + if (val == 0) { + buff[index++] = u'0'; + } else if (base == 2) { + while (val != 0) { + std::memcpy(buff + index, binaryNibbles[val & 0xF], sizeof(binaryNibbles[0])); + index += 4; + val >>= 4; + } + } else if (base == 16) { + while (val != 0) { + buff[index++] = hexadecimalNibbles[val & 0xF]; + val >>= 4; + } + } else { + while (val != 0) { + buff[index++] = u'0' + (val % 10); + val /= 10; + } + } + + GrowCapacity(size + index + sign); + + if (sign) { + buffer[size++] = u'-'; + } + + isize origIndex = index--; + for (isize i = 0; i < origIndex; i++, index--) { + buffer[i + size] = buff[index]; + } + size += origIndex; + + return Error::Okay; +} + +} // namespace draco::containers diff --git a/engine/native/core/definitions/definitions.cppm b/engine/native/core/definitions/definitions.cppm index 822c9985..488f4713 100644 --- a/engine/native/core/definitions/definitions.cppm +++ b/engine/native/core/definitions/definitions.cppm @@ -6,24 +6,27 @@ export module core.defs; export import core.version; export import core.stdtypes; -static_assert(__cplusplus >= 202207L, "Minimum of C++23 required. Consider upgrading your compiler."); +static_assert(__cplusplus >= 202207L, "Minimum of C++23 required. Consider upgrading your compiler."); export namespace draco { - template - concept arithmetic = std::is_arithmetic_v; +template +concept integral = std::is_integral_v; - template - concept trivial = std::is_trivial_v; +template +concept arithmetic = std::is_arithmetic_v; - // Whether the default value of a type is just all-0 bytes. - // This can most commonly be exploited by using memset for these types instead of loop-construct. - // Must be explicitly specialized to mark a type as such. - template - struct is_zero_constructible : std::false_type {}; +template +concept trivial = std::is_trivial_v; - template - constexpr bool is_zero_constructible_v = is_zero_constructible::value; +// Whether the default value of a type is just all-0 bytes. +// This can most commonly be exploited by using memset for these types instead of loop-construct. +// Must be explicitly specialized to mark a type as such. +template +struct is_zero_constructible : std::false_type {}; - template - concept zero_constructible = is_zero_constructible_v; -} +template +constexpr bool is_zero_constructible_v = is_zero_constructible::value; + +template +concept zero_constructible = is_zero_constructible_v; +} //namespace draco diff --git a/engine/native/core/definitions/stdtypes.cppm b/engine/native/core/definitions/stdtypes.cppm index fb1284b7..c9a5c910 100644 --- a/engine/native/core/definitions/stdtypes.cppm +++ b/engine/native/core/definitions/stdtypes.cppm @@ -27,7 +27,10 @@ using rawptr = void *; using uintptr = uintptr_t; using ptrdiff = ptrdiff_t; -// UTF-32 type -using rune = u32; +// Unicode types +using utf8 = char8_t; +using utf16 = char16_t; +using utf32 = char32_t; +using rune = char32_t; } // namespace draco diff --git a/engine/native/core/math/functions.cppm b/engine/native/core/math/functions.cppm index c3260098..ccf438b4 100644 --- a/engine/native/core/math/functions.cppm +++ b/engine/native/core/math/functions.cppm @@ -1,6 +1,5 @@ module; -#include #include #include #include @@ -11,6 +10,16 @@ import core.defs; import core.stdtypes; export namespace draco::math { + template + constexpr T min(T a, T b) { + return a < b ? a : b; + } + + template + constexpr T max(T a, T b) { + return b < a ? a : b; + } + template constexpr T sqr(T x) noexcept { return x*x; } @@ -193,4 +202,4 @@ export namespace draco::math { T d = (control_1 - start) * T{3.} * omt2 + (control_2 - control_1) * T{6.} * omt * t + (end - control_2) * T{3.} * t2; return d; } -} \ No newline at end of file +} From 7026321dc3b200985a898dd0ec63fe9d9731156e Mon Sep 17 00:00:00 2001 From: JoltedJon <14169426+JoltedJon@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:39:33 -0400 Subject: [PATCH 2/3] Coderabbit suggestions --- engine/native/core/containers/string.cppm | 58 ++++++++++++++--------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/engine/native/core/containers/string.cppm b/engine/native/core/containers/string.cppm index c6f1ee85..cdfa7aa2 100644 --- a/engine/native/core/containers/string.cppm +++ b/engine/native/core/containers/string.cppm @@ -180,14 +180,14 @@ isize StringLength(utf16 const *str) { } int constexpr String::StringCompare(String s) { - if (text == nullptr) { - return -1; - } - if (s.text == nullptr) { - return 1; + if (text == nullptr || s.text == nullptr) { + if (text == s.text) { + return 0; + } + return text == nullptr ? -1 : 1; } - isize min_size = draco::math::min(min_size, min_size); + isize min_size = draco::math::min(size, s.size); int result = 0; if consteval { @@ -226,11 +226,12 @@ bool constexpr String::operator<=(String s) { } String String::Substr(isize pos, isize len) { - if (pos + len >= size) { + if (pos < 0 || len <= 0 || pos >= size) { return EMPTY_STRING; } - return { text + pos, len }; + isize clampedLen = draco::math::min(len, size - pos); + return { text + pos, clampedLen }; } bool constexpr String::StartsWith(String prefix) { @@ -320,7 +321,7 @@ bool constexpr StringIterator::Advance(rune *codepoint) { memory::Error StringBuilder::Create(Allocator allocator, isize capacity) { // TODO(Jon) Assert capacity is greater than 0 Slice dst; - memory::Error err = allocator.alloc(&dst, capacity, alignof(utf16)); + memory::Error err = allocator.alloc(&dst, capacity * sizeof(utf16), alignof(utf16)); if (err != memory::Error::Okay) { return err; } @@ -369,6 +370,10 @@ memory::Error StringBuilder::Create(utf16 *buffer, isize buffer_max_capacity) { } memory::Error StringBuilder::Reserve(isize newCapacity) { + if (newCapacity <= capacity) { + return Error::Okay; + } + Slice newDst; Error err = allocator.alloc(&newDst, newCapacity * sizeof(utf16), alignof(utf16)); if (err != Error::Okay) { @@ -379,7 +384,7 @@ memory::Error StringBuilder::Reserve(isize newCapacity) { Slice oldDst = { .data = buffer, - .size = (usize)capacity + .size = (usize)capacity * sizeof(utf16) }; allocator.free(oldDst); @@ -437,7 +442,7 @@ memory::Error StringBuilder::Write(rune r) { memory::Error StringBuilder::Write(utf16 const *str) { // TODO(Jon) assert not null isize length = StringLength(str); - Error err = GrowCapacity(length); + Error err = GrowCapacity(size + length); if (err != Error::Okay) { return err; } @@ -447,7 +452,7 @@ memory::Error StringBuilder::Write(utf16 const *str) { } memory::Error StringBuilder::Write(String str) { - Error err = GrowCapacity(str.size); + Error err = GrowCapacity(size + str.size); if (err != Error::Okay) { return err; } @@ -500,34 +505,41 @@ memory::Error StringBuilder::WriteInt(T val, int base) { isize index = 0; bool sign = false; + using U = std::make_unsigned_t; + U unsignedVal = val; if constexpr (std::is_signed_v) { if (val < 0) { - val = -val; + unsignedVal = U{} - static_cast(val); sign = true; + } else { + unsignedVal = static_cast(val); } } - if (val == 0) { + if (unsignedVal == 0) { buff[index++] = u'0'; } else if (base == 2) { while (val != 0) { - std::memcpy(buff + index, binaryNibbles[val & 0xF], sizeof(binaryNibbles[0])); + std::memcpy(buff + index, binaryNibbles[unsignedVal & 0xF], sizeof(binaryNibbles[0])); index += 4; - val >>= 4; + unsignedVal >>= 4; } } else if (base == 16) { - while (val != 0) { - buff[index++] = hexadecimalNibbles[val & 0xF]; - val >>= 4; + while (unsignedVal != 0) { + buff[index++] = hexadecimalNibbles[unsignedVal & 0xF]; + unsignedVal >>= 4; } } else { - while (val != 0) { - buff[index++] = u'0' + (val % 10); - val /= 10; + while (unsignedVal != 0) { + buff[index++] = u'0' + (unsignedVal % 10); + unsignedVal /= 10; } } - GrowCapacity(size + index + sign); + Error err = GrowCapacity(size + index + sign); + if (err != Error::Okay) { + return err; + } if (sign) { buffer[size++] = u'-'; From 7a0c807a840053f5af951359ab0b77127869c57f Mon Sep 17 00:00:00 2001 From: JoltedJon <14169426+JoltedJon@users.noreply.github.com> Date: Sun, 14 Jun 2026 18:54:08 -0400 Subject: [PATCH 3/3] Real People suggestions --- engine/native/core/containers/string.cpp | 101 ++++++++++ engine/native/core/containers/string.cppm | 222 +++------------------- 2 files changed, 126 insertions(+), 197 deletions(-) create mode 100644 engine/native/core/containers/string.cpp diff --git a/engine/native/core/containers/string.cpp b/engine/native/core/containers/string.cpp new file mode 100644 index 00000000..64c51904 --- /dev/null +++ b/engine/native/core/containers/string.cpp @@ -0,0 +1,101 @@ +module; + +#include + +module core.containers.string; + +namespace draco::containers { + +memory::Error StringBuilder::Reserve(isize newCapacity) { + if (newCapacity <= capacity) { + return Error::Okay; + } + + Slice newDst; + Error err = allocator.alloc(&newDst, newCapacity * sizeof(utf16), alignof(utf16)); + if (err != Error::Okay) { + return err; + } + + memcpy(newDst.data, buffer, size * sizeof(utf16)); + + Slice oldDst = { + .data = buffer, + .size = (usize)capacity * sizeof(utf16) + }; + allocator.free(oldDst); + + capacity = newCapacity; + buffer = (utf16 *)newDst.data; + + return Error::Okay; +} + +memory::Error StringBuilder::GrowCapacity(isize minCapacity) { + if (capacity >= minCapacity) { + return Error::Okay; + } + + isize newCapacity = capacity + draco::math::max(capacity / 2, minCapacity); + return Reserve(newCapacity); +} + +memory::Error StringBuilder::Write(utf16 c) { + Error err = GrowCapacity(size + 1); + if (err != Error::Okay) { + return err; + } + + buffer[size++] = c; + return Error::Okay; +} + +memory::Error StringBuilder::Write(rune r) { + if (r > 0x10FFFF || (r >= 0xD800 && r <= 0xDFFF)) { + return Error::Other; + } + + Error err = GrowCapacity(size + 2); + if (err != Error::Okay) { + return err; + } + + if (r <= 0xFFFF) { + buffer[size++] = (utf16)r; + return Error::Okay; + } + + r -= 0x10000; + + utf16 high = 0xD800 + (r >> 10); + utf16 low = 0xDC00 + (r & 0x3FF); + + buffer[size++] = high; + buffer[size++] = low; + + return Error::Okay; +} + +memory::Error StringBuilder::Write(utf16 const *str) { + // TODO(Jon) assert not null + isize length = StringLength(str); + Error err = GrowCapacity(size + length); + if (err != Error::Okay) { + return err; + } + memcpy(buffer + size, str, length * sizeof(utf16)); + size += length; + return Error::Okay; +} + +memory::Error StringBuilder::Write(String str) { + Error err = GrowCapacity(size + str.size); + if (err != Error::Okay) { + return err; + } + memcpy(buffer + size, str.text, str.size * sizeof(utf16)); + size += str.size; + return Error::Okay; +} + +} //namespace draco::containers diff --git a/engine/native/core/containers/string.cppm b/engine/native/core/containers/string.cppm index cdfa7aa2..877f95dd 100644 --- a/engine/native/core/containers/string.cppm +++ b/engine/native/core/containers/string.cppm @@ -1,6 +1,5 @@ module; -#include #include #include @@ -30,12 +29,24 @@ struct String { } int constexpr StringCompare(String s); - bool constexpr operator==(String s); - bool constexpr operator!=(String s); - bool constexpr operator>(String s); - bool constexpr operator>=(String s); - bool constexpr operator<(String s); - bool constexpr operator<=(String s); + bool constexpr operator==(String s) { + return StringCompare(s) == 0; + } + bool constexpr operator!=(String s) { + return StringCompare(s) != 0; + } + bool constexpr operator>(String s) { + return StringCompare(s) > 0; + } + bool constexpr operator>=(String s) { + return StringCompare(s) >= 0; + } + bool constexpr operator<(String s) { + return StringCompare(s) < 0; + } + bool constexpr operator<=(String s) { + return StringCompare(s) <= 0; + } /// Creates a substring of the String /// @@ -43,7 +54,7 @@ struct String { /// @param len The length of the substring, if exceeds size of string, will truncate to end of string /// /// @return Requested substring - String Substr(isize pos, isize len); + String constexpr Substr(isize pos, isize len); bool constexpr StartsWith(String prefix); bool constexpr EndsWith(String suffix); @@ -74,32 +85,12 @@ struct StringBuilder { isize size; isize capacity; - // Initialization functions, pass in StringBuilder that has been allocated already and these will initialize it with data - // Not constructors since allocations are performed on initialization - - /// Initializes with initial capacity - /// - /// @param allocator Initalized allocator that allows for string builder to grow - /// @param capacity The initial capacity that would like to be requested - memory::Error Create(Allocator allocator, isize capacity = 16); - - /// Initialize with preallocated buffer with initial capacity - /// - /// @param allocator Initalized allocator that allows for string builder to grow - /// @param buffer Preallocated buffer that was created with allocator - /// @param capacity The initial capacity that buffer has before it needs to be reallocated - memory::Error Create(Allocator allocator, utf16 *buffer, isize capacity); - - /// Initialize with preallocated constant sized buffer, size in number of elements - /// - /// @param buffer Preallocated buffer with no access to allocations - /// @param bufferMaxCapacity Max Capacity of preallocated buffer - memory::Error Create(utf16 *buffer, isize bufferMaxCapacity); + StringBuilder(Allocator allocator) : allocator{ allocator }, buffer{ nullptr }, size{ 0 }, capacity{ 0 } {} - String GetString() const { + String constexpr GetString() const { return String{ buffer, size }; } - void Reset() { + void constexpr Reset() { size = 0; } @@ -120,7 +111,7 @@ struct StringBuilder { /// Write utf-16 encoded null-terminated string /// example: u"Hello World" - memory::Error Write(utf16 const *utf16_c_str); + memory::Error Write(utf16 const *str); /// Copies contents of str into StringBuilder memory::Error Write(String str); @@ -138,7 +129,7 @@ struct StringBuilder { /// Acquire the length of a utf-16 encoded C string /// Should not be needed largely but given just incase. -isize StringLength(utf16 const *str) { +isize constexpr StringLength(utf16 const *str) { // TODO(Jon) This can be enhanced with SIMD checks utf16 const *start = str; @@ -206,26 +197,7 @@ int constexpr String::StringCompare(String s) { return result; } -bool constexpr String::operator==(String s) { - return StringCompare(s) == 0; -} -bool constexpr String::operator!=(String s) { - return StringCompare(s) != 0; -} -bool constexpr String::operator>(String s) { - return StringCompare(s) > 0; -} -bool constexpr String::operator>=(String s) { - return StringCompare(s) >= 0; -} -bool constexpr String::operator<(String s) { - return StringCompare(s) < 0; -} -bool constexpr String::operator<=(String s) { - return StringCompare(s) <= 0; -} - -String String::Substr(isize pos, isize len) { +String constexpr String::Substr(isize pos, isize len) { if (pos < 0 || len <= 0 || pos >= size) { return EMPTY_STRING; } @@ -317,150 +289,6 @@ bool constexpr StringIterator::Advance(rune *codepoint) { /* STRING BUILDER */ -// Initializes with initial capacity -memory::Error StringBuilder::Create(Allocator allocator, isize capacity) { - // TODO(Jon) Assert capacity is greater than 0 - Slice dst; - memory::Error err = allocator.alloc(&dst, capacity * sizeof(utf16), alignof(utf16)); - if (err != memory::Error::Okay) { - return err; - } - - *this = StringBuilder{ - .allocator = allocator, - .buffer = (utf16 *)dst.data, - .size = 0, - .capacity = capacity - }; - - return Error::Okay; -} - -// Initialize with preallocated buffer with initial capacity -memory::Error StringBuilder::Create(Allocator allocator, utf16 *buffer, isize capacity) { - // TODO(Jon) Assert capacity is greater than 0 - *this = StringBuilder{ - .allocator = allocator, - .buffer = buffer, - .size = 0, - .capacity = capacity - }; - return Error::Okay; -} - -// Initialize with preallocated constant sized buffer, size in number of elements -memory::Error StringBuilder::Create(utf16 *buffer, isize buffer_max_capacity) { - fixed::FixedAllocator fixed_allocator; - Slice slice = { - .data = buffer, - .size = buffer_max_capacity * sizeof(utf16) - }; - fixed::init(&fixed_allocator, slice); - - Allocator allocator; - fixed::asAllocator(&allocator, &fixed_allocator); - - *this = StringBuilder{ - .allocator = allocator, - .buffer = buffer, - .size = 0, - .capacity = buffer_max_capacity - }; - return Error::Okay; -} - -memory::Error StringBuilder::Reserve(isize newCapacity) { - if (newCapacity <= capacity) { - return Error::Okay; - } - - Slice newDst; - Error err = allocator.alloc(&newDst, newCapacity * sizeof(utf16), alignof(utf16)); - if (err != Error::Okay) { - return err; - } - - memcpy(newDst.data, buffer, size * sizeof(utf16)); - - Slice oldDst = { - .data = buffer, - .size = (usize)capacity * sizeof(utf16) - }; - allocator.free(oldDst); - - capacity = newCapacity; - buffer = (utf16 *)newDst.data; - - return Error::Okay; -} - -memory::Error StringBuilder::GrowCapacity(isize minCapacity) { - if (capacity >= minCapacity) { - return Error::Okay; - } - - isize newCapacity = capacity + draco::math::max(capacity / 2, minCapacity); - return Reserve(newCapacity); -} - -memory::Error StringBuilder::Write(utf16 c) { - Error err = GrowCapacity(size + 1); - if (err != Error::Okay) { - return err; - } - - buffer[size++] = c; - return Error::Okay; -} - -memory::Error StringBuilder::Write(rune r) { - if (r > 0x10FFFF || (r >= 0xD800 && r <= 0xDFFF)) { - return Error::Other; - } - - Error err = GrowCapacity(size + 2); - if (err != Error::Okay) { - return err; - } - - if (r <= 0xFFFF) { - buffer[size++] = (utf16)r; - return Error::Okay; - } - - r -= 0x10000; - - utf16 high = 0xD800 + (r >> 10); - utf16 low = 0xDC00 + (r & 0x3FF); - - buffer[size++] = high; - buffer[size++] = low; - - return Error::Okay; -} - -memory::Error StringBuilder::Write(utf16 const *str) { - // TODO(Jon) assert not null - isize length = StringLength(str); - Error err = GrowCapacity(size + length); - if (err != Error::Okay) { - return err; - } - memcpy(buffer + size, str, length * sizeof(utf16)); - size += length; - return Error::Okay; -} - -memory::Error StringBuilder::Write(String str) { - Error err = GrowCapacity(size + str.size); - if (err != Error::Okay) { - return err; - } - memcpy(buffer + size, str.text, str.size * sizeof(utf16)); - size += str.size; - return Error::Okay; -} - template memory::Error StringBuilder::WriteInt(T val, int base) { // Backwards since the algorithm reverses the string after creating it