#include "testHarness.h" #include "vf/ChunkCache.h" #include #include #include using namespace vf; namespace { Chunk markedChunk(int marker) { Chunk chunk; chunk.set(0, 0, 0, Block::Stone); chunk.set(1, 0, 0, static_cast(marker)); return chunk; } ChunkMesh meshWithQuads(int quads, float marker) { ChunkMesh mesh; for (int quad = 0; quad < quads; ++quad) { std::uint32_t start = static_cast(mesh.opaque.vertices.size()); for (int corner = 0; corner < 4; ++corner) { Vertex vertex; vertex.px = marker; vertex.py = static_cast(quad); vertex.pz = static_cast(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(coord.x))); } } // namespace VF_TEST(entrySizeIsMeasuredFromTheRealPayload) { std::uint64_t blocks = static_cast(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(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(3)); CHECK_EQ(cache.bytesUsed(), entry * 3); CHECK(cache.bytesUsed() <= cache.byteBudget()); CHECK_EQ(cache.stats().evictions, static_cast(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(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(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 order = cache.evictionOrder(); CHECK_EQ(order.size(), static_cast(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(1)); std::vector 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);