97 lines
3.2 KiB
C++
97 lines
3.2 KiB
C++
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#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<ChunkFragment> survey(const uint8_t* data, std::size_t size) const;
|
|
std::vector<ChunkFragment> 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
|