diff --git a/openVulkanoCpp/Data/Containers/String.hpp b/openVulkanoCpp/Data/Containers/String.hpp new file mode 100644 index 0000000..b74ddd3 --- /dev/null +++ b/openVulkanoCpp/Data/Containers/String.hpp @@ -0,0 +1,325 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace OpenVulkano +{ + class String final + { + public: + String() = default; + String(const char* str) : m_string(str) {} + String(const std::string& str) : m_string(str) {} + String(const std::string_view& str) : m_string(str) {} + String(const String& other) : m_string(other.m_string) {} + String(String&& other) noexcept : m_string(std::move(other.m_string)) {} + String(std::string&& other) noexcept : m_string(std::move(other)) {} + ~String() = default; + + template String& operator=(const T& other) + { + m_string = other; + return *this; + } + + template String& operator=(T&& other) + { + m_string = std::move(other); + return *this; + } + + String& operator=(const String& other) + { + m_string = other.m_string; + return *this; + } + + String& operator=(String&& other) + { + m_string = std::move(other.m_string); + return *this; + } + + template String& operator+=(const T& other) + { + m_string += other; + return *this; + } + + String& operator+=(const String& other) + { + m_string += other.m_string; + return *this; + } + + + + template String operator+(const T& other) const { return m_string + other; } + String operator+(const String& other) const { return m_string + other.m_string; } + + template auto operator<=>(const T& other) const { return m_string <=> other; } + auto operator<=>(const String& other) const = default; + explicit operator bool() const { return !m_string.empty(); } + + operator std::string() const { return m_string; } + explicit operator std::string_view() const { return m_string; } + + char& operator[](size_t index) noexcept { return m_string[index]; } + const char& operator[](size_t index) const noexcept { return m_string[index]; } + char& At(size_t index) { return m_string.at(index); } + const char& At(size_t index) const { return m_string.at(index); } + const char* CharString() const { return m_string.c_str(); } + const char* Data() { return m_string.data(); } + size_t Length() const { return m_string.length(); } + size_t Size() const { return m_string.size(); } + bool Empty() const { return m_string.empty(); } + size_t Capacity() const { return m_string.capacity(); } + size_t CharCount() const { return utf8::distance(m_string.begin(), m_string.end()); } + + char& Front() { return m_string.front(); } + char& Back() { return m_string.back(); } + void PopBack() { m_string.pop_back(); } + void Clear() { m_string.clear(); } + void ShrinkToFit() { m_string.shrink_to_fit(); } + String SubString(size_t start, size_t elementCount) const { return m_string.substr(start, elementCount); } + bool StartsWith(const std::string_view& str) const { return m_string.find(str) == 0; } + bool EndsWith(const std::string_view& str) const { return m_string.rfind(str) == m_string.size() - str.size(); } + bool Contains(const std::string_view& str) const { return m_string.find(str) != std::string::npos; } + + size_t FindStartIndexOf(const std::string_view& str) const { return m_string.find(str); } + size_t FindEndIndexOf(const std::string_view& str) const { return m_string.rfind(str); } + + String& Trim() noexcept { return TrimBack().TrimFront(); } + + [[nodiscard]] String Trim() const { return Trim(); } + + String& TrimFront() noexcept + { + size_t start = m_string.find_first_not_of(" \t\n\r"); + if (start == std::string::npos) + { + m_string.clear(); + } + else + { + m_string.erase(0, start); + } + return *this; + } + + String& TrimBack() noexcept + { + size_t end = m_string.find_last_not_of(" \t\n\r"); + if (end == std::string::npos) + { + m_string.clear(); + } + else + { + m_string.resize(end + 1); + } + return *this; + } + + std::vector Split(const std::string_view& delimiter) const + { + std::vector result; + size_t start = 0; + size_t end = m_string.find(delimiter); + while (end != std::string::npos) + { + result.push_back(m_string.substr(start, end - start)); + start = end + delimiter.size(); + end = m_string.find(delimiter, start); + } + result.push_back(m_string.substr(start, end)); + return result; + } + + std::pair SplitAtLastOccurrence(const std::string_view& delimiter) const + { + size_t end = m_string.rfind(delimiter); + if (end == std::string::npos) + { + return { m_string, "" }; + } + return { m_string.substr(0, end), m_string.substr(end + delimiter.size()) }; + } + + std::pair SplitAtFirstOccurrence(const std::string_view& delimiter) const + { + size_t end = m_string.find(delimiter); + if (end == std::string::npos) + { + return { m_string, "" }; + } + return { m_string.substr(0, end), m_string.substr(end + delimiter.size()) }; + } + + std::vector SplitAsStringViews(const std::string_view& delimiter) const + { + std::vector result; + size_t start = 0; + size_t end = m_string.find(delimiter); + while (end != std::string::npos) + { + result.push_back(std::string_view(m_string.c_str() + start, end - start)); + start = end + delimiter.size(); + end = m_string.find(delimiter, start); + } + result.push_back( + std::string_view(m_string.c_str() + start, m_string.size() - (start + end + delimiter.size()))); + return result; + } + + std::pair + SplitAtLastOccurenceAsStringViews(const std::string_view& delimiter) const + { + size_t end = m_string.rfind(delimiter); + if (end == std::string::npos) + { + return { m_string, "" }; + } + return { std::string_view(m_string.c_str(), end), + std::string_view(m_string.c_str() + end + delimiter.size(), + m_string.size() - end - delimiter.size()) }; + } + + std::pair + SplitAtFirstOccurenceAsStringViews(const std::string_view& delimiter) const + { + size_t end = m_string.find(delimiter); + if (end == std::string::npos) + { + return { m_string, "" }; + } + return { std::string_view(m_string.c_str(), end), + std::string_view(m_string.c_str() + end + delimiter.size(), + m_string.size() - end - delimiter.size()) }; + } + + static inline int64_t OctToInt(const std::string_view& string) + { + int64_t result = 0; + for (int i = 0; i < static_cast(string.length()); i++) + { + char c = string[i]; + if (c == 0) + { + break; + } + if (c == ' ') + { + continue; + } + if (c < '0' || c > '7') + { + return -1; + } + result = result * 8 + c - '0'; + } + return result; + } + + int64_t OctToInt() const { return OctToInt(m_string); } + + static inline int64_t HexToInt(const std::string_view& string) + { + int64_t result = 0; + for (char c: string) + { + if (c == 0) + { + break; + } + if (c == ' ') + { + continue; + } + if (c >= '0' && c <= '9') + { + result = result * 16 + c - '0'; + } + else if (c >= 'A' && c <= 'F') + { + result = result * 16 + c - 'A' + 10; + } + else if (c >= 'a' && c <= 'f') + { + result = result * 16 + c - 'a' + 10; + } + else + { + return -1; + } + } + return result; + } + + int64_t HexToInt() { return HexToInt(m_string); } + + static constexpr bool IsUrl(const std::string_view& str) + { + return str.find("http://") == 0 || str.find("https://") == 0 || str.find("ftp://") == 0; + } + + bool IsUrl() const { return IsUrl(m_string); } + + void ToUpper() noexcept + { + std::transform(m_string.begin(), m_string.end(), m_string.begin(), + [](unsigned char c) { return std::toupper(c); }); + } + + void ToLower() noexcept + { + std::transform(m_string.begin(), m_string.end(), m_string.begin(), + [](unsigned char c) { return std::tolower(c); }); + } + + void Capitalize() noexcept + { + if (!m_string.empty()) + { + m_string[0] = std::toupper(m_string[0]); + } + } + + template>> T FromString() const + { + T value; + c4::csubstr str = c4::to_csubstr(m_string.c_str()); + c4::from_chars(str, &value); + return value; + } + + // iterators + std::string::iterator begin() { return m_string.begin(); } + std::string::iterator end() { return m_string.end(); } + std::string::const_iterator cbegin() const { return m_string.cbegin(); } + std::string::const_iterator cend() const { return m_string.cend(); } + std::string::reverse_iterator rbegin() { return m_string.rbegin(); } + std::string::reverse_iterator rend() { return m_string.rend(); } + std::reverse_iterator crbegin() const { return m_string.crbegin(); } + std::reverse_iterator crend() const { return m_string.crend(); } + + private: + std::string m_string; + }; + + template String operator+(const T& lhs, const String& rhs) noexcept { return String(lhs) + rhs; } +} diff --git a/openVulkanoCpp/Host/GraphicsAppManager.cpp b/openVulkanoCpp/Host/GraphicsAppManager.cpp index 918a361..e49123b 100644 --- a/openVulkanoCpp/Host/GraphicsAppManager.cpp +++ b/openVulkanoCpp/Host/GraphicsAppManager.cpp @@ -87,7 +87,7 @@ namespace OpenVulkano void GraphicsAppManager::UpdateCappedFpsInfo(int32_t newFpsCap) { - if (newFpsCap < 0) + if (newFpsCap <= 0) { cappedFrameTime = std::chrono::microseconds(0); } diff --git a/tests/StringTest.cpp b/tests/StringTest.cpp new file mode 100644 index 0000000..5f2d7c3 --- /dev/null +++ b/tests/StringTest.cpp @@ -0,0 +1,281 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ + +#include + +#include "Data/Containers/String.hpp" + +using namespace OpenVulkano; + +TEST_CASE("Constructors") +{ + String str1; + REQUIRE(str1 == ""); + + String str2("Hello"); + REQUIRE(str2 == "Hello"); + + String str3(std::string("World")); + REQUIRE(str3 == "World"); + + String str4(str2); + REQUIRE(str4 == "Hello"); + + String str5(std::move(str3)); + REQUIRE(str5 == "World"); +} + +TEST_CASE("Assignment") +{ + String str1; + str1 = "Hello"; + REQUIRE(str1 == "Hello"); + + String str2; + str2 = std::string("World"); + REQUIRE(str2 == "World"); + + String str3; + str3 = str1; + REQUIRE(str3 == "Hello"); + + String str4; + str4 = std::move(str2); + REQUIRE(str4 == "World"); +} + +TEST_CASE("Concatenation") +{ + String str1("Hello"); + str1 += " World"; + REQUIRE(str1 == "Hello World"); + + String str2("Hello"); + String str3 = str2 + " World"; + REQUIRE(str3 == "Hello World"); + + String str4("Hello"); + str4 += std::string(" World"); + REQUIRE(str4 == "Hello World"); + + String str5("Hello"); + String str6 = str5 + std::string(" World"); + REQUIRE(str6 == "Hello World"); + + String str7("Hello"); + str7 += String(" World"); + REQUIRE(str7 == "Hello World"); + + String str8("Hello"); + String str9 = str8 + String(" World"); + REQUIRE(str9 == "Hello World"); + + String str10 = String("Hello") + " World"; + REQUIRE(str10 == "Hello World"); + + String str11 = String("Hello") + std::string(" World"); + REQUIRE(str11 == "Hello World"); + + String str12 = std::string("Hello") + String(" World"); + REQUIRE(str12 == "Hello World"); +} + +TEST_CASE("Comparison") +{ + String str1("Hello"); + REQUIRE(str1 == "Hello"); + REQUIRE(str1 != "World"); + + String str2("World"); + REQUIRE(str2 == "World"); + REQUIRE(str2 != "Hello"); +} + +TEST_CASE("Trim") +{ + String str1(" Hello World "); + REQUIRE(str1.Trim() == "Hello World"); +} + +TEST_CASE("FindIndex") +{ + String str1("Georg will save us all from our doom"); + REQUIRE(str1.FindStartIndexOf("save") == 11); +} + +TEST_CASE("Upper/Lower") +{ + String str1("Hello World"); + str1.ToUpper(); + REQUIRE(str1 == "HELLO WORLD"); + + String str2("Hello World"); + str2.ToLower(); + REQUIRE(str2 == "hello world"); +} + +TEST_CASE("Substring") +{ + String str1("Hello World"); + REQUIRE(str1.SubString(0, 5) == "Hello"); + REQUIRE(str1.SubString(6, 11) == "World"); +} + +TEST_CASE("FromString") +{ + String str1("42"); + REQUIRE(str1.FromString() == 42); + + String str2("42.42"); + REQUIRE(str2.FromString() == 42.42f); +} + +TEST_CASE("StartsWith") +{ + String str1("Hello World"); + REQUIRE(str1.StartsWith("Hello")); + REQUIRE(!str1.StartsWith("World")); +} + +TEST_CASE("EndsWith") +{ + String str1("Hello World"); + REQUIRE(str1.EndsWith("World")); + REQUIRE(!str1.EndsWith("Hello")); +} + +TEST_CASE("Contains") +{ + String str1("Hello World"); + REQUIRE(str1.Contains("Hello")); + REQUIRE(str1.Contains("World")); + REQUIRE(!str1.Contains("Georg")); +} + +TEST_CASE("FindEndIndexOf") +{ + String str1("Georg will save us all from our doom"); + REQUIRE(str1.FindEndIndexOf("save") == 11); +} + +TEST_CASE("TrimFront") +{ + String str1(" Hello World"); + REQUIRE(str1.TrimFront() == "Hello World"); +} + +TEST_CASE("TrimBack") +{ + String str1("Hello World "); + REQUIRE(str1.TrimBack() == "Hello World"); +} + +TEST_CASE("Empty") +{ + String str1; + REQUIRE(str1.Empty()); + REQUIRE(!str1); + + String str2("Hello World"); + REQUIRE(!str2.Empty()); + REQUIRE(!!str2); +} + +TEST_CASE("Size") +{ + String str1("Hello World"); + REQUIRE(str1.Size() == 11); +} + +TEST_CASE("Capacity") +{ + String str1("Hello World"); + REQUIRE(str1.Capacity() >= 11); +} + +TEST_CASE("CharCount") +{ + String str1("Hello World"); + REQUIRE(str1.CharCount() == 11); +} + +TEST_CASE("PopBack") +{ + String str1("Hello World"); + str1.PopBack(); + REQUIRE(str1 == "Hello Worl"); +} + +TEST_CASE("Clear") +{ + String str1("Hello World"); + str1.Clear(); + REQUIRE(str1.Empty()); +} + +TEST_CASE("Split") +{ + String str1("Hello,World"); + auto split = str1.Split(","); + REQUIRE(split.size() == 2); + REQUIRE(split[0] == "Hello"); + REQUIRE(split[1] == "World"); +} + +TEST_CASE("SplitAtLastOccurrence") +{ + String str1 = "Hello,World,Georg"; + auto split = str1.SplitAtLastOccurrence(","); + REQUIRE(split.first == "Hello,World"); + REQUIRE(split.second == "Georg"); +} + +TEST_CASE("SplitAtFirstOccurrence") +{ + String str1 = "Hello,World,Georg"; + auto split = str1.SplitAtFirstOccurrence(","); + REQUIRE(split.first == "Hello"); + REQUIRE(split.second == "World,Georg"); +} + +TEST_CASE("SplitAsStringViews") +{ + String str1("Hello,World"); + auto split = str1.SplitAsStringViews(","); + REQUIRE(split.size() == 2); + REQUIRE(split[0] == "Hello"); + REQUIRE(split[1] == "World"); +} + +TEST_CASE("SplitAtFirstOccurrenceAsStringViews") +{ + String str1 = "Hello,World,Georg"; + auto split = str1.SplitAtFirstOccurenceAsStringViews(","); + REQUIRE(split.first == "Hello"); + REQUIRE(split.second == "World,Georg"); +} + +TEST_CASE("SplitAtLastOccurenceAsStringViews") +{ + String str1 = "Hello,World,Georg"; + auto split = str1.SplitAtLastOccurenceAsStringViews(","); + REQUIRE(split.first == "Hello,World"); + REQUIRE(split.second == "Georg"); +} + +TEST_CASE("OctToInt") +{ + String str1("47"); + REQUIRE(str1.OctToInt() == 39); + REQUIRE(String::OctToInt("552") == 362); +} + +TEST_CASE("HexToInt") +{ + String str1("2A"); + REQUIRE(str1.HexToInt() == 42); + REQUIRE(String::HexToInt("FF") == 255); +}