feat: extend RegionCompact

This commit is contained in:
dualfroz committed 2026-09-08 21:29:25 +02:00
1 parent 5dd69bfdf9
commit a7f26fa0ee
1 file changed
+101
+101
View File
@@ -157,3 +157,104 @@ int RegionCompactor::fragmentationPercent(const uint8_t* data, std::size_t size)
return static_cast<int>((reclaimable * 100u) / measure.totalSectors);
}
bool RegionCompactor::compact(const ByteBuffer& in, ByteBuffer& out,
CompactionStats& stats) const {
return compact(in.data(), in.size(), out, stats);
}
bool RegionCompactor::compact(const uint8_t* data, std::size_t size, ByteBuffer& out,
CompactionStats& stats) const {
stats = CompactionStats{};
LayoutMeasure measure = measureLayout(data, size);
if (!measure.ok) {
stats.failure = "buffer is not a readable save container";
return false;
}
SaveScanner scanner;
IntegrityReport report = scanner.scan(data, size);
if (report.countAtLeast(IssueSeverity::Recoverable) != 0) {
stats.failure = "container reports " +
std::to_string(report.countAtLeast(IssueSeverity::Recoverable)) +
" unresolved integrity findings; repair it before compacting";
return false;
}
SaveVersion version = static_cast<SaveVersion>(measure.header.version);
uint32_t sectorBytes = measure.header.sectorBytes;
stats.sectorBytes = sectorBytes;
stats.sectorsBefore = measure.totalSectors;
stats.headerSectors = measure.headerSectors;
stats.referencedSectors = measure.referencedSectors;
stats.holeSectors = measure.holeSectors;
stats.excessSectors = measure.excessSectors;
stats.slackBytes = measure.slackBytes;
stats.bytesBefore = size;
stats.fragmentationBefore = fragmentationPercent(data, size);
std::vector<ChunkFragment> fragments = survey(data, size);
std::vector<std::size_t> order;
for (std::size_t i = 0; i < fragments.size(); ++i) {
++stats.operations;
if (dropEmptyChunks && fragments[i].empty) {
++stats.chunksDropped;
++stats.operations;
continue;
}
order.push_back(i);
}
std::sort(order.begin(), order.end(), [&](std::size_t a, std::size_t b) {
return saveCoordLess(fragments[a].coord, fragments[b].coord);
});
std::vector<Vec3i> coords;
std::vector<ByteBuffer> payloads;
coords.reserve(order.size());
payloads.reserve(order.size());
bool repack = repackWithStrongerCompression && version == currentSaveVersion;
for (std::size_t index : order) {
const ChunkFragment& fragment = fragments[index];
std::size_t at = static_cast<std::size_t>(fragment.sectorOffset) * sectorBytes;
ByteBuffer payload(data + at, data + at + fragment.payloadBytes);
if (repack) {
SaveChunkRecord record;
record.coord = fragment.coord;
if (!decodeChunkPayload(version, payload.data(), payload.size(), record, nullptr)) {
stats.failure = "a payload stopped decoding while repacking";
return false;
}
ByteBuffer repacked = encodeChunkPayload(version, record, true);
if (repacked.size() < payload.size()) {
payload = std::move(repacked);
++stats.chunksRepacked;
++stats.operations;
}
}
coords.push_back(fragment.coord);
payloads.push_back(std::move(payload));
}
std::vector<uint32_t> newOffsets;
out = packSaveContainer(version, measure.header.regionX, measure.header.regionZ, sectorBytes,
coords, payloads, &newOffsets);
++stats.operations;
for (std::size_t i = 0; i < order.size(); ++i) {
if (fragments[order[i]].sectorOffset != newOffsets[i]) {
++stats.chunksMoved;
++stats.operations;
}
}
stats.chunksKept = coords.size();
stats.bytesAfter = out.size();
stats.bytesReclaimed = size > out.size() ? size - out.size() : 0;
stats.sectorsAfter = static_cast<uint32_t>(divideUp(out.size(), sectorBytes));
stats.fragmentationAfter = fragmentationPercent(out.data(), out.size());
stats.ok = true;
return true;
}
} // namespace vf