Files
voxelforge/tests/testChunkCache.cpp
T

159 lines
5.3 KiB
C++

#include "testHarness.h"
#include "vf/ChunkCache.h"
#include <atomic>
#include <thread>
#include <vector>
using namespace vf;
namespace {
Chunk markedChunk(int marker) {
Chunk chunk;
chunk.set(0, 0, 0, Block::Stone);
chunk.set(1, 0, 0, static_cast<Block>(marker));
return chunk;
}
ChunkMesh meshWithQuads(int quads, float marker) {
ChunkMesh mesh;
for (int quad = 0; quad < quads; ++quad) {
std::uint32_t start = static_cast<std::uint32_t>(mesh.opaque.vertices.size());
for (int corner = 0; corner < 4; ++corner) {
Vertex vertex;
vertex.px = marker;
vertex.py = static_cast<float>(quad);
vertex.pz = static_cast<float>(corner);
vertex.nx = 0.0f;
vertex.ny = 1.0f;
vertex.nz = 0.0f;
vertex.u = corner == 1 || corner == 2 ? 1.0f : 0.0f;
vertex.v = corner >= 2 ? 1.0f : 0.0f;
vertex.tile = marker;
vertex.light = 1.0f;
vertex.ao = 1.0f;
mesh.opaque.vertices.push_back(vertex);
}
const std::uint32_t triangles[6] = {start, start + 1, start + 2,
start, start + 2, start + 3};
for (std::uint32_t index : triangles) mesh.opaque.indices.push_back(index);
}
return mesh;
}
std::uint64_t sizeOfEntry(int quads) {
return ChunkCache::entrySizeBytes(markedChunk(1), meshWithQuads(quads, 0.0f));
}
void storeMarked(ChunkCache& cache, const Vec3i& coord, int quads) {
cache.store(coord, markedChunk(coord.x), meshWithQuads(quads, static_cast<float>(coord.x)));
}
} // namespace
VF_TEST(entrySizeIsMeasuredFromTheRealPayload) {
std::uint64_t blocks = static_cast<std::uint64_t>(chunkVolume) * sizeof(Block);
std::uint64_t perQuad = 4 * sizeof(Vertex) + 6 * sizeof(std::uint32_t);
CHECK_EQ(sizeOfEntry(0), blocks);
CHECK_EQ(sizeOfEntry(10), blocks + 10 * perQuad);
ChunkCache cache(1ull << 30, CachePolicy::Lru);
storeMarked(cache, {0, 0, 0}, 10);
CHECK_EQ(cache.bytesUsed(), sizeOfEntry(10));
storeMarked(cache, {1, 0, 0}, 4);
CHECK_EQ(cache.bytesUsed(), sizeOfEntry(10) + sizeOfEntry(4));
CHECK_EQ(cache.size(), static_cast<std::size_t>(2));
}
VF_TEST(hardByteBudgetEvictsUntilItFits) {
std::uint64_t entry = sizeOfEntry(10);
ChunkCache cache(entry * 3, CachePolicy::Lru);
for (int i = 0; i < 4; ++i) storeMarked(cache, {i, 0, 0}, 10);
CHECK_EQ(cache.size(), static_cast<std::size_t>(3));
CHECK_EQ(cache.bytesUsed(), entry * 3);
CHECK(cache.bytesUsed() <= cache.byteBudget());
CHECK_EQ(cache.stats().evictions, static_cast<std::uint64_t>(1));
CHECK(!cache.contains({0, 0, 0}));
CHECK(cache.contains({3, 0, 0}));
}
VF_TEST(lruEvictsTheLeastRecentlyUsedEntry) {
std::uint64_t entry = sizeOfEntry(6);
ChunkCache cache(entry * 2, CachePolicy::Lru);
storeMarked(cache, {0, 0, 0}, 6);
storeMarked(cache, {1, 0, 0}, 6);
Chunk fetched;
CHECK(cache.fetchChunk({0, 0, 0}, fetched)); // refreshes the older entry
storeMarked(cache, {2, 0, 0}, 6);
CHECK_EQ(cache.size(), static_cast<std::size_t>(2));
CHECK(cache.contains({0, 0, 0}));
CHECK(!cache.contains({1, 0, 0}));
CHECK(cache.contains({2, 0, 0}));
CHECK_EQ(cache.stats().evictions, static_cast<std::uint64_t>(1));
}
VF_TEST(fetchMovesAnEntryToTheBackOfTheEvictionQueue) {
ChunkCache cache(1ull << 30, CachePolicy::Lru);
for (int i = 0; i < 3; ++i) storeMarked(cache, {i, 0, 0}, 2);
std::vector<Vec3i> order = cache.evictionOrder();
CHECK_EQ(order.size(), static_cast<std::size_t>(3));
CHECK(order[0] == (Vec3i{0, 0, 0}));
CHECK(order[2] == (Vec3i{2, 0, 0}));
Chunk fetched;
CHECK(cache.fetchChunk({0, 0, 0}, fetched));
order = cache.evictionOrder();
CHECK(order[0] == (Vec3i{1, 0, 0}));
CHECK(order[2] == (Vec3i{0, 0, 0}));
}
VF_TEST(pinnedEntryIsNeverEvicted) {
std::uint64_t entry = sizeOfEntry(6);
ChunkCache cache(entry, CachePolicy::Lru);
storeMarked(cache, {0, 0, 0}, 6);
cache.pin({0, 0, 0});
CHECK(cache.isPinned({0, 0, 0}));
for (int i = 1; i <= 5; ++i) storeMarked(cache, {i, 0, 0}, 6);
CHECK(cache.contains({0, 0, 0}));
CHECK_EQ(cache.size(), static_cast<std::size_t>(1));
std::vector<Vec3i> order = cache.evictionOrder();
CHECK(order.empty()); // nothing left that may be dropped
}
VF_TEST(unpinningMakesAnEntryEvictableAgain) {
std::uint64_t entry = sizeOfEntry(6);
ChunkCache cache(entry, CachePolicy::Lru);
storeMarked(cache, {0, 0, 0}, 6);
cache.pin({0, 0, 0});
storeMarked(cache, {1, 0, 0}, 6);
CHECK(cache.contains({0, 0, 0}));
CHECK(!cache.contains({1, 0, 0}));
cache.unpin({0, 0, 0});
CHECK(!cache.isPinned({0, 0, 0}));
storeMarked(cache, {1, 0, 0}, 6);
CHECK(!cache.contains({0, 0, 0}));
CHECK(cache.contains({1, 0, 0}));
}
VF_TEST(distanceWeightedPolicyDropsFarChunksFirst) {
const Vec3i nearby{0, 0, 0};
const Vec3i distant{20, 0, 0};
ChunkCache cache(1ull << 30, CachePolicy::Lru);
cache.setObserver({0, 0, 0});
storeMarked(cache, nearby, 4);
storeMarked(cache, distant, 4);
CHECK(cache.evictionOrder().front() == nearby); // pure recency picks the older one
cache.setPolicy(CachePolicy::DistanceWeighted);
CHECK(cache.evictionOrder().front() == distant);