Files
voxelforge/tests/testRegionCompact.cpp
T

157 lines
6.0 KiB
C++

#include "testHarness.h"
#include "vf/Block.h"
#include "vf/RegionCompact.h"
#include "vf/SaveIntegrity.h"
#include "vf/SaveMigration.h"
#include <cstdint>
#include <vector>
using namespace vf;
namespace {
Chunk uniformChunk(Block block) {
Chunk chunk;
for (int x = 0; x < chunkSize; ++x)
for (int y = 0; y < chunkSize; ++y)
for (int z = 0; z < chunkSize; ++z) chunk.set(x, y, z, block);
return chunk;
}
Chunk busyChunk(int seed) {
Chunk chunk;
int cell = 0;
for (int x = 0; x < chunkSize; ++x)
for (int y = 0; y < chunkSize; ++y)
for (int z = 0; z < chunkSize; ++z) {
int value = (cell + seed) % static_cast<int>(Block::Count);
chunk.set(x, y, z, static_cast<Block>(value));
++cell;
}
return chunk;
}
SaveChunkRecord buildRecord(const Vec3i& coord, const Chunk& blocks) {
SaveChunkRecord record;
record.coord = coord;
record.blocks = blocks;
record.light = defaultChunkLight(blocks, saveDefaultSkyLight);
return record;
}
SaveContainer uniformContainer(int chunkTotal) {
SaveContainer container;
container.version = SaveVersion::V4;
for (int i = 0; i < chunkTotal; ++i)
container.chunks.push_back(buildRecord(Vec3i{i, 0, 0}, uniformChunk(Block::Stone)));
return container;
}
std::vector<Vec3i> coordsOf(const ByteBuffer& buffer) {
std::vector<Vec3i> coords;
SaveHeader header;
if (!readSaveHeader(buffer.data(), buffer.size(), header)) return coords;
std::vector<SaveTableEntry> entries;
if (!readSaveTable(buffer.data(), buffer.size(), header, entries)) return coords;
for (const SaveTableEntry& entry : entries) coords.push_back(entry.coord);
return coords;
}
// A layout written by hand so the fragmentation arithmetic is known before the
// code runs: 13 sectors of 64 bytes, 2 header sectors, runs of 4 and 3 sectors
// at sector 2 and sector 10, so 4 sectors are holes and 1 sector inside the
// second run is reserved for nothing.
ByteBuffer handBuiltLayout() {
const uint32_t sectorBytes = 64;
ByteBuffer buffer(13 * static_cast<std::size_t>(sectorBytes), 0);
saveWriteU32At(buffer.data(), buffer.size(), 0, saveMagic);
saveWriteU32At(buffer.data(), buffer.size(), 4, static_cast<uint32_t>(SaveVersion::V4));
saveWriteU32At(buffer.data(), buffer.size(), 8, 0u);
saveWriteU32At(buffer.data(), buffer.size(), 12, 0u);
saveWriteU32At(buffer.data(), buffer.size(), 16, 2u);
saveWriteU32At(buffer.data(), buffer.size(), 20, sectorBytes);
std::size_t at = saveHeaderBytes;
saveWriteU32At(buffer.data(), buffer.size(), at + 0, 0u);
saveWriteU32At(buffer.data(), buffer.size(), at + 4, 0u);
saveWriteU32At(buffer.data(), buffer.size(), at + 8, 0u);
saveWriteU32At(buffer.data(), buffer.size(), at + 12, 2u);
saveWriteU32At(buffer.data(), buffer.size(), at + 16, 4u);
saveWriteU32At(buffer.data(), buffer.size(), at + 20, 200u);
at += saveEntryBytes;
saveWriteU32At(buffer.data(), buffer.size(), at + 0, 0u);
saveWriteU32At(buffer.data(), buffer.size(), at + 4, 0u);
saveWriteU32At(buffer.data(), buffer.size(), at + 8, 1u);
saveWriteU32At(buffer.data(), buffer.size(), at + 12, 10u);
saveWriteU32At(buffer.data(), buffer.size(), at + 16, 3u);
saveWriteU32At(buffer.data(), buffer.size(), at + 20, 100u);
uint32_t checksum = fnv1a32(buffer.data() + saveHeaderBytes, 2 * saveEntryBytes);
saveWriteU32At(buffer.data(), buffer.size(), 24, checksum);
return buffer;
}
} // namespace
VF_TEST(fragmentationOfAHandBuiltLayoutMatchesTheArithmetic) {
ByteBuffer buffer = handBuiltLayout();
RegionCompactor compactor;
CHECK_EQ(compactor.fragmentationPercent(buffer), 38);
std::vector<ChunkFragment> fragments = compactor.survey(buffer);
CHECK_EQ(fragments.size(), static_cast<std::size_t>(2));
if (fragments.size() != 2) return;
CHECK_EQ(fragments[0].sectorOffset, static_cast<uint32_t>(2));
CHECK_EQ(fragments[0].sectorCount, static_cast<uint32_t>(4));
CHECK_EQ(fragments[0].payloadBytes, static_cast<uint32_t>(200));
CHECK_EQ(fragments[0].neededSectors, static_cast<uint32_t>(4));
CHECK_EQ(fragments[0].excessSectors, static_cast<uint32_t>(0));
CHECK_EQ(fragments[0].slackBytes, static_cast<uint32_t>(56));
CHECK(fragments[0].reachable);
CHECK_EQ(fragments[1].sectorOffset, static_cast<uint32_t>(10));
CHECK_EQ(fragments[1].sectorCount, static_cast<uint32_t>(3));
CHECK_EQ(fragments[1].neededSectors, static_cast<uint32_t>(2));
CHECK_EQ(fragments[1].excessSectors, static_cast<uint32_t>(1));
CHECK_EQ(fragments[1].slackBytes, static_cast<uint32_t>(92));
CHECK(fragments[1].reachable);
}
VF_TEST(aFreshContainerIsCanonicalAndUnfragmented) {
ByteBuffer buffer = writeSaveContainer(uniformContainer(4));
RegionCompactor compactor;
CHECK_EQ(compactor.fragmentationPercent(buffer), 0);
CHECK(isCanonicalSaveLayout(buffer.data(), buffer.size()));
ByteBuffer out;
CompactionStats stats;
CHECK(compactor.compact(buffer, out, stats));
CHECK(stats.ok);
CHECK(out == buffer);
CHECK_EQ(stats.chunksMoved, static_cast<std::size_t>(0));
CHECK_EQ(stats.chunksDropped, static_cast<std::size_t>(0));
CHECK_EQ(stats.chunksKept, static_cast<std::size_t>(4));
CHECK_EQ(stats.bytesReclaimed, static_cast<std::size_t>(0));
CHECK_EQ(stats.holeSectors, static_cast<uint32_t>(0));
CHECK_EQ(stats.excessSectors, static_cast<uint32_t>(0));
CHECK_EQ(stats.fragmentationBefore, 0);
CHECK_EQ(stats.fragmentationAfter, 0);
CHECK_EQ(stats.operations, static_cast<std::size_t>(5));
}
VF_TEST(aChunkThatOutgrowsItsRunMovesToTheEndAndLeavesAHole) {
ByteBuffer buffer = writeSaveContainer(uniformContainer(3));
RegionCompactor compactor;
std::vector<ChunkFragment> before = compactor.survey(buffer);
uint32_t abandoned = before[1].sectorCount;
std::size_t sizeBefore = buffer.size();
CHECK(updateChunkInPlace(buffer, buildRecord(Vec3i{1, 0, 0}, busyChunk(3)), false));
CHECK(buffer.size() > sizeBefore);