From 22324d7b48c6137b22a38cee948506533421063f Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 4 Sep 2026 14:23:09 +0200 Subject: [PATCH] feat(vf): add ChunkFragment and CompactionStats --- core/include/vf/RegionCompact.h | 96 +++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 core/include/vf/RegionCompact.h diff --git a/core/include/vf/RegionCompact.h b/core/include/vf/RegionCompact.h new file mode 100644 index 0000000..329cb04 --- /dev/null +++ b/core/include/vf/RegionCompact.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include +#include + +#include "Codec.h" +#include "Math.h" +#include "SaveIntegrity.h" +#include "SaveMigration.h" + +namespace vf { + +// Compaction for a region save container. +// +// A live world rewrites single chunks over and over. A rewrite that still fits +// its sector run is written in place and leaves the unused tail behind; one +// that outgrows it is appended at the end and abandons the old sectors. Both +// are cheap at write time and both waste space, so the file drifts towards a +// layout where most sectors carry nothing. +// +// The compactor measures that drift, rewrites the container back to the tight +// canonical layout, and copies every surviving payload verbatim - the bytes a +// chunk reads back are the bytes it had before, unless repacking was asked for +// explicitly. It refuses to run on a container the scanner calls damaged, +// because moving corrupt records around only spreads the damage. + +struct ChunkFragment { + Vec3i coord; + int entryIndex = 0; + uint32_t sectorOffset = 0; + uint32_t sectorCount = 0; + uint32_t payloadBytes = 0; + uint32_t neededSectors = 0; + uint32_t excessSectors = 0; + uint32_t slackBytes = 0; + bool empty = false; + bool reachable = false; +}; + +struct CompactionStats { + bool ok = false; + std::string failure; + + uint32_t sectorBytes = 0; + uint32_t sectorsBefore = 0; + uint32_t sectorsAfter = 0; + uint32_t headerSectors = 0; + uint32_t referencedSectors = 0; + uint32_t holeSectors = 0; + uint32_t excessSectors = 0; + uint32_t slackBytes = 0; + + int fragmentationBefore = 0; + int fragmentationAfter = 0; + + std::size_t bytesBefore = 0; + std::size_t bytesAfter = 0; + std::size_t bytesReclaimed = 0; + + std::size_t chunksKept = 0; + std::size_t chunksMoved = 0; + std::size_t chunksDropped = 0; + std::size_t chunksRepacked = 0; + + // Cost in units of work done, not wall clock: one per entry surveyed, one + // per chunk moved, dropped or repacked, one for the header rewrite. + std::size_t operations = 0; +}; + +// Rewrites one chunk that the container already holds, exactly the way a +// running server would: in place while the payload still fits its sector run, +// appended at the end otherwise. Returns false if the chunk is not in the +// table - growing the entry table is a full rewrite, not an update. +bool updateChunkInPlace(ByteBuffer& buffer, const SaveChunkRecord& record, bool compressBody); + +class RegionCompactor { +public: + bool dropEmptyChunks = true; + bool repackWithStrongerCompression = false; + + std::vector survey(const uint8_t* data, std::size_t size) const; + std::vector survey(const ByteBuffer& buffer) const; + + // Share of the sectors that a tight rewrite would give back, in percent. + // Zero for a canonical layout, -1 when the buffer is not a container. + int fragmentationPercent(const uint8_t* data, std::size_t size) const; + int fragmentationPercent(const ByteBuffer& buffer) const; + + bool compact(const uint8_t* data, std::size_t size, ByteBuffer& out, + CompactionStats& stats) const; + bool compact(const ByteBuffer& in, ByteBuffer& out, CompactionStats& stats) const; +}; + +} // namespace vf