feat: add LayoutMeasure and measureLayout

This commit is contained in:
dualfroz committed 2026-09-08 21:28:55 +02:00
1 parent 71a936867b
commit 5dd69bfdf9
1 file changed
+159
+159
View File
@@ -0,0 +1,159 @@
#include "vf/RegionCompact.h"
#include <algorithm>
namespace vf {
namespace {
std::size_t divideUp(std::size_t value, std::size_t unit) {
return unit == 0 ? 0 : (value + unit - 1) / unit;
}
struct LayoutMeasure {
bool ok = false;
SaveHeader header;
std::vector<SaveTableEntry> entries;
uint32_t totalSectors = 0;
uint32_t headerSectors = 0;
uint32_t referencedSectors = 0;
uint32_t holeSectors = 0;
uint32_t excessSectors = 0;
uint32_t slackBytes = 0;
};
LayoutMeasure measureLayout(const uint8_t* data, std::size_t size) {
LayoutMeasure measure;
if (!readSaveHeader(data, size, measure.header)) return measure;
if (measure.header.magic != saveMagic) return measure;
if (!isKnownSaveVersion(measure.header.version)) return measure;
if (measure.header.sectorBytes == 0) return measure;
if (!readSaveTable(data, size, measure.header, measure.entries)) return measure;
uint32_t sectorBytes = measure.header.sectorBytes;
measure.totalSectors = static_cast<uint32_t>(divideUp(size, sectorBytes));
measure.headerSectors = static_cast<uint32_t>(saveHeaderSectors(measure.header));
for (const SaveTableEntry& entry : measure.entries) {
measure.referencedSectors += entry.sectorCount;
uint32_t needed =
static_cast<uint32_t>(divideUp(entry.payloadBytes, sectorBytes));
if (entry.sectorCount > needed) measure.excessSectors += entry.sectorCount - needed;
uint32_t runBytes = entry.sectorCount * sectorBytes;
if (runBytes > entry.payloadBytes) measure.slackBytes += runBytes - entry.payloadBytes;
}
uint32_t accounted = measure.headerSectors + measure.referencedSectors;
measure.holeSectors = measure.totalSectors > accounted ? measure.totalSectors - accounted : 0;
measure.ok = true;
return measure;
}
void rewriteTableChecksum(ByteBuffer& buffer, const SaveHeader& header) {
if (!versionHasChecksum(static_cast<SaveVersion>(header.version))) return;
std::size_t tableBytes = saveTableBytes(header);
uint32_t checksum = fnv1a32(buffer.data() + saveHeaderBytes, tableBytes);
saveWriteU32At(buffer.data(), buffer.size(), 24, checksum);
}
} // namespace
bool updateChunkInPlace(ByteBuffer& buffer, const SaveChunkRecord& record, bool compressBody) {
SaveHeader header;
if (!readSaveHeader(buffer.data(), buffer.size(), header)) return false;
if (header.magic != saveMagic || !isKnownSaveVersion(header.version)) return false;
if (header.sectorBytes == 0) return false;
std::vector<SaveTableEntry> entries;
if (!readSaveTable(buffer.data(), buffer.size(), header, entries)) return false;
std::size_t slot = entries.size();
for (std::size_t i = 0; i < entries.size(); ++i)
if (entries[i].coord == record.coord) slot = i;
if (slot == entries.size()) return false;
SaveVersion version = static_cast<SaveVersion>(header.version);
ByteBuffer payload = encodeChunkPayload(version, record, compressBody);
uint32_t sectorBytes = header.sectorBytes;
uint32_t needed = static_cast<uint32_t>(divideUp(payload.size(), sectorBytes));
std::size_t entryAt = saveHeaderBytes + slot * saveEntryBytes;
if (needed <= entries[slot].sectorCount) {
std::size_t at = static_cast<std::size_t>(entries[slot].sectorOffset) * sectorBytes;
std::size_t runBytes = static_cast<std::size_t>(entries[slot].sectorCount) * sectorBytes;
if (at + runBytes > buffer.size()) return false;
std::fill(buffer.begin() + static_cast<std::ptrdiff_t>(at),
buffer.begin() + static_cast<std::ptrdiff_t>(at + runBytes), uint8_t{0});
std::copy(payload.begin(), payload.end(),
buffer.begin() + static_cast<std::ptrdiff_t>(at));
saveWriteU32At(buffer.data(), buffer.size(), entryAt + 20,
static_cast<uint32_t>(payload.size()));
rewriteTableChecksum(buffer, header);
return true;
}
if (buffer.size() % sectorBytes != 0)
buffer.resize(divideUp(buffer.size(), sectorBytes) * sectorBytes, 0);
uint32_t newOffset = static_cast<uint32_t>(buffer.size() / sectorBytes);
buffer.resize(buffer.size() + static_cast<std::size_t>(needed) * sectorBytes, 0);
std::copy(payload.begin(), payload.end(),
buffer.begin() + static_cast<std::ptrdiff_t>(
static_cast<std::size_t>(newOffset) * sectorBytes));
saveWriteU32At(buffer.data(), buffer.size(), entryAt + 12, newOffset);
saveWriteU32At(buffer.data(), buffer.size(), entryAt + 16, needed);
saveWriteU32At(buffer.data(), buffer.size(), entryAt + 20,
static_cast<uint32_t>(payload.size()));
rewriteTableChecksum(buffer, header);
return true;
}
std::vector<ChunkFragment> RegionCompactor::survey(const ByteBuffer& buffer) const {
return survey(buffer.data(), buffer.size());
}
std::vector<ChunkFragment> RegionCompactor::survey(const uint8_t* data, std::size_t size) const {
std::vector<ChunkFragment> fragments;
LayoutMeasure measure = measureLayout(data, size);
if (!measure.ok) return fragments;
uint32_t sectorBytes = measure.header.sectorBytes;
SaveVersion version = static_cast<SaveVersion>(measure.header.version);
for (std::size_t i = 0; i < measure.entries.size(); ++i) {
const SaveTableEntry& entry = measure.entries[i];
ChunkFragment fragment;
fragment.coord = entry.coord;
fragment.entryIndex = static_cast<int>(i);
fragment.sectorOffset = entry.sectorOffset;
fragment.sectorCount = entry.sectorCount;
fragment.payloadBytes = entry.payloadBytes;
fragment.neededSectors = static_cast<uint32_t>(divideUp(entry.payloadBytes, sectorBytes));
fragment.excessSectors = entry.sectorCount > fragment.neededSectors
? entry.sectorCount - fragment.neededSectors
: 0;
uint32_t runBytes = entry.sectorCount * sectorBytes;
fragment.slackBytes = runBytes > entry.payloadBytes ? runBytes - entry.payloadBytes : 0;
std::size_t at = static_cast<std::size_t>(entry.sectorOffset) * sectorBytes;
fragment.reachable =
entry.sectorCount != 0 && at + entry.payloadBytes <= size && at < size;
if (fragment.reachable && versionHasMetadata(version)) {
uint32_t flags = 0;
if (saveReadU32At(data, size, at, flags))
fragment.empty = (flags & saveRecordEmpty) != 0;
}
fragments.push_back(fragment);
}
return fragments;
}
int RegionCompactor::fragmentationPercent(const ByteBuffer& buffer) const {
return fragmentationPercent(buffer.data(), buffer.size());
}
int RegionCompactor::fragmentationPercent(const uint8_t* data, std::size_t size) const {
LayoutMeasure measure = measureLayout(data, size);
if (!measure.ok || measure.totalSectors == 0) return -1;
uint32_t reclaimable = measure.holeSectors + measure.excessSectors;
return static_cast<int>((reclaimable * 100u) / measure.totalSectors);
}