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.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 new file mode 100644 index 00000000..877f95dd --- /dev/null +++ b/engine/native/core/containers/string.cppm @@ -0,0 +1,385 @@ +module; + +#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) { + 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 + /// + /// @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 constexpr 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; + + StringBuilder(Allocator allocator) : allocator{ allocator }, buffer{ nullptr }, size{ 0 }, capacity{ 0 } {} + + String constexpr GetString() const { + return String{ buffer, size }; + } + void constexpr 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 *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 constexpr 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 || s.text == nullptr) { + if (text == s.text) { + return 0; + } + return text == nullptr ? -1 : 1; + } + + isize min_size = draco::math::min(size, s.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; +} + +String constexpr String::Substr(isize pos, isize len) { + if (pos < 0 || len <= 0 || pos >= size) { + return EMPTY_STRING; + } + + isize clampedLen = draco::math::min(len, size - pos); + return { text + pos, clampedLen }; +} + +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 */ + +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; + + using U = std::make_unsigned_t; + U unsignedVal = val; + if constexpr (std::is_signed_v) { + if (val < 0) { + unsignedVal = U{} - static_cast(val); + sign = true; + } else { + unsignedVal = static_cast(val); + } + } + + if (unsignedVal == 0) { + buff[index++] = u'0'; + } else if (base == 2) { + while (val != 0) { + std::memcpy(buff + index, binaryNibbles[unsignedVal & 0xF], sizeof(binaryNibbles[0])); + index += 4; + unsignedVal >>= 4; + } + } else if (base == 16) { + while (unsignedVal != 0) { + buff[index++] = hexadecimalNibbles[unsignedVal & 0xF]; + unsignedVal >>= 4; + } + } else { + while (unsignedVal != 0) { + buff[index++] = u'0' + (unsignedVal % 10); + unsignedVal /= 10; + } + } + + Error err = GrowCapacity(size + index + sign); + if (err != Error::Okay) { + return err; + } + + 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 +}