diff --git a/core/src/RegionCompact.cpp b/core/src/RegionCompact.cpp index 3841d6c..c735b5d 100644 --- a/core/src/RegionCompact.cpp +++ b/core/src/RegionCompact.cpp @@ -157,3 +157,104 @@ int RegionCompactor::fragmentationPercent(const uint8_t* data, std::size_t size) return static_cast((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(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 fragments = survey(data, size); + std::vector 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 coords; + std::vector 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(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 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(divideUp(out.size(), sectorBytes)); + stats.fragmentationAfter = fragmentationPercent(out.data(), out.size()); + stats.ok = true; + return true; +} + +} // namespace vf