42 template<
typename T>
requires std::is_aggregate_v<T>
43 static void SaveAggregateStruct(
const T& data,
const std::filesystem::path& file,
const bool safeMode =
false) {
46 Serializer serializer;
47 serializer.Write(data);
50 uint64_t checkSum =
Utility::fnv1a64(
reinterpret_cast<const char*
>(serializer.m_Buffer.data()), serializer.m_Buffer.size());
51 size_t size = serializer.m_Buffer.size();
53 std::filesystem::path targetFile = std::filesystem::path(file) +=
".tmp";
55 std::filesystem::create_directories(targetFile.parent_path());
57 std::ofstream out(targetFile, std::ios::binary | std::ios::trunc);
64 out.write(
reinterpret_cast<const char*
>(&uuid),
sizeof(uuid));
65 out.write(
reinterpret_cast<const char*
>(&structUID),
sizeof(structUID));
66 out.write(
reinterpret_cast<const char*
>(&checkSum),
sizeof(checkSum));
67 out.write(
reinterpret_cast<const char*
>(&size),
sizeof(size));
69 out.write(
reinterpret_cast<const char*
>(serializer.m_Buffer.data()), serializer.m_Buffer.size());
71 out.write(
reinterpret_cast<const char*
>(&uuid),
sizeof(uuid));
75 std::filesystem::rename(targetFile, file);
78 std::filesystem::path bakPath = std::filesystem::path(file) +=
".bak";
80 std::filesystem::create_directories(bakPath.parent_path());
82 std::ofstream outBak(bakPath, std::ios::binary | std::ios::trunc);
89 outBak.write(
reinterpret_cast<const char*
>(&uuid),
sizeof(uuid));
90 outBak.write(
reinterpret_cast<const char*
>(&structUID),
sizeof(structUID));
91 outBak.write(
reinterpret_cast<const char*
>(&checkSum),
sizeof(checkSum));
92 outBak.write(
reinterpret_cast<const char*
>(&size),
sizeof(size));
94 outBak.write(
reinterpret_cast<const char*
>(serializer.m_Buffer.data()), serializer.m_Buffer.size());
96 outBak.write(
reinterpret_cast<const char*
>(&uuid),
sizeof(uuid));
110 template<
typename T>
requires std::is_aggregate_v<T>
111 static std::expected<T, Core::CoriError<>>
LoadAggregateStruct(
const std::filesystem::path& file,
const bool backupFallback =
false) {
112 if (backupFallback) {
113 return LoadAggregateStructImpl<T>(file).or_else([file](
const Core::CoriError<>& error) {
116 return LoadAggregateStructImpl<T>(std::filesystem::path(file) +=
".bak");
120 return LoadAggregateStructImpl<T>(file);
125 template<
typename T>
requires std::is_aggregate_v<T>
126 static std::expected<T, Core::CoriError<>> LoadAggregateStructImpl(
const std::filesystem::path& file) {
129 std::ifstream in(file, std::ios::binary);
132 return std::unexpected(
Core::CoriError(std::format(
"Failed to open file '{}' for reading.", file.string())));
135 in.seekg(0, std::ios::end);
136 size_t fileSize = in.tellg();
138 if (fileSize < m_HeaderSize + m_FooterSize) {
139 return std::unexpected(
Core::CoriError(std::format(
"Failed to read file '{}'. Impossible file size '{}', file is corrupted.", file.string(), fileSize)));
142 in.seekg(0, std::ios::beg);
144 std::vector<std::byte> buffer(fileSize);
146 in.read(
reinterpret_cast<char*
>(buffer.data()), fileSize);
148 catch (
const std::exception& e) {
149 return std::unexpected(Core::CoriError(std::format(
"Failed to read from file '{}', error '{}'.", file.string(), e.what())));
152 auto headerUUID = *
reinterpret_cast<std::pair<uint64_t, uint64_t>*
>(&buffer[0]);
153 auto footerUUID = *
reinterpret_cast<std::pair<uint64_t, uint64_t>*
>(&buffer[fileSize - m_FooterSize]);
154 auto readStructUID = *
reinterpret_cast<uint64_t*
>(&buffer[16]);
155 auto readCheckSum = *
reinterpret_cast<uint64_t*
>(&buffer[24]);
156 auto readDataBlockSize = *
reinterpret_cast<size_t*
>(&buffer[32]);
158 if (readDataBlockSize + m_HeaderSize + m_FooterSize != fileSize) {
159 return std::unexpected(Core::CoriError(std::format(
"Failed to load file '{}'. Expected (cached) file size '{}' does not match the actual file size '{}'. File is corrupted.", file.string(), readDataBlockSize + m_HeaderSize + m_FooterSize, fileSize)));
162 if (headerUUID.first != footerUUID.first || headerUUID.second != footerUUID.second) {
163 return std::unexpected(Core::CoriError(std::format(
"Failed to load file '{}'. UUID in the header doesn't match UUID in the footer, usually happens when program began writing a file, but then crashed/got closen without finishing properly. File is corrupted.", file.string())));
167 if (readStructUID != structUID) {
168 return std::unexpected(Core::CoriError(std::format(
"Failed to load file '{}'. Expected (cached) struct UID '{}' doesn't match the struct UID '{}' (typename <{}>) you're trying to write the contents to. You either trying to load into a incorrect struct type, or the file is corrupted.", file.string(), readStructUID, structUID,
CORI_CLEAN_TYPE_NAME(T))));
171 uint64_t checkSum =
Utility::fnv1a64(
reinterpret_cast<const char*
>(buffer.data() + m_HeaderSize), readDataBlockSize);
173 if (readCheckSum != checkSum) {
174 return std::unexpected(Core::CoriError(std::format(
"Failed to load file '{}'. Expected (cached) check sum '{}' doesn't match the check sum '{}' of the loaded data block. File is corrupted.", file.string(), readCheckSum, checkSum)));
177 Deserializer deserializer(buffer, m_HeaderSize);
178 deserializer.Read(data);
184 template <
typename T>
185 void Write(
const T& value) {
186 if constexpr (Internal::Trivial<T>) {
187 const auto* ptr =
reinterpret_cast<const std::byte*
>(&value);
188 m_Buffer.insert(m_Buffer.end(), ptr, ptr +
sizeof(T));
190 else if constexpr (Internal::IsVector<T>) {
192 const auto* ptr =
reinterpret_cast<const std::byte*
>(value.data());
193 m_Buffer.insert(m_Buffer.end(), ptr, ptr + value.size() *
sizeof(
typename T::value_type));
195 else if constexpr (std::is_aggregate_v<T>) {
196 boost::pfr::for_each_field(value, [
this](
const auto& field) {
200 static_assert(!
sizeof(T),
"Unsupported type for serialization");
204 std::vector<std::byte> m_Buffer;
209 explicit Deserializer(
const std::vector<std::byte>& buffer,
const size_t startingOffset) : m_Buffer(buffer), m_Offset(startingOffset) {}
211 template <
typename T>
212 void Read(T& value) {
213 if (m_Offset >= m_Buffer.size()) {
214 throw std::runtime_error(
"Read past end of buffer.");
217 if constexpr (Internal::Trivial<T>) {
218 std::memcpy(&value, m_Buffer.data() + m_Offset,
sizeof(T));
219 m_Offset +=
sizeof(T);
221 else if constexpr (Internal::IsVector<T>) {
225 const size_t dataBlockSize = size *
sizeof(
typename T::value_type);
226 std::memcpy(value.data(), m_Buffer.data() + m_Offset, dataBlockSize);
227 m_Offset += dataBlockSize;
229 else if constexpr (std::is_aggregate_v<T>) {
230 boost::pfr::for_each_field(value, [
this](
auto& field) {
234 static_assert(!
sizeof(T),
"Unsupported type for deserialization");
239 const std::vector<std::byte>& m_Buffer;
243 static constexpr size_t m_HeaderSize = 40;
244 static constexpr size_t m_FooterSize = 16;