aboutsummaryrefslogtreecommitdiff
path: root/app/source/Cplt/Utils/IO
diff options
context:
space:
mode:
Diffstat (limited to 'app/source/Cplt/Utils/IO')
-rw-r--r--app/source/Cplt/Utils/IO/Archive.cpp57
-rw-r--r--app/source/Cplt/Utils/IO/Archive.hpp18
-rw-r--r--app/source/Cplt/Utils/IO/CstdioFile.cpp36
-rw-r--r--app/source/Cplt/Utils/IO/CstdioFile.hpp17
-rw-r--r--app/source/Cplt/Utils/IO/DataStream.cpp283
-rw-r--r--app/source/Cplt/Utils/IO/DataStream.hpp210
-rw-r--r--app/source/Cplt/Utils/IO/FileStream.cpp7
-rw-r--r--app/source/Cplt/Utils/IO/FileStream.hpp97
-rw-r--r--app/source/Cplt/Utils/IO/FileStream_Cstdio.inl126
-rw-r--r--app/source/Cplt/Utils/IO/FileStream_Custom.inl358
-rw-r--r--app/source/Cplt/Utils/IO/Helper.hpp43
-rw-r--r--app/source/Cplt/Utils/IO/StringIntegration.hpp37
-rw-r--r--app/source/Cplt/Utils/IO/TslArrayIntegration.hpp50
-rw-r--r--app/source/Cplt/Utils/IO/TslRobinIntegration.hpp78
-rw-r--r--app/source/Cplt/Utils/IO/UuidIntegration.hpp27
-rw-r--r--app/source/Cplt/Utils/IO/VectorIntegration.hpp42
-rw-r--r--app/source/Cplt/Utils/IO/fwd.hpp13
17 files changed, 1499 insertions, 0 deletions
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<const uint8_t, 8> DataArchive::GetMagicNumbers()
+{
+ return std::span<const uint8_t, 8>{ kMagicNumbers };
+}
+
+std::optional<InputDataStream> 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<OutputDataStream> 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 <Cplt/Utils/IO/DataStream.hpp>
+
+#include <cstdint>
+#include <filesystem>
+#include <optional>
+#include <span>
+
+class DataArchive
+{
+public:
+ static std::span<const uint8_t, 8> GetMagicNumbers();
+
+ // TODO more complete impl
+ static std::optional<InputDataStream> LoadFile(const std::filesystem::path& path);
+ static std::optional<OutputDataStream> 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 <Cplt/Utils/Macros.hpp>
+
+#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 <cstdio>
+#include <filesystem>
+
+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 <bit>
+#include <limits>
+#include <utility>
+
+static_assert(std::numeric_limits<float>::is_iec559, "Non IEE754/IEC559 'float' is not supported.");
+static_assert(std::numeric_limits<double>::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<std::byte*>(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 <class TSigned>
+static TSigned ByteSwap(TSigned n)
+{
+ using Unsigned = std::make_unsigned_t<TSigned>;
+
+ auto swapped = ::ByteSwap(std::bit_cast<Unsigned>(n));
+ return std::bit_cast<TSigned>(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<std::streamsize>(byteCount), reinterpret_cast<std::byte*>(buffer));
+}
+
+void InputDataStream::ReadBytes(size_t byteCount, char* buffer)
+{
+ mBackend.ReadBytes(static_cast<std::streamsize>(byteCount), reinterpret_cast<std::byte*>(buffer));
+}
+
+void InputDataStream::ReadBytes(size_t byteCount, signed char* buffer)
+{
+ mBackend.ReadBytes(static_cast<std::streamsize>(byteCount), reinterpret_cast<std::byte*>(buffer));
+}
+
+void InputDataStream::ReadBytes(size_t byteCount, unsigned char* buffer)
+{
+ mBackend.ReadBytes(static_cast<std::streamsize>(byteCount), reinterpret_cast<std::byte*>(buffer));
+}
+
+void InputDataStream::Read(int8_t& n)
+{
+ // sizeof() of a reference type yields the size of the reference
+ mBackend.ReadBytes(sizeof(n), reinterpret_cast<std::byte*>(&n));
+}
+
+void InputDataStream::Read(int16_t& n)
+{
+ int16_t tmp;
+ mBackend.ReadBytes(sizeof(tmp), reinterpret_cast<std::byte*>(&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<std::byte*>(&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<std::byte*>(&tmp));
+ if (GetEndianness() != std::endian::native) {
+ n = ::ByteSwap(tmp);
+ } else {
+ n = tmp;
+ }
+}
+
+void InputDataStream::Read(uint8_t& n)
+{
+ mBackend.ReadBytes(sizeof(n), reinterpret_cast<std::byte*>(&n));
+}
+
+void InputDataStream::Read(uint16_t& n)
+{
+ uint16_t tmp;
+ mBackend.ReadBytes(sizeof(tmp), reinterpret_cast<std::byte*>(&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<std::byte*>(&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<std::byte*>(&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<float>(buffer);
+}
+
+void InputDataStream::Read(double& n)
+{
+ uint64_t buffer;
+ Read(buffer);
+
+ if (GetEndianness() != std::endian::native) {
+ buffer = ::ByteSwap(buffer);
+ }
+
+ n = std::bit_cast<double>(buffer);
+}
+
+OutputDataStream::OutputDataStream(OutputFileStream stream)
+ : mBackend{ std::move(stream) }
+{
+}
+
+void OutputDataStream::WriteBytes(size_t byteCount, const std::byte* buffer)
+{
+ mBackend.WriteBytes(static_cast<std::streamsize>(byteCount), reinterpret_cast<const std::byte*>(buffer));
+}
+
+void OutputDataStream::WriteBytes(size_t byteCount, const char* buffer)
+{
+ mBackend.WriteBytes(static_cast<std::streamsize>(byteCount), reinterpret_cast<const std::byte*>(buffer));
+}
+
+void OutputDataStream::WriteBytes(size_t byteCount, const signed char* buffer)
+{
+ mBackend.WriteBytes(static_cast<std::streamsize>(byteCount), reinterpret_cast<const std::byte*>(buffer));
+}
+
+void OutputDataStream::WriteBytes(size_t byteCount, const unsigned char* buffer)
+{
+ mBackend.WriteBytes(static_cast<std::streamsize>(byteCount), reinterpret_cast<const std::byte*>(buffer));
+}
+
+void OutputDataStream::Write(int8_t n)
+{
+ mBackend.WriteBytes(sizeof(n), reinterpret_cast<const std::byte*>(&n));
+}
+
+void OutputDataStream::Write(int16_t n)
+{
+ if (GetEndianness() != std::endian::native) {
+ n = ::ByteSwap(n);
+ }
+ mBackend.WriteBytes(sizeof(n), reinterpret_cast<const std::byte*>(&n));
+}
+
+void OutputDataStream::Write(int32_t n)
+{
+ if (GetEndianness() != std::endian::native) {
+ n = ::ByteSwap(n);
+ }
+ mBackend.WriteBytes(sizeof(n), reinterpret_cast<const std::byte*>(&n));
+}
+
+void OutputDataStream::Write(int64_t n)
+{
+ if (GetEndianness() != std::endian::native) {
+ n = ::ByteSwap(n);
+ }
+ mBackend.WriteBytes(sizeof(n), reinterpret_cast<const std::byte*>(&n));
+}
+
+void OutputDataStream::Write(uint8_t n)
+{
+ mBackend.WriteBytes(sizeof(n), reinterpret_cast<const std::byte*>(&n));
+}
+
+void OutputDataStream::Write(uint16_t n)
+{
+ if (GetEndianness() != std::endian::native) {
+ n = ::ByteSwap(n);
+ }
+ mBackend.WriteBytes(sizeof(n), reinterpret_cast<const std::byte*>(&n));
+}
+
+void OutputDataStream::Write(uint32_t n)
+{
+ if (GetEndianness() != std::endian::native) {
+ n = ::ByteSwap(n);
+ }
+ mBackend.WriteBytes(sizeof(n), reinterpret_cast<const std::byte*>(&n));
+}
+
+void OutputDataStream::Write(uint64_t n)
+{
+ if (GetEndianness() != std::endian::native) {
+ n = ::ByteSwap(n);
+ }
+ mBackend.WriteBytes(sizeof(n), reinterpret_cast<const std::byte*>(&n));
+}
+
+void OutputDataStream::Write(float n)
+{
+ auto buffer = std::bit_cast<uint32_t>(n);
+ if (GetEndianness() != std::endian::native) {
+ buffer = ::ByteSwap(buffer);
+ }
+ mBackend.WriteBytes(sizeof(buffer), reinterpret_cast<const std::byte*>(&buffer));
+}
+
+void OutputDataStream::Write(double n)
+{
+ auto buffer = std::bit_cast<uint64_t>(n);
+ if (GetEndianness() != std::endian::native) {
+ buffer = ::ByteSwap(buffer);
+ }
+ mBackend.WriteBytes(sizeof(buffer), reinterpret_cast<const std::byte*>(&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 <Cplt/fwd.hpp>
+
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+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 <class TInserter>
+ 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 <class TEnum>
+ requires std::is_enum_v<TEnum>
+ void ReadEnum(TEnum& e)
+ {
+ std::underlying_type_t<TEnum> n;
+ Read(n);
+ e = static_cast<TEnum>(e);
+ }
+
+ template <class TObject>
+ void ReadObject(TObject& obj)
+ {
+ obj.ReadFromDataStream(*this);
+ }
+
+ template <class TAdapter, class TObject>
+ void ReadObjectAdapted(TObject& obj)
+ {
+ TAdapter::ReadFromDataStream(*this, obj);
+ }
+
+public:
+ // Proxy functions for writing templated IO functions
+
+ template <class T>
+ void Bytes(size_t byteCount, T* buffer)
+ {
+ ReadBytes(byteCount, buffer);
+ }
+
+ template <class T>
+ void Value(T& t)
+ {
+ Read(t);
+ }
+
+ template <class T>
+ void Enum(T& t)
+ {
+ ReadEnum(t);
+ }
+
+ template <class T>
+ void Object(T& obj)
+ {
+ ReadObject(obj);
+ }
+
+ template <class TAdapter, class TObject>
+ void ObjectAdapted(TObject& obj)
+ {
+ ReadObjectAdapted<TAdapter>(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 <class TIterator>
+ 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 <class TEnum>
+ requires std::is_enum_v<TEnum>
+ void WriteEnum(TEnum e)
+ {
+ auto n = static_cast<std::underlying_type_t<TEnum>>(e);
+ Write(n);
+ }
+
+ template <class TObject>
+ void WriteObject(const TObject& obj)
+ {
+ obj.WriteToDataStream(*this);
+ }
+
+ template <class TAdapter, class TObject>
+ void WriteObjectAdapted(const TObject& obj)
+ {
+ TAdapter::WriteToDataStream(*this, obj);
+ }
+
+public:
+ // Proxy functions for writing templated IO functions
+
+ template <class T>
+ void Bytes(size_t byteCount, T* buffer)
+ {
+ WriteBytes(byteCount, buffer);
+ }
+
+ template <class T>
+ void Value(T t)
+ {
+ Write(t);
+ }
+
+ template <class T>
+ void Enum(T t)
+ {
+ WriteEnum(t);
+ }
+
+ template <class T>
+ void Object(T& obj)
+ {
+ WriteObject(obj);
+ }
+
+ template <class TAdapter, class TObject>
+ void ObjectAdapted(TObject& obj)
+ {
+ WriteObjectAdapted<TAdapter>(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 <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <memory>
+
+// 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<std::byte[]> 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<std::byte[]> 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 <Cplt/Utils/IO/CstdioFile.hpp>
+
+#include <cstdio>
+#include <filesystem>
+
+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 <cstring>
+#include <filesystem>
+#include <iostream>
+
+namespace fs = std::filesystem;
+
+#if defined(_WIN32)
+# define WIN32_LEAN_AND_MEAN
+# define NOMINMAX
+# include <Windows.h>
+
+InputFileStream::InputFileStream(const fs::path& path)
+ : mOsFileHandle{ 0 }
+{
+ auto handle = reinterpret_cast<HANDLE*>(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<HANDLE*>(mOsFileHandle);
+ CloseHandle(*handle);
+}
+
+OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode)
+ : mOsFileHandle{ 0 }
+{
+ auto handle = reinterpret_cast<HANDLE*>(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<HANDLE*>(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 <fcntl.h>
+# include <sys/stat.h>
+# include <sys/types.h>
+# include <unistd.h>
+
+InputFileStream::InputFileStream(const fs::path& path)
+ : mOsFileHandle{ 0 }
+{
+ auto fd = reinterpret_cast<int*>(mOsFileHandle);
+ *fd = open(path.c_str(), O_RDONLY);
+}
+
+InputFileStream::~InputFileStream()
+{
+ auto fd = reinterpret_cast<int*>(mOsFileHandle);
+ close(*fd);
+}
+
+OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode)
+ : mOsFileHandle{ 0 }
+{
+ auto fd = reinterpret_cast<int*>(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<int*>(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<const int*>(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<const int*>(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<std::byte[]>(size);
+ }
+}
+
+bool InputFileStream::IsEof() const
+{
+ return mEof;
+}
+
+IoResult InputFileStream::ReadBytes(size_t bufferLength, std::byte* buffer)
+{
+ // TODO reduce duplicated code
+
+ auto bytesMoved = std::min<size_t>(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<std::byte[]>(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 <Cplt/Utils/IO/DataStream.hpp>
+
+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 <class TAdapter, class T>
+void ReadHelper(InputDataStream& stream, T& t)
+{
+ if constexpr (!std::is_same_v<TAdapter, void>) {
+ stream.ReadObjectAdapted<TAdapter>(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 <class TAdapter, class T>
+void WriteHelper(OutputDataStream& stream, T& t)
+{
+ if constexpr (!std::is_same_v<TAdapter, void>) {
+ stream.WriteObjectAdapted<TAdapter>(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 <Cplt/Utils/IO/DataStream.hpp>
+
+#include <iterator>
+#include <string>
+#include <string_view>
+
+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 <Cplt/Utils/IO/DataStream.hpp>
+#include <Cplt/Utils/IO/Helper.hpp>
+#include <Cplt/Utils/IO/StringIntegration.hpp>
+
+#include <tsl/array_map.h>
+#include <tsl/array_set.h>
+#include <string>
+#include <type_traits>
+
+// TODO support custom key types
+
+namespace DataStreamAdapters {
+template <class TAdapter = void>
+struct TslArrayMap
+{
+ template <class TValue>
+ static void ReadFromDataStream(InputDataStream& stream, tsl::array_map<char, TValue>& map)
+ {
+ static_assert(std::is_default_constructible_v<TValue>);
+ static_assert(std::is_move_constructible_v<TValue>);
+
+ uint64_t size;
+ stream.Read(size);
+ map.reserve(size);
+
+ for (uint64_t i = 0; i < size; ++i) {
+ std::string key;
+ stream.ReadObjectAdapted<DataStreamAdapters::String>(key);
+
+ TValue value;
+ ReadHelper<TAdapter>(stream, value);
+
+ map.insert(key, std::move(value));
+ }
+ }
+
+ template <class TValue>
+ static void WriteToDataStream(OutputDataStream& stream, const tsl::array_map<char, TValue>& map)
+ {
+ stream.Write((uint64_t)map.size());
+
+ for (auto it = map.begin(); it != map.end(); ++it) {
+ stream.WriteObjectAdapted<DataStreamAdapters::StringView>(it.key_sv());
+ WriteHelper<TAdapter>(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 <Cplt/Utils/IO/DataStream.hpp>
+#include <Cplt/Utils/IO/Helper.hpp>
+
+#include <tsl/robin_map.h>
+#include <tsl/robin_set.h>
+#include <type_traits>
+
+namespace DataStreamAdapters {
+template <class TKeyAdapter = void, class TValueAdapter = void>
+struct TslRobinMap
+{
+ template <class TKey, class TValue>
+ static void ReadFromDataStream(InputDataStream& stream, tsl::robin_map<TKey, TValue>& map)
+ {
+ static_assert(std::is_default_constructible_v<TValue>);
+ static_assert(std::is_move_constructible_v<TValue>);
+
+ uint64_t size;
+ stream.Read(size);
+ map.reserve(size);
+
+ for (uint64_t i = 0; i < size; ++i) {
+ TKey key;
+ ReadHelper<TKeyAdapter>(stream, key);
+
+ TValue value;
+ ReadHelper<TValueAdapter>(stream, value);
+
+ map.insert(std::move(key), std::move(value));
+ }
+ }
+
+ template <class TKey, class TValue>
+ static void WriteToDataStream(OutputDataStream& stream, const tsl::robin_map<TKey, TValue>& map)
+ {
+ stream.Write((uint64_t)map.size());
+
+ for (auto it = map.begin(); it != map.end(); ++it) {
+ WriteHelper<TKeyAdapter>(stream, it.key());
+ WriteHelper<TValueAdapter>(stream, it.value());
+ }
+ }
+};
+
+template <class TAdapter = void>
+struct TslRobinSet
+{
+ template <class TElement>
+ static void ReadFromDataStream(InputDataStream& stream, tsl::robin_set<TElement>& set)
+ {
+ static_assert(std::is_default_constructible_v<TElement>);
+ static_assert(std::is_move_constructible_v<TElement>);
+
+ uint64_t size;
+ stream.Read(size);
+ set.reserve(size);
+
+ for (uint64_t i = 0; i < size; ++i) {
+ TElement element;
+ ReadHelper<TAdapter>(stream, element);
+
+ set.insert(std::move(element));
+ }
+ }
+
+ template <class TElement>
+ static void WriteToDataStream(OutputDataStream& stream, const tsl::robin_set<TElement>& set)
+ {
+ stream.Write((uint64_t)set.size());
+
+ for (auto& element : set) {
+ WriteHelper<TAdapter>(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 <Cplt/Utils/IO/DataStream.hpp>
+#include <Cplt/Utils/UUID.hpp>
+
+#include <cstddef>
+#include <cstdint>
+#include <iterator>
+
+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<uint8_t, 16>{ 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 <Cplt/Utils/IO/DataStream.hpp>
+#include <Cplt/Utils/IO/Helper.hpp>
+
+#include <type_traits>
+#include <vector>
+
+namespace DataStreamAdapters {
+template <class TAdapter = void>
+struct Vector
+{
+ template <class TElement>
+ static void ReadFromDataStream(InputDataStream& stream, std::vector<TElement>& vec)
+ {
+ static_assert(std::is_default_constructible_v<TElement>);
+ static_assert(std::is_move_constructible_v<TElement>);
+
+ uint64_t size;
+ stream.Read(size);
+
+ vec.clear();
+ vec.reserve(size);
+
+ for (uint64_t i = 0; i < size; ++i) {
+ TElement element;
+ ReadHelper<TAdapter>(stream, element);
+
+ vec.push_back(std::move(element));
+ }
+ }
+
+ template <class TElement>
+ static void WriteToDataStream(OutputDataStream& stream, const std::vector<TElement>& vec)
+ {
+ stream.Write((uint64_t)vec.size());
+ for (auto& element : vec) {
+ WriteHelper<TAdapter>(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;