From 7fe47a9d5b1727a61dc724523b530762f6d6ba19 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Thu, 30 Jun 2022 21:38:53 -0700 Subject: Restructure project --- app/source/Cplt/Utils/Color.hpp | 202 +++++++++++++ app/source/Cplt/Utils/Hash.hpp | 15 + app/source/Cplt/Utils/I18n.hpp | 10 + app/source/Cplt/Utils/IO/Archive.cpp | 57 ++++ app/source/Cplt/Utils/IO/Archive.hpp | 18 ++ app/source/Cplt/Utils/IO/CstdioFile.cpp | 36 +++ app/source/Cplt/Utils/IO/CstdioFile.hpp | 17 ++ app/source/Cplt/Utils/IO/DataStream.cpp | 283 ++++++++++++++++++ app/source/Cplt/Utils/IO/DataStream.hpp | 210 +++++++++++++ app/source/Cplt/Utils/IO/FileStream.cpp | 7 + app/source/Cplt/Utils/IO/FileStream.hpp | 97 ++++++ app/source/Cplt/Utils/IO/FileStream_Cstdio.inl | 126 ++++++++ app/source/Cplt/Utils/IO/FileStream_Custom.inl | 358 +++++++++++++++++++++++ app/source/Cplt/Utils/IO/Helper.hpp | 43 +++ app/source/Cplt/Utils/IO/StringIntegration.hpp | 37 +++ app/source/Cplt/Utils/IO/TslArrayIntegration.hpp | 50 ++++ app/source/Cplt/Utils/IO/TslRobinIntegration.hpp | 78 +++++ app/source/Cplt/Utils/IO/UuidIntegration.hpp | 27 ++ app/source/Cplt/Utils/IO/VectorIntegration.hpp | 42 +++ app/source/Cplt/Utils/IO/fwd.hpp | 13 + app/source/Cplt/Utils/Macros.hpp | 13 + app/source/Cplt/Utils/Math.hpp | 11 + app/source/Cplt/Utils/RTTI.hpp | 49 ++++ app/source/Cplt/Utils/ScopeGuard.hpp | 39 +++ app/source/Cplt/Utils/Sigslot.cpp | 233 +++++++++++++++ app/source/Cplt/Utils/Sigslot.hpp | 165 +++++++++++ app/source/Cplt/Utils/Size.hpp | 65 ++++ app/source/Cplt/Utils/StandardDirectories.cpp | 78 +++++ app/source/Cplt/Utils/StandardDirectories.hpp | 10 + app/source/Cplt/Utils/Time.cpp | 29 ++ app/source/Cplt/Utils/Time.hpp | 11 + app/source/Cplt/Utils/UUID.hpp | 5 + app/source/Cplt/Utils/Variant.hpp | 33 +++ app/source/Cplt/Utils/Vector.hpp | 144 +++++++++ app/source/Cplt/Utils/VectorHash.hpp | 46 +++ app/source/Cplt/Utils/fwd.hpp | 17 ++ 36 files changed, 2674 insertions(+) create mode 100644 app/source/Cplt/Utils/Color.hpp create mode 100644 app/source/Cplt/Utils/Hash.hpp create mode 100644 app/source/Cplt/Utils/I18n.hpp create mode 100644 app/source/Cplt/Utils/IO/Archive.cpp create mode 100644 app/source/Cplt/Utils/IO/Archive.hpp create mode 100644 app/source/Cplt/Utils/IO/CstdioFile.cpp create mode 100644 app/source/Cplt/Utils/IO/CstdioFile.hpp create mode 100644 app/source/Cplt/Utils/IO/DataStream.cpp create mode 100644 app/source/Cplt/Utils/IO/DataStream.hpp create mode 100644 app/source/Cplt/Utils/IO/FileStream.cpp create mode 100644 app/source/Cplt/Utils/IO/FileStream.hpp create mode 100644 app/source/Cplt/Utils/IO/FileStream_Cstdio.inl create mode 100644 app/source/Cplt/Utils/IO/FileStream_Custom.inl create mode 100644 app/source/Cplt/Utils/IO/Helper.hpp create mode 100644 app/source/Cplt/Utils/IO/StringIntegration.hpp create mode 100644 app/source/Cplt/Utils/IO/TslArrayIntegration.hpp create mode 100644 app/source/Cplt/Utils/IO/TslRobinIntegration.hpp create mode 100644 app/source/Cplt/Utils/IO/UuidIntegration.hpp create mode 100644 app/source/Cplt/Utils/IO/VectorIntegration.hpp create mode 100644 app/source/Cplt/Utils/IO/fwd.hpp create mode 100644 app/source/Cplt/Utils/Macros.hpp create mode 100644 app/source/Cplt/Utils/Math.hpp create mode 100644 app/source/Cplt/Utils/RTTI.hpp create mode 100644 app/source/Cplt/Utils/ScopeGuard.hpp create mode 100644 app/source/Cplt/Utils/Sigslot.cpp create mode 100644 app/source/Cplt/Utils/Sigslot.hpp create mode 100644 app/source/Cplt/Utils/Size.hpp create mode 100644 app/source/Cplt/Utils/StandardDirectories.cpp create mode 100644 app/source/Cplt/Utils/StandardDirectories.hpp create mode 100644 app/source/Cplt/Utils/Time.cpp create mode 100644 app/source/Cplt/Utils/Time.hpp create mode 100644 app/source/Cplt/Utils/UUID.hpp create mode 100644 app/source/Cplt/Utils/Variant.hpp create mode 100644 app/source/Cplt/Utils/Vector.hpp create mode 100644 app/source/Cplt/Utils/VectorHash.hpp create mode 100644 app/source/Cplt/Utils/fwd.hpp (limited to 'app/source/Cplt/Utils') diff --git a/app/source/Cplt/Utils/Color.hpp b/app/source/Cplt/Utils/Color.hpp new file mode 100644 index 0000000..15fe6a1 --- /dev/null +++ b/app/source/Cplt/Utils/Color.hpp @@ -0,0 +1,202 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +class RgbaColor +{ +public: + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + +public: + constexpr RgbaColor() noexcept + : r{ 255 } + , g{ 255 } + , b{ 255 } + , a{ 255 } + { + } + + constexpr RgbaColor(float r, float g, float b, float a = 1.0f) noexcept + : r{ static_cast(r * 255.0f) } + , g{ static_cast(g * 255.0f) } + , b{ static_cast(b * 255.0f) } + , a{ static_cast(a * 255.0f) } + { + } + + constexpr RgbaColor(int r, int g, int b, int a = 255) noexcept + : r{ static_cast(r & 0xFF) } + , g{ static_cast(g & 0xFF) } + , b{ static_cast(b & 0xFF) } + , a{ static_cast(a & 0xFF) } + { + } + + constexpr RgbaColor(uint32_t rgba) noexcept + : r{ static_cast((rgba >> 0) & 0xFF) } + , g{ static_cast((rgba >> 8) & 0xFF) } + , b{ static_cast((rgba >> 16) & 0xFF) } + , a{ static_cast((rgba >> 24) & 0xFF) } + { + } + + constexpr uint32_t GetScalar() const noexcept + { + uint32_t res = 0; + res |= r << 24; + res |= g << 16; + res |= b << 8; + res |= a; + return res; + } + + constexpr void SetScalar(uint32_t scalar) noexcept + { + r = (scalar >> 0) & 0xFF; + g = (scalar >> 8) & 0xFF; + b = (scalar >> 16) & 0xFF; + a = (scalar >> 24) & 0xFF; + } + + constexpr float GetNormalizedRed() const noexcept + { + return r / 255.0f; + } + + constexpr float GetNormalizedGreen() const noexcept + { + return g / 255.0f; + } + + constexpr float GetNormalizedBlue() const noexcept + { + return b / 255.0f; + } + + constexpr float GetNormalizedAlpha() const noexcept + { + return a / 255.0f; + } + + constexpr Vec4i AsVec4i() const noexcept + { + return Vec4i{ r, g, b, a }; + } + + constexpr Vec4f AsVec4f() const noexcept + { + return Vec4f{ + GetNormalizedRed(), + GetNormalizedGreen(), + GetNormalizedBlue(), + GetNormalizedAlpha(), + }; + } + + ImVec4 AsImVec() const + { + auto v = AsVec4f(); + return ImVec4{ v.x, v.y, v.z, v.w }; + } + + ImColor AsImColor() const + { + auto v = AsVec4f(); + return ImColor{ v.x, v.y, v.z, v.w }; + } + + ImU32 AsImU32() const + { + ImU32 res; + res |= r << IM_COL32_R_SHIFT; + res |= g << IM_COL32_G_SHIFT; + res |= b << IM_COL32_B_SHIFT; + res |= a << IM_COL32_A_SHIFT; + return res; + } + + constexpr void SetVec(const Vec4f& vec) noexcept + { + r = (uint8_t)(vec.x * 255.0f); + g = (uint8_t)(vec.y * 255.0f); + b = (uint8_t)(vec.z * 255.0f); + a = (uint8_t)(vec.w * 255.0f); + } + + // Forward declaring because cyclic reference between RgbaColor and HsvColor + constexpr HsvColor ToHsv() const noexcept; + + friend constexpr bool operator==(const RgbaColor&, const RgbaColor&) noexcept = default; +}; + +class HsvColor +{ +public: + float h; + float s; + float v; + float a; + +public: + constexpr HsvColor() noexcept + : h{ 0.0f } + , s{ 0.0f } + , v{ 1.0f } + , a{ 1.0f } + { + } + + constexpr HsvColor(float h, float s, float v, float a) noexcept + : h{ h } + , s{ s } + , v{ v } + , a{ a } + { + } + + // Forward declaring because cyclic reference between RgbaColor and HsvColor + constexpr RgbaColor ToRgba() const noexcept; +}; + +constexpr HsvColor RgbaColor::ToHsv() const noexcept +{ + float fr = GetNormalizedRed(); + float fg = GetNormalizedBlue(); + float fb = GetNormalizedGreen(); + float fa = GetNormalizedAlpha(); + + auto p = fg < fb ? Vec4f{ fb, fg, -1, 2.0f / 3.0f } : Vec4f{ fg, fb, 0, -1.0f / 3.0f }; + auto q = fr < p.x ? Vec4f{ p.x, p.y, p.w, fr } : Vec4f{ fr, p.y, p.z, p.x }; + float c = q.x - std::min(q.w, q.y); + float h = MathUtils::Abs((q.w - q.y) / (6 * c + std::numeric_limits::epsilon()) + q.z); + + Vec3f hcv{ h, c, q.x }; + float s = hcv.y / (hcv.z + std::numeric_limits::epsilon()); + return HsvColor(hcv.x, s, hcv.z, fa); +} + +constexpr RgbaColor HsvColor::ToRgba() const noexcept +{ + float r = MathUtils::Abs(h * 6 - 3) - 1; + float g = 2 - MathUtils::Abs(h * 6 - 2); + float b = 2 - MathUtils::Abs(h * 6 - 4); + + auto rgb = Vec3f{ + std::clamp(r, 0.0f, 1.0f), + std::clamp(g, 0.0f, 1.0f), + std::clamp(b, 0.0f, 1.0f), + }; + auto vc = (rgb - Vec3f{ 0, 0, 0 }) * s + Vec3f{ 1, 1, 1 } * v; + + return RgbaColor(vc.x, vc.y, vc.z, a); +} diff --git a/app/source/Cplt/Utils/Hash.hpp b/app/source/Cplt/Utils/Hash.hpp new file mode 100644 index 0000000..cf7713a --- /dev/null +++ b/app/source/Cplt/Utils/Hash.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace HashUtils { + +template +void Combine(size_t& seed, const T& v) +{ + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +} // namespace HashUtils diff --git a/app/source/Cplt/Utils/I18n.hpp b/app/source/Cplt/Utils/I18n.hpp new file mode 100644 index 0000000..895856a --- /dev/null +++ b/app/source/Cplt/Utils/I18n.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +#if !defined(TARGET_LOCALE) +# define I18N_TEXT(defaultText, name) defaultText +#else +# include TARGET_LOCALE_FILE +# define I18N_TEXT(defaultText, name) name +#endif diff --git a/app/source/Cplt/Utils/IO/Archive.cpp b/app/source/Cplt/Utils/IO/Archive.cpp new file mode 100644 index 0000000..f6e7b27 --- /dev/null +++ b/app/source/Cplt/Utils/IO/Archive.cpp @@ -0,0 +1,57 @@ +#include "Archive.hpp" + +constexpr uint8_t kMagicNumbers[] = { 0x98, 0xd8, 0xa4, 0x65, 0x18, 0xa2, 0xd6, 0xa0 }; +constexpr size_t kMagicNumberCount = std::size(kMagicNumbers); + +constexpr uint8_t kByteOrderMark = []() { + switch (std::endian::native) { + case std::endian::little: return 0; + case std::endian::big: return 1; + } +}(); + +std::span DataArchive::GetMagicNumbers() +{ + return std::span{ kMagicNumbers }; +} + +std::optional DataArchive::LoadFile(const std::filesystem::path& path) +{ + InputFileStream fileStream(path); + fileStream.SetReadInSize(1024); + InputDataStream stream(std::move(fileStream)); + + uint8_t magicNumbers[kMagicNumberCount]; + stream.ReadBytes(kMagicNumberCount, magicNumbers); + + for (size_t i = 0; i < kMagicNumberCount; ++i) { + if (magicNumbers[i] != kMagicNumbers[i]) { + return {}; + } + } + + uint8_t byteOrderMark; + stream.Read(byteOrderMark); + + switch (byteOrderMark) { + case 0: stream.SetEndianness(std::endian::little); break; + case 1: stream.SetEndianness(std::endian::big); break; + default: std::abort(); + } + + return stream; +} + +std::optional DataArchive::SaveFile(const std::filesystem::path& path) +{ + OutputFileStream fileStream(path, OutputFileStream::TruncateFile); + fileStream.SetMaxBufferSize(1024); + OutputDataStream stream(std::move(fileStream)); + + stream.WriteBytes(kMagicNumberCount, kMagicNumbers); + stream.Write(kByteOrderMark); + + stream.SetEndianness(std::endian::native); + + return stream; +} diff --git a/app/source/Cplt/Utils/IO/Archive.hpp b/app/source/Cplt/Utils/IO/Archive.hpp new file mode 100644 index 0000000..7632f3b --- /dev/null +++ b/app/source/Cplt/Utils/IO/Archive.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include +#include +#include +#include + +class DataArchive +{ +public: + static std::span GetMagicNumbers(); + + // TODO more complete impl + static std::optional LoadFile(const std::filesystem::path& path); + static std::optional SaveFile(const std::filesystem::path& path); +}; diff --git a/app/source/Cplt/Utils/IO/CstdioFile.cpp b/app/source/Cplt/Utils/IO/CstdioFile.cpp new file mode 100644 index 0000000..c414dfb --- /dev/null +++ b/app/source/Cplt/Utils/IO/CstdioFile.cpp @@ -0,0 +1,36 @@ +#include "CstdioFile.hpp" + +#include + +#pragma push_macro("MODE_STRING") +#undef MODE_STRING + +#if defined(_WIN32) +# define MODE_STRING(x) L##x +#else +# define MODE_STRING(x) x +#endif + +namespace CPLT_UNITY_ID { +auto GetModeString(FileUtils::IoMode mode) +{ + switch (mode) { + case FileUtils::IM_Read: return MODE_STRING("rb"); + case FileUtils::IM_WriteAppend: return MODE_STRING("ab"); + case FileUtils::IM_WriteTruncate: return MODE_STRING("wb"); + } + return MODE_STRING(""); +} +} // namespace CPLT_UNITY_ID + +#pragma pop_macro("MODE_STRING") + +FILE* FileUtils::OpenCstdioFile(const std::filesystem::path& path, IoMode mode) +{ +#ifdef _WIN32 + // std::filesystem::path::c_str() returns `const wchar_t*` under Windows, because NT uses UTF-16 natively + return _wfopen(path.c_str(), ::GetModeString(mode)); +#else + return fopen(path.c_str(), CPLT_UNITY_ID::GetModeString(mode)); +#endif +} diff --git a/app/source/Cplt/Utils/IO/CstdioFile.hpp b/app/source/Cplt/Utils/IO/CstdioFile.hpp new file mode 100644 index 0000000..e863dd5 --- /dev/null +++ b/app/source/Cplt/Utils/IO/CstdioFile.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace FileUtils { + +enum IoMode +{ + IM_Read, + IM_WriteAppend, + IM_WriteTruncate, +}; + +FILE* OpenCstdioFile(const std::filesystem::path& path, IoMode mode); + +} // namespace FileUtils diff --git a/app/source/Cplt/Utils/IO/DataStream.cpp b/app/source/Cplt/Utils/IO/DataStream.cpp new file mode 100644 index 0000000..c0797e3 --- /dev/null +++ b/app/source/Cplt/Utils/IO/DataStream.cpp @@ -0,0 +1,283 @@ +#include "DataStream.hpp" + +#include +#include +#include + +static_assert(std::numeric_limits::is_iec559, "Non IEE754/IEC559 'float' is not supported."); +static_assert(std::numeric_limits::is_iec559, "Non IEE754/IEC559 'double' is not supported."); + +static_assert(std::endian::native == std::endian::little || std::endian::native == std::endian::big, "Mixed endian is not supported."); + +// Reading/writing signed integer byte-by-byte is fine, since the representation got fixed to 2's complements in C++20 + +static uint16_t ByteSwap(uint16_t n) +{ + auto bytes = reinterpret_cast(n); + std::swap(bytes[0], bytes[1]); + return n; +} + +static uint32_t ByteSwap(uint32_t n) +{ +#ifdef _MSC_VER + return _byteswap_ulong(n); +#else + return __builtin_bswap32(n); +#endif +} + +static uint64_t ByteSwap(uint64_t n) +{ +#ifdef _MSC_VER + return _byteswap_uint64(n); +#else + return __builtin_bswap64(n); +#endif +} + +template +static TSigned ByteSwap(TSigned n) +{ + using Unsigned = std::make_unsigned_t; + + auto swapped = ::ByteSwap(std::bit_cast(n)); + return std::bit_cast(swapped); +} + +std::endian BaseDataStream::GetEndianness() const +{ + return mEndian; +} + +void BaseDataStream::SetEndianness(std::endian endianness) +{ + mEndian = endianness; +} + +InputDataStream::InputDataStream(InputFileStream stream) + : mBackend{ std::move(stream) } +{ +} + +void InputDataStream::ReadBytes(size_t byteCount, std::byte* buffer) +{ + mBackend.ReadBytes(static_cast(byteCount), reinterpret_cast(buffer)); +} + +void InputDataStream::ReadBytes(size_t byteCount, char* buffer) +{ + mBackend.ReadBytes(static_cast(byteCount), reinterpret_cast(buffer)); +} + +void InputDataStream::ReadBytes(size_t byteCount, signed char* buffer) +{ + mBackend.ReadBytes(static_cast(byteCount), reinterpret_cast(buffer)); +} + +void InputDataStream::ReadBytes(size_t byteCount, unsigned char* buffer) +{ + mBackend.ReadBytes(static_cast(byteCount), reinterpret_cast(buffer)); +} + +void InputDataStream::Read(int8_t& n) +{ + // sizeof() of a reference type yields the size of the reference + mBackend.ReadBytes(sizeof(n), reinterpret_cast(&n)); +} + +void InputDataStream::Read(int16_t& n) +{ + int16_t tmp; + mBackend.ReadBytes(sizeof(tmp), reinterpret_cast(&tmp)); + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(tmp); + } else { + n = tmp; + } +} + +void InputDataStream::Read(int32_t& n) +{ + int32_t tmp; + mBackend.ReadBytes(sizeof(tmp), reinterpret_cast(&tmp)); + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(tmp); + } else { + n = tmp; + } +} + +void InputDataStream::Read(int64_t& n) +{ + int64_t tmp; + mBackend.ReadBytes(sizeof(tmp), reinterpret_cast(&tmp)); + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(tmp); + } else { + n = tmp; + } +} + +void InputDataStream::Read(uint8_t& n) +{ + mBackend.ReadBytes(sizeof(n), reinterpret_cast(&n)); +} + +void InputDataStream::Read(uint16_t& n) +{ + uint16_t tmp; + mBackend.ReadBytes(sizeof(tmp), reinterpret_cast(&tmp)); + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(tmp); + } else { + n = tmp; + } +} + +void InputDataStream::Read(uint32_t& n) +{ + uint32_t tmp; + mBackend.ReadBytes(sizeof(tmp), reinterpret_cast(&tmp)); + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(tmp); + } else { + n = tmp; + } +} + +void InputDataStream::Read(uint64_t& n) +{ + uint64_t tmp; + mBackend.ReadBytes(sizeof(tmp), reinterpret_cast(&tmp)); + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(tmp); + } else { + n = tmp; + } +} + +void InputDataStream::Read(float& n) +{ + uint32_t buffer; + Read(buffer); + + if (GetEndianness() != std::endian::native) { + buffer = ::ByteSwap(buffer); + } + + n = std::bit_cast(buffer); +} + +void InputDataStream::Read(double& n) +{ + uint64_t buffer; + Read(buffer); + + if (GetEndianness() != std::endian::native) { + buffer = ::ByteSwap(buffer); + } + + n = std::bit_cast(buffer); +} + +OutputDataStream::OutputDataStream(OutputFileStream stream) + : mBackend{ std::move(stream) } +{ +} + +void OutputDataStream::WriteBytes(size_t byteCount, const std::byte* buffer) +{ + mBackend.WriteBytes(static_cast(byteCount), reinterpret_cast(buffer)); +} + +void OutputDataStream::WriteBytes(size_t byteCount, const char* buffer) +{ + mBackend.WriteBytes(static_cast(byteCount), reinterpret_cast(buffer)); +} + +void OutputDataStream::WriteBytes(size_t byteCount, const signed char* buffer) +{ + mBackend.WriteBytes(static_cast(byteCount), reinterpret_cast(buffer)); +} + +void OutputDataStream::WriteBytes(size_t byteCount, const unsigned char* buffer) +{ + mBackend.WriteBytes(static_cast(byteCount), reinterpret_cast(buffer)); +} + +void OutputDataStream::Write(int8_t n) +{ + mBackend.WriteBytes(sizeof(n), reinterpret_cast(&n)); +} + +void OutputDataStream::Write(int16_t n) +{ + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(n); + } + mBackend.WriteBytes(sizeof(n), reinterpret_cast(&n)); +} + +void OutputDataStream::Write(int32_t n) +{ + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(n); + } + mBackend.WriteBytes(sizeof(n), reinterpret_cast(&n)); +} + +void OutputDataStream::Write(int64_t n) +{ + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(n); + } + mBackend.WriteBytes(sizeof(n), reinterpret_cast(&n)); +} + +void OutputDataStream::Write(uint8_t n) +{ + mBackend.WriteBytes(sizeof(n), reinterpret_cast(&n)); +} + +void OutputDataStream::Write(uint16_t n) +{ + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(n); + } + mBackend.WriteBytes(sizeof(n), reinterpret_cast(&n)); +} + +void OutputDataStream::Write(uint32_t n) +{ + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(n); + } + mBackend.WriteBytes(sizeof(n), reinterpret_cast(&n)); +} + +void OutputDataStream::Write(uint64_t n) +{ + if (GetEndianness() != std::endian::native) { + n = ::ByteSwap(n); + } + mBackend.WriteBytes(sizeof(n), reinterpret_cast(&n)); +} + +void OutputDataStream::Write(float n) +{ + auto buffer = std::bit_cast(n); + if (GetEndianness() != std::endian::native) { + buffer = ::ByteSwap(buffer); + } + mBackend.WriteBytes(sizeof(buffer), reinterpret_cast(&buffer)); +} + +void OutputDataStream::Write(double n) +{ + auto buffer = std::bit_cast(n); + if (GetEndianness() != std::endian::native) { + buffer = ::ByteSwap(buffer); + } + mBackend.WriteBytes(sizeof(buffer), reinterpret_cast(&buffer)); +} diff --git a/app/source/Cplt/Utils/IO/DataStream.hpp b/app/source/Cplt/Utils/IO/DataStream.hpp new file mode 100644 index 0000000..133adc2 --- /dev/null +++ b/app/source/Cplt/Utils/IO/DataStream.hpp @@ -0,0 +1,210 @@ +#pragma once + +#include "FileStream.hpp" +#include + +#include +#include +#include +#include + +class BaseDataStream +{ +private: + std::endian mEndian = std::endian::big; + +public: + std::endian GetEndianness() const; + void SetEndianness(std::endian endianness); +}; + +class InputDataStream : public BaseDataStream +{ +private: + InputFileStream mBackend; + +public: + static constexpr bool IsSerializer() + { + return false; + } + + InputDataStream(InputFileStream stream); + + void ReadBytes(size_t byteCount, std::byte* buffer); + void ReadBytes(size_t byteCount, char* buffer); + void ReadBytes(size_t byteCount, signed char* buffer); + void ReadBytes(size_t byteCount, unsigned char* buffer); + + template + void ReadBytes(size_t byteCount, TInserter&& inserter) + { + for (size_t i = 0; i < byteCount; ++i) { + uint8_t byte; + Read(byte); + + inserter = byte; + } + } + + void Read(int8_t& n); + void Read(int16_t& n); + void Read(int32_t& n); + void Read(int64_t& n); + + void Read(uint8_t& n); + void Read(uint16_t& n); + void Read(uint32_t& n); + void Read(uint64_t& n); + + void Read(float& n); + void Read(double& n); + + template + requires std::is_enum_v + void ReadEnum(TEnum& e) + { + std::underlying_type_t n; + Read(n); + e = static_cast(e); + } + + template + void ReadObject(TObject& obj) + { + obj.ReadFromDataStream(*this); + } + + template + void ReadObjectAdapted(TObject& obj) + { + TAdapter::ReadFromDataStream(*this, obj); + } + +public: + // Proxy functions for writing templated IO functions + + template + void Bytes(size_t byteCount, T* buffer) + { + ReadBytes(byteCount, buffer); + } + + template + void Value(T& t) + { + Read(t); + } + + template + void Enum(T& t) + { + ReadEnum(t); + } + + template + void Object(T& obj) + { + ReadObject(obj); + } + + template + void ObjectAdapted(TObject& obj) + { + ReadObjectAdapted(obj); + } +}; + +class OutputDataStream : public BaseDataStream +{ +private: + OutputFileStream mBackend; + +public: + static constexpr bool IsSerializer() + { + return true; + } + + OutputDataStream(OutputFileStream stream); + + void WriteBytes(size_t byteCount, const std::byte* buffer); + void WriteBytes(size_t byteCount, const char* buffer); + void WriteBytes(size_t byteCount, const signed char* buffer); + void WriteBytes(size_t byteCount, const unsigned char* buffer); + + template + void WriteBytes(TIterator&& begin, TIterator&& end) + { + for (; begin != end; ++begin) { + uint8_t byte = *begin; + Write(byte); + } + } + + void Write(int8_t n); + void Write(int16_t n); + void Write(int32_t n); + void Write(int64_t n); + + void Write(uint8_t n); + void Write(uint16_t n); + void Write(uint32_t n); + void Write(uint64_t n); + + void Write(float n); + void Write(double n); + + template + requires std::is_enum_v + void WriteEnum(TEnum e) + { + auto n = static_cast>(e); + Write(n); + } + + template + void WriteObject(const TObject& obj) + { + obj.WriteToDataStream(*this); + } + + template + void WriteObjectAdapted(const TObject& obj) + { + TAdapter::WriteToDataStream(*this, obj); + } + +public: + // Proxy functions for writing templated IO functions + + template + void Bytes(size_t byteCount, T* buffer) + { + WriteBytes(byteCount, buffer); + } + + template + void Value(T t) + { + Write(t); + } + + template + void Enum(T t) + { + WriteEnum(t); + } + + template + void Object(T& obj) + { + WriteObject(obj); + } + + template + void ObjectAdapted(TObject& obj) + { + WriteObjectAdapted(obj); + } +}; diff --git a/app/source/Cplt/Utils/IO/FileStream.cpp b/app/source/Cplt/Utils/IO/FileStream.cpp new file mode 100644 index 0000000..8b83712 --- /dev/null +++ b/app/source/Cplt/Utils/IO/FileStream.cpp @@ -0,0 +1,7 @@ +#include "FileStream.hpp" + +#if defined(CPLT_FILESTREAM_USE_CSTDIO) +# include "FileStream_Cstdio.inl" +#else +# include "FileStream_Custom.inl" +#endif diff --git a/app/source/Cplt/Utils/IO/FileStream.hpp b/app/source/Cplt/Utils/IO/FileStream.hpp new file mode 100644 index 0000000..453ddbe --- /dev/null +++ b/app/source/Cplt/Utils/IO/FileStream.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include + +// TODO switch to custom when unit tests are ready and bugs are fixed +#define CPLT_FILESTREAM_USE_CSTDIO + +struct IoResult +{ + enum ErrorKind + { + ERR_None, + ERR_PermissionDenied, + ERR_UnexpectedEof, + ERR_Unsupported, + ERR_OutOfSpace, + ERR_Other, + }; + + ErrorKind Error; + uint32_t SystemError; + size_t BytesMoved; +}; + +class InputFileStream +{ +private: +#if defined(CPLT_FILESTREAM_USE_CSTDIO) + FILE* mFile; +#else + alignas(void*) char mOsFileHandle[sizeof(void*)]; + + // mBuffer is always mReadInSize size + std::unique_ptr mBuffer; + int mReadInSize = 1024; + + int mFirstByteIdx = 0; + int mAvailableBytes = 0; + + bool mEof = false; +#endif + +public: + InputFileStream(const std::filesystem::path& path); + ~InputFileStream(); + + InputFileStream(const InputFileStream&) = delete; + InputFileStream& operator=(const InputFileStream&) = delete; + InputFileStream(InputFileStream&&); + InputFileStream& operator=(InputFileStream&&); + + int GetReadInSize() const; + void SetReadInSize(int size); + + bool IsEof() const; + + IoResult ReadBytes(size_t bufferLength, std::byte* buffer); +}; + +class OutputFileStream +{ +public: + enum WriteMode + { + AppendFile, + TruncateFile, + }; + +private: +#if defined(CPLT_FILESTREAM_USE_CSTDIO) + FILE* mFile; +#else + alignas(void*) char mOsFileHandle[sizeof(void*)]; + std::unique_ptr mBuffer; + int mMaxBufferSize = 1024; + int mCurrentBufferSize = 0; +#endif + +public: + OutputFileStream(const std::filesystem::path& path, WriteMode mode); + ~OutputFileStream(); + + OutputFileStream(const OutputFileStream&) = delete; + OutputFileStream& operator=(const OutputFileStream&) = delete; + OutputFileStream(OutputFileStream&&); + OutputFileStream& operator=(OutputFileStream&&); + + int GetMaxBufferSize() const; + void SetMaxBufferSize(int maxSize); + + IoResult WriteBytes(size_t bufferLength, const std::byte* buffer); + + void FlushBuffer(); +}; diff --git a/app/source/Cplt/Utils/IO/FileStream_Cstdio.inl b/app/source/Cplt/Utils/IO/FileStream_Cstdio.inl new file mode 100644 index 0000000..ff2ca01 --- /dev/null +++ b/app/source/Cplt/Utils/IO/FileStream_Cstdio.inl @@ -0,0 +1,126 @@ +// Note: included by FileStream.cpp conditionally, not compiled separately +#include "FileStream.hpp" + +#include + +#include +#include + +namespace fs = std::filesystem; + +InputFileStream::InputFileStream(const fs::path& path) + : mFile{ FileUtils::OpenCstdioFile(path, FileUtils::IM_Read) } +{ +} + +InputFileStream::~InputFileStream() +{ + if (mFile) { + std::fclose(mFile); + } +} + +InputFileStream::InputFileStream(InputFileStream&& that) + : mFile{ that.mFile } +{ + that.mFile = nullptr; +} + +InputFileStream& InputFileStream::operator=(InputFileStream&& that) +{ + if (this == &that) return *this; + + if (mFile) { + std::fclose(mFile); + } + mFile = that.mFile; + that.mFile = nullptr; + + return *this; +} + +OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode) +{ + switch (mode) { + case AppendFile: mFile = FileUtils::OpenCstdioFile(path, FileUtils::IM_WriteAppend); break; + case TruncateFile: mFile = FileUtils::OpenCstdioFile(path, FileUtils::IM_WriteTruncate); break; + } +} + +OutputFileStream::~OutputFileStream() +{ + if (mFile) { + std::fclose(mFile); + } +} + +OutputFileStream::OutputFileStream(OutputFileStream&& that) + : mFile{ that.mFile } +{ + that.mFile = nullptr; +} + +OutputFileStream& OutputFileStream::operator=(OutputFileStream&& that) +{ + if (this == &that) return *this; + + if (mFile) { + std::fclose(mFile); + } + mFile = that.mFile; + that.mFile = nullptr; + + return *this; +} + +int InputFileStream::GetReadInSize() const +{ + return 0; +} + +void InputFileStream::SetReadInSize(int size) +{ + // No-op +} + +bool InputFileStream::IsEof() const +{ + return std::feof(mFile); +} + +IoResult InputFileStream::ReadBytes(size_t bufferLength, std::byte* buffer) +{ + auto bytesRead = std::fread(buffer, 1, bufferLength, mFile); + + return { + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bytesRead, + }; +} + +int OutputFileStream::GetMaxBufferSize() const +{ + return 0; +} + +void OutputFileStream::SetMaxBufferSize(int maxSize) +{ + // No-op +} + +IoResult OutputFileStream::WriteBytes(size_t bufferLength, const std::byte* buffer) +{ + auto bytesWritten = std::fwrite(buffer, 1, bufferLength, mFile); + + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bytesWritten, + }; +} + +void OutputFileStream::FlushBuffer() +{ + // No-op +} diff --git a/app/source/Cplt/Utils/IO/FileStream_Custom.inl b/app/source/Cplt/Utils/IO/FileStream_Custom.inl new file mode 100644 index 0000000..004dd01 --- /dev/null +++ b/app/source/Cplt/Utils/IO/FileStream_Custom.inl @@ -0,0 +1,358 @@ +// Note: included by FileStream.cpp conditionally, not compiled separately +#include "FileStream.hpp" + +#include +#include +#include + +namespace fs = std::filesystem; + +#if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +# define NOMINMAX +# include + +InputFileStream::InputFileStream(const fs::path& path) + : mOsFileHandle{ 0 } +{ + auto handle = reinterpret_cast(mOsFileHandle); + + *handle = CreateFileW( + path.c_str(), // fs::path::c_str() returns a wide string on Windows + GENERIC_READ, + /* No sharing */ 0, + /* Use default security*/ nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + /* No attribute template */ nullptr); + + // TODO handle error +} + +InputFileStream::~InputFileStream() +{ + auto handle = reinterpret_cast(mOsFileHandle); + CloseHandle(*handle); +} + +OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode) + : mOsFileHandle{ 0 } +{ + auto handle = reinterpret_cast(mOsFileHandle); + + DWORD creationDisposition; + switch (mode) { + case AppendFile: creationDisposition = OPEN_ALWAYS; break; + case TruncateFile: creationDisposition = CREATE_ALWAYS; break; + } + + *handle = CreateFileW( + path.c_str(), + GENERIC_WRITE, + /* No sharing */ 0, + /* Use default security*/ nullptr, + creationDisposition, + FILE_ATTRIBUTE_NORMAL, + /* No attribute template */ nullptr); + + // TODO handle error +} + +OutputFileStream::~OutputFileStream() +{ + auto handle = reinterpret_cast(mOsFileHandle); + CloseHandle(*handle); +} + +static IoResult::ErrorKind MapErrorCodeToIoResult(DWORD error) +{ + switch (error) { + // TODO + + default: + std::cerr << "Unimplemented win32 error code " << error << ", report bug immediately.\n"; + std::abort(); + } +} + +static IoResult ReadBytesDirect(HANDLE hFile, size_t byteCount, std::byte* bytes) +{ + DWORD bytesRead; + BOOL result = ReadFile(hFile, bytes, byteCount, &bytesRead, nullptr); + + if (result) { + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bytesRead, + }; + } else { + DWORD errorCode = GetLastError(); + return IoResult{ + .Error = ::MapErrorCodeToIoResult(errorCode), + .SystemError = errorCode, + .BytesMoved = bytesRead, + }; + } +} + +static IoResult WriteBytesDirect(HANDLE hFile, size_t byteCount, const std::byte* bytes) +{ + DWORD bytesWritten; + BOOL result = WriteFile(hFile, bytes, byteCount, &bytesWritten, nullptr); + + if (result) { + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bytesWritten, + }; + } else { + DWORD errorCode = GetLastError(); + return IoResult{ + .Error = ::MapErrorCodeToIoResult(errorCode), + .SystemError = errorCode, + .BytesMoved = bytesWritten, + }; + } +} + +#elif defined(__APPLE__) || defined(__linux__) +# include +# include +# include +# include + +InputFileStream::InputFileStream(const fs::path& path) + : mOsFileHandle{ 0 } +{ + auto fd = reinterpret_cast(mOsFileHandle); + *fd = open(path.c_str(), O_RDONLY); +} + +InputFileStream::~InputFileStream() +{ + auto fd = reinterpret_cast(mOsFileHandle); + close(*fd); +} + +OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode) + : mOsFileHandle{ 0 } +{ + auto fd = reinterpret_cast(mOsFileHandle); + + int flags = O_WRONLY | O_CREAT; + switch (mode) { + case AppendFile: flags |= O_APPEND; break; + case TruncateFile: flags |= O_TRUNC; break; + } + + *fd = open(path.c_str(), flags, 0644); +} + +OutputFileStream::~OutputFileStream() +{ + auto fd = reinterpret_cast(mOsFileHandle); + close(*fd); +} + +static IoResult::ErrorKind MapErrnoToIoResult(int err) +{ + switch (err) { + // TODO + case EFAULT: return IoResult::ERR_UnexpectedEof; + case EPERM: return IoResult::ERR_PermissionDenied; + case ENOSPC: return IoResult::ERR_OutOfSpace; + case EIO: return IoResult::ERR_Other; + + default: + std::cerr << "Unimplemented POSIX errno " << err << ", report bug immediately.\n"; + std::abort(); + } +} + +static IoResult ReadBytesDirect(const char* osFileHandle, size_t byteCount, std::byte* bytes) +{ + int fd = *reinterpret_cast(osFileHandle); + int status = read(fd, bytes, byteCount); + + if (status == -1) { + int err = errno; + return IoResult{ + .Error = ::MapErrnoToIoResult(err), + .SystemError = (uint32_t)err, + .BytesMoved = 0, + }; + } else { + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = (size_t)status, // Equal to number of bytes read + }; + } +} + +static IoResult WriteBytesDirect(const char* osFileHandle, size_t byteCount, const std::byte* bytes) +{ + int fd = *reinterpret_cast(osFileHandle); + int status = write(fd, bytes, byteCount); + + if (status == -1) { + int err = errno; + return IoResult{ + .Error = ::MapErrnoToIoResult(err), + .SystemError = (uint32_t)err, + .BytesMoved = 0, + }; + } else { + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = (size_t)status, // Equal to number of bytes read + }; + } +} + +#else +# error "Unsupported target platform." +#endif + +int InputFileStream::GetReadInSize() const +{ + return mReadInSize; +} + +void InputFileStream::SetReadInSize(int size) +{ + if (size > mReadInSize) { + mReadInSize = size; + mBuffer = std::make_unique(size); + } +} + +bool InputFileStream::IsEof() const +{ + return mEof; +} + +IoResult InputFileStream::ReadBytes(size_t bufferLength, std::byte* buffer) +{ + // TODO reduce duplicated code + + auto bytesMoved = std::min(mAvailableBytes, bufferLength); + + // On first call after construction, mFirstByteIdx will equal to mReadInSize, i.e. bytesAvailable == 0 + // and this call to std::memcpy will be no-op + std::memcpy(buffer, &mBuffer[mFirstByteIdx], bytesMoved); + mFirstByteIdx += (int)bytesMoved; + mAvailableBytes -= (int)bytesMoved; + buffer += bytesMoved; + + size_t bytesLeft = bufferLength - bytesMoved; + if (bytesLeft > mReadInSize) { + // Our buffer can't handle rest of the request, just skip the buffering step + + // Read rest of the data into buffer + { + auto result = ::ReadBytesDirect(mOsFileHandle, bytesLeft, buffer); + bytesMoved += result.BytesMoved; + + if (result.Error == IoResult::ERR_None) { + if (result.BytesMoved < mReadInSize) { + mEof = true; + } + } else { + goto end; + } + } + + // Refill our buffer + { + auto result = ::ReadBytesDirect(mOsFileHandle, mReadInSize, mBuffer.get()); + mFirstByteIdx = 0; + mAvailableBytes = (int)result.BytesMoved; + + if (result.Error == IoResult::ERR_None) { + if (result.BytesMoved < mReadInSize) { + mEof = true; + } + } else { + goto end; + } + } + } else if (bytesLeft > 0) { + // Our buffer can handle rest of the request, first buffer than supply the requested data + + // Refill our buffer + { + auto result = ::ReadBytesDirect(mOsFileHandle, mReadInSize, mBuffer.get()); + mFirstByteIdx = 0; + mAvailableBytes = (int)result.BytesMoved; + + if (result.Error == IoResult::ERR_None) { + if (result.BytesMoved < mReadInSize) { + mEof = true; + } + } else { + goto end; + } + } + + // Copy data into buffer + { + std::memcpy(buffer, &mBuffer[mFirstByteIdx], bytesLeft); + mFirstByteIdx += (int)bytesLeft; + bytesMoved += bytesLeft; + buffer += bytesLeft; + } + } else { + // Request completed already + } + +end: + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bytesMoved, + }; +} + +int OutputFileStream::GetMaxBufferSize() const +{ + return mMaxBufferSize; +} + +void OutputFileStream::SetMaxBufferSize(int maxSize) +{ + FlushBuffer(); + if (maxSize > mMaxBufferSize) { + mMaxBufferSize = maxSize; + mBuffer = std::make_unique(maxSize); + } +} + +IoResult OutputFileStream::WriteBytes(size_t bufferLength, const std::byte* buffer) +{ + if (bufferLength + mCurrentBufferSize > mMaxBufferSize) { + FlushBuffer(); + + if (bufferLength > mMaxBufferSize) { + return ::WriteBytesDirect(mOsFileHandle, bufferLength, buffer); + } + } + + std::memcpy(mBuffer.get() + mCurrentBufferSize, buffer, bufferLength); + mCurrentBufferSize += (int)bufferLength; + + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bufferLength, + }; +} + +void OutputFileStream::FlushBuffer() +{ + ::WriteBytesDirect(mOsFileHandle, mCurrentBufferSize, mBuffer.get()); + mCurrentBufferSize = 0; +} diff --git a/app/source/Cplt/Utils/IO/Helper.hpp b/app/source/Cplt/Utils/IO/Helper.hpp new file mode 100644 index 0000000..7a84103 --- /dev/null +++ b/app/source/Cplt/Utils/IO/Helper.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace DataStreamAdapters { + +/// Helper to invoke either Read() or ReadObject(). +/// This is intended for writing IO adapters, users that's writing IO logic shouldn't using this - it increases compile time while reducing readability. +template +void ReadHelper(InputDataStream& stream, T& t) +{ + if constexpr (!std::is_same_v) { + stream.ReadObjectAdapted(t); + } else if constexpr (requires(T tt, InputDataStream ss) { ss.Read(tt); }) { + stream.Read(t); + } else if constexpr (requires(T tt, InputDataStream ss) { ss.ReadEnum(tt); }) { + stream.ReadEnum(t); + } else if constexpr (requires(T tt, InputDataStream ss) { ss.ReadObject(tt); }) { + stream.ReadObject(t); + } else { + static_assert(false && sizeof(T), "This type is neither a 'value' nor an 'object'."); + } +} + +/// Helper to invoke either Write() or WriteObject(). +/// This is intended for writing IO adapters, users that's writing IO logic shouldn't using this - it increases compile time while reducing readability. +template +void WriteHelper(OutputDataStream& stream, T& t) +{ + if constexpr (!std::is_same_v) { + stream.WriteObjectAdapted(t); + } else if constexpr (requires(T tt, OutputDataStream ss) { ss.Write(tt); }) { + stream.Write(t); + } else if constexpr (requires(T tt, OutputDataStream ss) { ss.WriteEnum(tt); }) { + stream.WriteEnum(t); + } else if constexpr (requires(T tt, OutputDataStream ss) { ss.WriteObject(tt); }) { + stream.WriteObject(t); + } else { + static_assert(false && sizeof(T), "This type is neither a 'value' nor an 'object'."); + } +} + +} // namespace DataStreamAdapters diff --git a/app/source/Cplt/Utils/IO/StringIntegration.hpp b/app/source/Cplt/Utils/IO/StringIntegration.hpp new file mode 100644 index 0000000..66f42b0 --- /dev/null +++ b/app/source/Cplt/Utils/IO/StringIntegration.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include +#include +#include + +namespace DataStreamAdapters { +struct String +{ + static void ReadFromDataStream(InputDataStream& stream, std::string& str) + { + uint64_t size; + stream.Read(size); + + str = {}; + str.reserve(size); + stream.ReadBytes(size, std::back_inserter(str)); + } + + static void WriteToDataStream(OutputDataStream& stream, const std::string& str) + { + stream.Write((uint64_t)str.size()); + stream.WriteBytes(str.size(), str.data()); + } +}; + +struct StringView +{ + static void WriteToDataStream(OutputDataStream& stream, const std::string_view& str) + { + stream.Write((uint64_t)str.size()); + stream.WriteBytes(str.size(), str.data()); + } +}; +} // namespace DataStreamAdapters diff --git a/app/source/Cplt/Utils/IO/TslArrayIntegration.hpp b/app/source/Cplt/Utils/IO/TslArrayIntegration.hpp new file mode 100644 index 0000000..b585bee --- /dev/null +++ b/app/source/Cplt/Utils/IO/TslArrayIntegration.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +// TODO support custom key types + +namespace DataStreamAdapters { +template +struct TslArrayMap +{ + template + static void ReadFromDataStream(InputDataStream& stream, tsl::array_map& map) + { + static_assert(std::is_default_constructible_v); + static_assert(std::is_move_constructible_v); + + uint64_t size; + stream.Read(size); + map.reserve(size); + + for (uint64_t i = 0; i < size; ++i) { + std::string key; + stream.ReadObjectAdapted(key); + + TValue value; + ReadHelper(stream, value); + + map.insert(key, std::move(value)); + } + } + + template + static void WriteToDataStream(OutputDataStream& stream, const tsl::array_map& map) + { + stream.Write((uint64_t)map.size()); + + for (auto it = map.begin(); it != map.end(); ++it) { + stream.WriteObjectAdapted(it.key_sv()); + WriteHelper(stream, it.value()); + } + } +}; +} // namespace DataStreamAdapters diff --git a/app/source/Cplt/Utils/IO/TslRobinIntegration.hpp b/app/source/Cplt/Utils/IO/TslRobinIntegration.hpp new file mode 100644 index 0000000..bdea505 --- /dev/null +++ b/app/source/Cplt/Utils/IO/TslRobinIntegration.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace DataStreamAdapters { +template +struct TslRobinMap +{ + template + static void ReadFromDataStream(InputDataStream& stream, tsl::robin_map& map) + { + static_assert(std::is_default_constructible_v); + static_assert(std::is_move_constructible_v); + + uint64_t size; + stream.Read(size); + map.reserve(size); + + for (uint64_t i = 0; i < size; ++i) { + TKey key; + ReadHelper(stream, key); + + TValue value; + ReadHelper(stream, value); + + map.insert(std::move(key), std::move(value)); + } + } + + template + static void WriteToDataStream(OutputDataStream& stream, const tsl::robin_map& map) + { + stream.Write((uint64_t)map.size()); + + for (auto it = map.begin(); it != map.end(); ++it) { + WriteHelper(stream, it.key()); + WriteHelper(stream, it.value()); + } + } +}; + +template +struct TslRobinSet +{ + template + static void ReadFromDataStream(InputDataStream& stream, tsl::robin_set& set) + { + static_assert(std::is_default_constructible_v); + static_assert(std::is_move_constructible_v); + + uint64_t size; + stream.Read(size); + set.reserve(size); + + for (uint64_t i = 0; i < size; ++i) { + TElement element; + ReadHelper(stream, element); + + set.insert(std::move(element)); + } + } + + template + static void WriteToDataStream(OutputDataStream& stream, const tsl::robin_set& set) + { + stream.Write((uint64_t)set.size()); + + for (auto& element : set) { + WriteHelper(stream, element); + } + } +}; +} // namespace DataStreamAdapters diff --git a/app/source/Cplt/Utils/IO/UuidIntegration.hpp b/app/source/Cplt/Utils/IO/UuidIntegration.hpp new file mode 100644 index 0000000..20c1e7e --- /dev/null +++ b/app/source/Cplt/Utils/IO/UuidIntegration.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace DataStreamAdapters { +struct Uuid +{ + static void ReadFromDataStream(InputDataStream& stream, uuids::uuid& uuid) + { + uint8_t buffer[16]; + stream.ReadBytes(16, buffer); + + uuid = uuids::uuid(gsl::span{ buffer }); + } + + static void WriteToDataStream(OutputDataStream& stream, const uuids::uuid& uuid) + { + auto gslSpan = uuid.as_bytes(); + stream.WriteBytes(gslSpan.size(), gslSpan.data()); + } +}; +} // namespace DataStreamAdapters diff --git a/app/source/Cplt/Utils/IO/VectorIntegration.hpp b/app/source/Cplt/Utils/IO/VectorIntegration.hpp new file mode 100644 index 0000000..93967f6 --- /dev/null +++ b/app/source/Cplt/Utils/IO/VectorIntegration.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#include +#include + +namespace DataStreamAdapters { +template +struct Vector +{ + template + static void ReadFromDataStream(InputDataStream& stream, std::vector& vec) + { + static_assert(std::is_default_constructible_v); + static_assert(std::is_move_constructible_v); + + uint64_t size; + stream.Read(size); + + vec.clear(); + vec.reserve(size); + + for (uint64_t i = 0; i < size; ++i) { + TElement element; + ReadHelper(stream, element); + + vec.push_back(std::move(element)); + } + } + + template + static void WriteToDataStream(OutputDataStream& stream, const std::vector& vec) + { + stream.Write((uint64_t)vec.size()); + for (auto& element : vec) { + WriteHelper(stream, element); + } + } +}; +} // namespace DataStreamAdapters diff --git a/app/source/Cplt/Utils/IO/fwd.hpp b/app/source/Cplt/Utils/IO/fwd.hpp new file mode 100644 index 0000000..9f1492b --- /dev/null +++ b/app/source/Cplt/Utils/IO/fwd.hpp @@ -0,0 +1,13 @@ +#pragma once + +// Archive.hpp +class DataArchive; + +// BaseDataStream.hpp +class BaseDataStream; +class InputDataStream; +class OutputDataStream; + +// FileStream.hpp +class InputFileStream; +class OutputFileStream; diff --git a/app/source/Cplt/Utils/Macros.hpp b/app/source/Cplt/Utils/Macros.hpp new file mode 100644 index 0000000..6958ed1 --- /dev/null +++ b/app/source/Cplt/Utils/Macros.hpp @@ -0,0 +1,13 @@ +#pragma once + +#define STRINGIFY_IMPL(text) #text +#define STRINGIFY(text) STRINGIFY_IMPL(text) + +#define CONCAT_IMPL(a, b) a##b +#define CONCAT(a, b) CONCAT_IMPL(a, b) +#define CONCAT_3(a, b, c) CONCAT(a, CONCAT(b, c)) +#define CONCAT_4(a, b, c, d) CONCAT(CONCAT(a, b), CONCAT(c, d)) + +#define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__) +#define UNIQUE_NAME_LINE(prefix) CONCAT(prefix, __LINE__) +#define DISCARD UNIQUE_NAME(_discard) diff --git a/app/source/Cplt/Utils/Math.hpp b/app/source/Cplt/Utils/Math.hpp new file mode 100644 index 0000000..da53da2 --- /dev/null +++ b/app/source/Cplt/Utils/Math.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace MathUtils { + +template +constexpr T Abs(T t) +{ + return t < 0 ? -t : t; +} + +} // namespace MathUtils diff --git a/app/source/Cplt/Utils/RTTI.hpp b/app/source/Cplt/Utils/RTTI.hpp new file mode 100644 index 0000000..86b1e2c --- /dev/null +++ b/app/source/Cplt/Utils/RTTI.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +template +bool is_a(TBase* t) +{ + assert(t != nullptr); + return T::IsInstance(t); +} + +template +bool is_a_nullable(TBase* t) +{ + if (t) { + return is_a(t); + } else { + return false; + } +} + +template +T* dyn_cast(TBase* t) +{ + assert(t != nullptr); + if (T::IsInstance(t)) { + return static_cast(t); + } else { + return nullptr; + } +} + +template +const T* dyn_cast(const TBase* t) +{ + assert(t != nullptr); + if (T::IsInstance(t)) { + return static_cast(t); + } else { + return nullptr; + } +} + +template +T* dyn_cast_nullable(TBase* t) +{ + if (!t) return nullptr; + return dyn_cast(t); +} diff --git a/app/source/Cplt/Utils/ScopeGuard.hpp b/app/source/Cplt/Utils/ScopeGuard.hpp new file mode 100644 index 0000000..f2b7f46 --- /dev/null +++ b/app/source/Cplt/Utils/ScopeGuard.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include + +template +class ScopeGuard +{ +private: + TCleanupFunc mFunc; + bool mDismissed = false; + +public: + /// Specifically left this implicit so that constructs like + /// \code + /// ScopeGuard sg = [&]() { res.Cleanup(); }; + /// \endcode + /// would work. It is highly discourage and unlikely that one would want to use ScopeGuard as a function + /// parameter, so the normal argument that implicit conversion are harmful doesn't really apply here. + ScopeGuard(TCleanupFunc func) + : mFunc{ std::move(func) } + { + } + + ~ScopeGuard() + { + if (!mDismissed) { + mFunc(); + } + } + + void Dismiss() noexcept + { + mDismissed = true; + } +}; + +#define DEFER ScopeGuard UNIQUE_NAME(scopeGuard) = [&]() diff --git a/app/source/Cplt/Utils/Sigslot.cpp b/app/source/Cplt/Utils/Sigslot.cpp new file mode 100644 index 0000000..1132dfb --- /dev/null +++ b/app/source/Cplt/Utils/Sigslot.cpp @@ -0,0 +1,233 @@ +#include "Sigslot.hpp" + +#include + +bool SignalStub::Connection::IsOccupied() const +{ + return id != InvalidId; +} + +SignalStub::SignalStub(IWrapper& wrapper) + : mWrapper{ &wrapper } +{ +} + +SignalStub::~SignalStub() +{ + RemoveAllConnections(); +} + +std::span SignalStub::GetConnections() const +{ + return mConnections; +} + +SignalStub::Connection& SignalStub::InsertConnection(SlotGuard* guard) +{ + Connection* result; + int size = static_cast(mConnections.size()); + for (int i = 0; i < size; ++i) { + auto& conn = mConnections[i]; + if (!conn.IsOccupied()) { + result = &conn; + result->id = i; + goto setup; + } + } + + mConnections.push_back(Connection{}); + result = &mConnections.back(); + result->id = size; + +setup: + if (guard) { + result->guard = guard; + result->slotId = guard->InsertConnection(*this, result->id); + } + return *result; +} + +void SignalStub::RemoveConnection(int id) +{ + if (id >= 0 && id < mConnections.size()) { + auto& conn = mConnections[id]; + if (conn.IsOccupied()) { + mWrapper->RemoveFunction(conn.id); + if (conn.guard) { + conn.guard->RemoveConnection(conn.slotId); + } + + conn.guard = nullptr; + conn.slotId = SignalStub::InvalidId; + conn.id = SignalStub::InvalidId; + } + } +} + +void SignalStub::RemoveConnectionFor(SlotGuard& guard) +{ + guard.RemoveConnectionFor(*this); +} + +void SignalStub::RemoveAllConnections() +{ + for (size_t i = 0; i < mConnections.size(); ++i) { + RemoveConnection(i); + } +} + +SlotGuard::SlotGuard() +{ +} + +SlotGuard::~SlotGuard() +{ + DisconnectAll(); +} + +void SlotGuard::DisconnectAll() +{ + for (auto& conn : mConnections) { + if (conn.stub) { + // Also calls SlotGuard::removeConnection, our copy of the data will be cleared in it + conn.stub->RemoveConnection(conn.stubId); + } + } +} + +int SlotGuard::InsertConnection(SignalStub& stub, int stubId) +{ + int size = static_cast(mConnections.size()); + for (int i = 0; i < size; ++i) { + auto& conn = mConnections[i]; + if (!conn.stub) { + conn.stub = &stub; + conn.stubId = stubId; + return i; + } + } + + mConnections.push_back(Connection{}); + auto& conn = mConnections.back(); + conn.stub = &stub; + conn.stubId = stubId; + return size; +} + +void SlotGuard::RemoveConnectionFor(SignalStub& stub) +{ + for (auto& conn : mConnections) { + if (conn.stub == &stub) { + conn.stub->RemoveConnection(conn.stubId); + } + } +} + +void SlotGuard::RemoveConnection(int slotId) +{ + mConnections[slotId] = {}; +} + +TEST_CASE("Signal connect and disconnect") +{ + Signal<> sig; + + int counter = 0; + int id = sig.Connect([&]() { counter++; }); + + sig(); + CHECK(counter == 1); + + sig(); + CHECK(counter == 2); + + sig.Disconnect(id); + sig(); + CHECK(counter == 2); +} + +TEST_CASE("Signal with parameters") +{ + Signal sig; + + int counter = 0; + int id = sig.Connect([&](int i) { counter += i; }); + + sig(1); + CHECK(counter == 1); + + sig(0); + CHECK(counter == 1); + + sig(4); + CHECK(counter == 5); + + sig.Disconnect(id); + sig(1); + CHECK(counter == 5); +} + +TEST_CASE("Signal disconnectAll()") +{ + Signal<> sig; + + int counter1 = 0; + int counter2 = 0; + sig.Connect([&]() { counter1++; }); + sig.Connect([&]() { counter2++; }); + + sig(); + CHECK(counter1 == 1); + CHECK(counter2 == 1); + + sig(); + CHECK(counter1 == 2); + CHECK(counter2 == 2); + + sig.DisconnectAll(); + sig(); + CHECK(counter1 == 2); + CHECK(counter2 == 2); +} + +TEST_CASE("SlotGuard auto-disconnection") +{ + int counter1 = 0; + int counter2 = 0; + Signal<> sig; + + { + SlotGuard guard; + sig.Connect(guard, [&]() { counter1 += 1; }); + sig.Connect(guard, [&]() { counter2 += 1; }); + + sig(); + CHECK(counter1 == 1); + CHECK(counter2 == 1); + + sig(); + CHECK(counter1 == 2); + CHECK(counter2 == 2); + } + + sig(); + CHECK(counter1 == 2); + CHECK(counter2 == 2); +} + +TEST_CASE("Signal destruct before SlotGuard") +{ + int counter = 0; + SlotGuard guard; + + { + Signal<> sig2; + sig2.Connect(guard, [&]() { counter++; }); + + sig2(); + CHECK(counter == 1); + } + + // Shouldn't error + guard.DisconnectAll(); +} diff --git a/app/source/Cplt/Utils/Sigslot.hpp b/app/source/Cplt/Utils/Sigslot.hpp new file mode 100644 index 0000000..a4ab94e --- /dev/null +++ b/app/source/Cplt/Utils/Sigslot.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +class SignalStub +{ +public: + /// Non-template interface for Signal to implement (a barrier to stop template + /// arguments propagation). + class IWrapper + { + public: + virtual ~IWrapper() = default; + virtual void RemoveFunction(int id) = 0; + }; + + enum + { + InvalidId = -1, + }; + + struct Connection + { + SlotGuard* guard; + int slotId; + int id = InvalidId; // If `InvalidId`, then this "spot" is unused + + bool IsOccupied() const; + }; + +private: + std::vector mConnections; + IWrapper* mWrapper; + +private: + template + friend class Signal; + friend class SlotGuard; + + SignalStub(IWrapper& wrapper); + ~SignalStub(); + + SignalStub(const SignalStub&) = delete; + SignalStub& operator=(const SignalStub&) = delete; + SignalStub(SignalStub&&) = default; + SignalStub& operator=(SignalStub&&) = default; + + std::span GetConnections() const; + Connection& InsertConnection(SlotGuard* guard = nullptr); + void RemoveConnection(int id); + void RemoveConnectionFor(SlotGuard& guard); + void RemoveAllConnections(); +}; + +template +class Signal : public SignalStub::IWrapper +{ +private: + // Must be in this order so that mFunctions is still intact when mStub's destructor runs + std::vector> mFunctions; + SignalStub mStub; + +public: + Signal() + : mStub(*this) + { + } + + virtual ~Signal() = default; + + Signal(const Signal&) = delete; + Signal& operator=(const Signal&) = delete; + Signal(Signal&&) = default; + Signal& operator=(Signal&&) = default; + + void operator()(TArgs... args) + { + for (auto& conn : mStub.GetConnections()) { + if (conn.IsOccupied()) { + mFunctions[conn.id](std::forward(args)...); + } + } + } + + template + int Connect(TFunction slot) + { + auto& conn = mStub.InsertConnection(); + mFunctions.resize(std::max(mFunctions.size(), (size_t)conn.id + 1)); + mFunctions[conn.id] = std::move(slot); + return conn.id; + } + + template + int Connect(SlotGuard& guard, TFunction slot) + { + auto& conn = mStub.InsertConnection(&guard); + mFunctions.resize(std::max(mFunctions.size(), (size_t)conn.id + 1)); + mFunctions[conn.id] = std::move(slot); + return conn.id; + } + + void Disconnect(int id) + { + mStub.RemoveConnection(id); + } + + void DisconnectFor(SlotGuard& guard) + { + mStub.RemoveConnectionFor(guard); + } + + void DisconnectAll() + { + mStub.RemoveAllConnections(); + } + + virtual void RemoveFunction(int id) + { + mFunctions[id] = {}; + } +}; + +/// Automatic disconnection mechanism for Signal<>. +/// Bind connection to this guard by using the Connect(SlotGuard&, TFunction) overload. +/// Either DisconnectAll() or the destructor disconnects all connections bound to this guard. +class SlotGuard +{ +private: + struct Connection + { + SignalStub* stub = nullptr; + int stubId = SignalStub::InvalidId; + }; + std::vector mConnections; + +public: + friend class SignalStub; + SlotGuard(); + ~SlotGuard(); + + SlotGuard(const SlotGuard&) = delete; + SlotGuard& operator=(const SlotGuard&) = delete; + SlotGuard(SlotGuard&&) = default; + SlotGuard& operator=(SlotGuard&&) = default; + + /// DisconnectBySource all connection associated with this SlotGuard. + void DisconnectAll(); + +private: + /// \return Slot id. + int InsertConnection(SignalStub& stub, int stubId); + /// Remove the connection data in this associated with slotId. This does not invoke + /// the connections' stub's RemoveConnection function. + void RemoveConnection(int slotId); + /// DisconnectBySource all connections from the given stub associated with this SlotGuard. + /// Implementation for SignalStub::RemoveConnectionsFor(SlotGuard&) + void RemoveConnectionFor(SignalStub& stub); +}; diff --git a/app/source/Cplt/Utils/Size.hpp b/app/source/Cplt/Utils/Size.hpp new file mode 100644 index 0000000..ae38e8a --- /dev/null +++ b/app/source/Cplt/Utils/Size.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +template +class Size2 { +public: + T width; + T height; + +public: + Size2() + : width{ 0 }, height{ 0 } { + } + + Size2(T width, T height) + : width{ width }, height{ height } { + } + + Size2(Vec2 vec) + : width{ vec.x }, height{ vec.y } + { + } + + operator Vec2() const + { + return { width, height }; + } + + Vec2 AsVec() const + { + return { width, height }; + } + + friend bool operator==(const Size2&, const Size2&) = default; + + template + Size2 Cast() const + { + return { + static_cast(width), + static_cast(height), + }; + } +}; + +template +Size2 operator+(Size2 a, Size2 b) { + return { a.width + b.width, a.height + b.height }; +} + +template +Size2 operator-(Size2 a, Size2 b) { + return { a.width - b.width, a.height - b.height }; +} + +template +auto operator*(Size2 a, N mult) -> Size2 { + return { a.width * mult, a.height * mult }; +} + +template +auto operator/(Size2 a, N mult) -> Size2 { + return { a.width / mult, a.height / mult }; +} diff --git a/app/source/Cplt/Utils/StandardDirectories.cpp b/app/source/Cplt/Utils/StandardDirectories.cpp new file mode 100644 index 0000000..2202f51 --- /dev/null +++ b/app/source/Cplt/Utils/StandardDirectories.cpp @@ -0,0 +1,78 @@ +#include "StandardDirectories.hpp" + +#include +#include + +namespace fs = std::filesystem; + +#if defined(_WIN32) +// https://stackoverflow.com/questions/54499256/how-to-find-the-saved-games-folder-programmatically-in-c-c +# include +# include +# pragma comment(lib, "shell32.lib") +# pragma comment(lib, "ole32.lib") + +static fs::path GetAppDataRoaming() +{ + PWSTR path = nullptr; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, nullptr, &path); + if (SUCCEEDED(hr)) { + auto dataDir = fs::path(path); + CoTaskMemFree(path); + + fs::create_directories(dataDir); + return dataDir; + } else { + fs::path dataDir("~/AppData/Roaming"); + fs::create_directories(dataDir); + return dataDir; + } +} + +#elif defined(__APPLE__) +// TODO +#elif defined(__linux__) +# include + +static fs::path GetEnvVar(const char* name, const char* backup) +{ + if (const char* path = std::getenv(name)) { + fs::path dataDir(path); + fs::create_directories(dataDir); + return dataDir; + } else { + fs::path dataDir(backup); + fs::create_directories(dataDir); + return dataDir; + } +} + +#endif + +const std::filesystem::path& StandardDirectories::UserData() +{ + static auto userDataDir = []() -> fs::path { +#if defined(_WIN32) + return GetAppDataRoaming(); +#elif defined(__APPLE__) + // TODO where? +#elif defined(__linux__) + return GetEnvVar("XDG_DATA_HOME", "~/.local/share"); +#endif + }(); + return userDataDir; +} + +const std::filesystem::path& StandardDirectories::UserConfig() +{ + static auto userConfigDir = []() -> fs::path { +#if defined(_WIN32) + return GetAppDataRoaming(); +#elif defined(__APPLE__) + // TODO where? +#elif defined(__linux__) + return GetEnvVar("XDG_CONFIG_HOME", "~/.config"); +#endif + }(); + return userConfigDir; +} diff --git a/app/source/Cplt/Utils/StandardDirectories.hpp b/app/source/Cplt/Utils/StandardDirectories.hpp new file mode 100644 index 0000000..4f7e5e2 --- /dev/null +++ b/app/source/Cplt/Utils/StandardDirectories.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace StandardDirectories { + +const std::filesystem::path& UserData(); +const std::filesystem::path& UserConfig(); + +} // namespace StandardDirectories diff --git a/app/source/Cplt/Utils/Time.cpp b/app/source/Cplt/Utils/Time.cpp new file mode 100644 index 0000000..4e79ffa --- /dev/null +++ b/app/source/Cplt/Utils/Time.cpp @@ -0,0 +1,29 @@ +#include "Time.hpp" + +#include + +std::string TimeUtils::StringifyTimePoint(std::chrono::time_point tp) +{ + auto t = std::chrono::system_clock::to_time_t(tp); + + char data[32]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // C++ doesn't have std::localtime_s + std::strftime(data, sizeof(data), "%Y-%m-%d %H:%M:%S", std::localtime(&t)); +#pragma clang diagnostic pop + + return std::string(data); +} + +std::string TimeUtils::StringifyTimeStamp(int64_t timeStamp) +{ + if (timeStamp == 0) { + return ""; + } + + namespace chrono = std::chrono; + chrono::milliseconds d{ timeStamp }; + chrono::time_point tp{ d }; + + return StringifyTimePoint(tp); +} diff --git a/app/source/Cplt/Utils/Time.hpp b/app/source/Cplt/Utils/Time.hpp new file mode 100644 index 0000000..fbbd3b2 --- /dev/null +++ b/app/source/Cplt/Utils/Time.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace TimeUtils { + +std::string StringifyTimePoint(std::chrono::time_point tp); +std::string StringifyTimeStamp(int64_t timeStamp); + +} // namespace TimeUtils diff --git a/app/source/Cplt/Utils/UUID.hpp b/app/source/Cplt/Utils/UUID.hpp new file mode 100644 index 0000000..9044aa6 --- /dev/null +++ b/app/source/Cplt/Utils/UUID.hpp @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include diff --git a/app/source/Cplt/Utils/Variant.hpp b/app/source/Cplt/Utils/Variant.hpp new file mode 100644 index 0000000..df2f882 --- /dev/null +++ b/app/source/Cplt/Utils/Variant.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +template +struct Overloaded : Ts... +{ + using Ts::operator()...; +}; +template +Overloaded(Ts...) -> Overloaded; + +template +struct VariantCastProxy +{ + std::variant v; + + template + operator std::variant() const + { + return std::visit( + [](auto&& arg) -> std::variant { return arg; }, + v); + } +}; + +/// Use snake_case naming to align with `static_cast`, `dynamic_cast`, etc.. +template +auto variant_cast(std::variant v) -> VariantCastProxy +{ + return { std::move(v) }; +} diff --git a/app/source/Cplt/Utils/Vector.hpp b/app/source/Cplt/Utils/Vector.hpp new file mode 100644 index 0000000..79f4ea2 --- /dev/null +++ b/app/source/Cplt/Utils/Vector.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include + +template +struct Vec2 +{ + T x = 0; + T y = 0; + + template + Vec2 Cast() const + { + return { + static_cast(x), + static_cast(y), + }; + } + + void ReadFromDataStream(InputDataStream& stream) + { + stream.Value(x); + stream.Value(y); + } + + void WriteToDataStream(OutputDataStream& stream) const + { + stream.Value(x); + stream.Value(y); + } + + friend constexpr bool operator==(const Vec2& a, const Vec2& b) = default; + + friend constexpr Vec2 operator+(const Vec2& a, const Vec2& b) { return { a.x + b.x, a.y + b.y }; } + friend constexpr Vec2 operator-(const Vec2& a, const Vec2& b) { return { a.x - b.x, a.y - b.y }; } + friend constexpr Vec2 operator*(const Vec2& a, const Vec2& b) { return { a.x * b.x, a.y * b.y }; } + friend constexpr Vec2 operator/(const Vec2& a, const Vec2& b) { return { a.x / b.x, a.y / b.y }; } + + friend constexpr Vec2 operator+(const Vec2& a, T n) { return { a.x + n, a.y + n }; } + friend constexpr Vec2 operator-(const Vec2& a, T n) { return { a.x - n, a.y - n }; } + friend constexpr Vec2 operator*(const Vec2& a, T n) { return { a.x * n, a.y * n }; } + friend constexpr Vec2 operator/(const Vec2& a, T n) { return { a.x / n, a.y / n }; } +}; + +using Vec2i = Vec2; +using Vec2f = Vec2; + +template +struct Vec3 +{ + T x = 0; + T y = 0; + T z = 0; + + template + Vec3 Cast() const + { + return { + static_cast(x), + static_cast(y), + static_cast(z), + }; + } + + void ReadFromDataStream(InputDataStream& stream) + { + stream.Value(x); + stream.Value(y); + stream.Value(z); + } + + void WriteToDataStream(OutputDataStream& stream) const + { + stream.Value(x); + stream.Value(y); + stream.Value(z); + } + + friend constexpr bool operator==(const Vec3& a, const Vec3& b) = default; + + friend constexpr Vec3 operator+(const Vec3& a, const Vec3& b) { return { a.x + b.x, a.y + b.y, a.z + b.z }; } + friend constexpr Vec3 operator-(const Vec3& a, const Vec3& b) { return { a.x - b.x, a.y - b.y, a.z - b.z }; } + friend constexpr Vec3 operator*(const Vec3& a, const Vec3& b) { return { a.x * b.x, a.y * b.y, a.z * b.z }; } + friend constexpr Vec3 operator/(const Vec3& a, const Vec3& b) { return { a.x / b.x, a.y / b.y, a.z / b.z }; } + + friend constexpr Vec3 operator+(const Vec3& a, T n) { return { a.x + n, a.y + n, a.z + n }; } + friend constexpr Vec3 operator-(const Vec3& a, T n) { return { a.x - n, a.y - n, a.z - n }; } + friend constexpr Vec3 operator*(const Vec3& a, T n) { return { a.x * n, a.y * n, a.z * n }; } + friend constexpr Vec3 operator/(const Vec3& a, T n) { return { a.x / n, a.y / n, a.z / n }; } +}; + +using Vec3i = Vec3; +using Vec3f = Vec3; + +template +struct Vec4 +{ + T x = 0; + T y = 0; + T z = 0; + T w = 0; + + template + Vec4 Cast() const + { + return { + static_cast(x), + static_cast(y), + static_cast(z), + static_cast(w), + }; + } + + void ReadFromDataStream(InputDataStream& stream) + { + stream.Value(x); + stream.Value(y); + stream.Value(z); + stream.Value(w); + } + + void WriteToDataStream(OutputDataStream& stream) const + { + stream.Value(x); + stream.Value(y); + stream.Value(z); + stream.Value(w); + } + + friend constexpr bool operator==(const Vec4& a, const Vec4& b) = default; + + friend constexpr Vec4 operator+(const Vec4& a, const Vec4& b) { return { a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w }; } + friend constexpr Vec4 operator-(const Vec4& a, const Vec4& b) { return { a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w }; } + friend constexpr Vec4 operator*(const Vec4& a, const Vec4& b) { return { a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w }; } + friend constexpr Vec4 operator/(const Vec4& a, const Vec4& b) { return { a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w }; } + + friend constexpr Vec4 operator+(const Vec4& a, T n) { return { a.x + n, a.y + n, a.z + n, a.w + n }; } + friend constexpr Vec4 operator-(const Vec4& a, T n) { return { a.x - n, a.y - n, a.z - n, a.w - n }; } + friend constexpr Vec4 operator*(const Vec4& a, T n) { return { a.x * n, a.y * n, a.z * n, a.w * n }; } + friend constexpr Vec4 operator/(const Vec4& a, T n) { return { a.x / n, a.y / n, a.z / n, a.w / n }; } +}; + +using Vec4i = Vec4; +using Vec4f = Vec4; diff --git a/app/source/Cplt/Utils/VectorHash.hpp b/app/source/Cplt/Utils/VectorHash.hpp new file mode 100644 index 0000000..f649367 --- /dev/null +++ b/app/source/Cplt/Utils/VectorHash.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include +#include + +template +struct std::hash> +{ + size_t operator()(const Vec2& vec) const + { + size_t result; + HashUtils::Combine(result, vec.x); + HashUtils::Combine(result, vec.y); + return result; + } +}; + +template +struct std::hash> +{ + size_t operator()(const Vec3& vec) const + { + size_t result; + HashUtils::Combine(result, vec.x); + HashUtils::Combine(result, vec.y); + HashUtils::Combine(result, vec.z); + return result; + } +}; + +template +struct std::hash> +{ + size_t operator()(const Vec4& vec) const + { + size_t result; + HashUtils::Combine(result, vec.x); + HashUtils::Combine(result, vec.y); + HashUtils::Combine(result, vec.z); + HashUtils::Combine(result, vec.w); + return result; + } +}; diff --git a/app/source/Cplt/Utils/fwd.hpp b/app/source/Cplt/Utils/fwd.hpp new file mode 100644 index 0000000..366cacc --- /dev/null +++ b/app/source/Cplt/Utils/fwd.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +// Color.hpp +class RgbaColor; +class HsvColor; + +// Sigslot.hpp +class SignalStub; +template +class Signal; +class SlotGuard; + +// String.hpp +class Utf8Iterator; +class Utf8IterableString; -- cgit v1.2.3-70-g09d2