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/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 + 17 files changed, 1499 insertions(+) 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 (limited to 'app/source/Cplt/Utils/IO') 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; -- cgit v1.2.3-70-g09d2