From 2615cf1c269c3faea1bf61fec7351789bf9e42d7 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 25 Aug 2026 21:03:27 +0200 Subject: [PATCH] feat(core): add world snapshot serialization helpers --- core/include/vf/Serialize.h | 18 ++++++ core/src/Serialize.cpp | 114 ++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 core/include/vf/Serialize.h create mode 100644 core/src/Serialize.cpp diff --git a/core/include/vf/Serialize.h b/core/include/vf/Serialize.h new file mode 100644 index 0000000..57a5d80 --- /dev/null +++ b/core/include/vf/Serialize.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include "World.h" + +namespace vf { + +// Binary world format "VXF1": magic, version, chunk count, then per chunk the +// coordinate and a run-length-encoded block stream. Compact for the large +// runs of air and stone that voxel terrain produces. +std::vector serializeWorld(const World& world); + +// Rebuilds `out` from a serialized buffer. Returns false on a bad magic, +// version, or truncated stream. +bool deserializeWorld(const uint8_t* data, std::size_t size, World& out); + +} // namespace vf diff --git a/core/src/Serialize.cpp b/core/src/Serialize.cpp new file mode 100644 index 0000000..3709cc9 --- /dev/null +++ b/core/src/Serialize.cpp @@ -0,0 +1,114 @@ +#include "vf/Serialize.h" + +namespace vf { + +namespace { + +constexpr uint32_t magic = 0x31465856; // "VXF1" little-endian +constexpr uint32_t formatVersion = 1; + +void putU32(std::vector& out, uint32_t value) { + out.push_back(static_cast(value & 0xff)); + out.push_back(static_cast((value >> 8) & 0xff)); + out.push_back(static_cast((value >> 16) & 0xff)); + out.push_back(static_cast((value >> 24) & 0xff)); +} + +void putI32(std::vector& out, int32_t value) { + putU32(out, static_cast(value)); +} + +void putU16(std::vector& out, uint16_t value) { + out.push_back(static_cast(value & 0xff)); + out.push_back(static_cast((value >> 8) & 0xff)); +} + +struct Reader { + const uint8_t* data; + std::size_t size; + std::size_t pos = 0; + + bool u32(uint32_t& out) { + if (pos + 4 > size) return false; + out = static_cast(data[pos]) | (static_cast(data[pos + 1]) << 8) | + (static_cast(data[pos + 2]) << 16) | + (static_cast(data[pos + 3]) << 24); + pos += 4; + return true; + } + bool i32(int32_t& out) { + uint32_t v; + if (!u32(v)) return false; + out = static_cast(v); + return true; + } + bool u16(uint16_t& out) { + if (pos + 2 > size) return false; + out = static_cast(data[pos]) | (static_cast(data[pos + 1]) << 8); + pos += 2; + return true; + } + bool u8(uint8_t& out) { + if (pos + 1 > size) return false; + out = data[pos++]; + return true; + } +}; + +} // namespace + +std::vector serializeWorld(const World& world) { + std::vector out; + putU32(out, magic); + putU32(out, formatVersion); + putU32(out, static_cast(world.chunkCount())); + + for (const auto& entry : world.allChunks()) { + const Vec3i& coord = entry.first; + const Chunk& chunk = *entry.second; + putI32(out, coord.x); + putI32(out, coord.y); + putI32(out, coord.z); + + const auto& blocks = chunk.raw(); + std::size_t i = 0; + while (i < blocks.size()) { + Block value = blocks[i]; + uint16_t run = 1; + while (i + run < blocks.size() && blocks[i + run] == value && run < 0xffff) ++run; + out.push_back(static_cast(value)); + putU16(out, run); + i += run; + } + } + return out; +} + +bool deserializeWorld(const uint8_t* data, std::size_t size, World& out) { + Reader reader{data, size}; + uint32_t fileMagic = 0; + uint32_t version = 0; + uint32_t chunkTotal = 0; + if (!reader.u32(fileMagic) || fileMagic != magic) return false; + if (!reader.u32(version) || version != formatVersion) return false; + if (!reader.u32(chunkTotal)) return false; + + for (uint32_t c = 0; c < chunkTotal; ++c) { + int32_t cx, cy, cz; + if (!reader.i32(cx) || !reader.i32(cy) || !reader.i32(cz)) return false; + Chunk& chunk = out.ensureChunk({cx, cy, cz}); + auto& blocks = chunk.raw(); + + std::size_t written = 0; + while (written < blocks.size()) { + uint8_t value; + uint16_t run; + if (!reader.u8(value) || !reader.u16(run)) return false; + if (run == 0 || written + run > blocks.size()) return false; + for (uint16_t r = 0; r < run; ++r) blocks[written++] = static_cast(value); + } + } + return true; +} + +} // namespace vf