From 6e6a878a9b6bd2e1590e6d6766f93ddd68a3ba9e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 12:57:51 +0200 Subject: [PATCH] test: cover markedChunk and meshWithQuads --- tests/testChunkCache.cpp | 158 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 tests/testChunkCache.cpp diff --git a/tests/testChunkCache.cpp b/tests/testChunkCache.cpp new file mode 100644 index 0000000..506d18c --- /dev/null +++ b/tests/testChunkCache.cpp @@ -0,0 +1,158 @@ +#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); +