From fe22ed9085311f5fb5cbd5c092b6380ffdb0c768 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 13:14:24 +0200 Subject: [PATCH] test: cover testChunkCache --- tests/testChunkCache.cpp | 187 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/tests/testChunkCache.cpp b/tests/testChunkCache.cpp index 506d18c..ea5b797 100644 --- a/tests/testChunkCache.cpp +++ b/tests/testChunkCache.cpp @@ -156,3 +156,190 @@ VF_TEST(distanceWeightedPolicyDropsFarChunksFirst) { cache.setPolicy(CachePolicy::DistanceWeighted); CHECK(cache.evictionOrder().front() == distant); + std::uint64_t entry = sizeOfEntry(4); + ChunkCache tight(entry, CachePolicy::DistanceWeighted); + tight.setObserver({0, 0, 0}); + storeMarked(tight, nearby, 4); + storeMarked(tight, distant, 4); + CHECK(tight.contains(nearby)); + CHECK(!tight.contains(distant)); + + ChunkCache recencyOnly(entry, CachePolicy::Lru); + recencyOnly.setObserver({0, 0, 0}); + storeMarked(recencyOnly, nearby, 4); + storeMarked(recencyOnly, distant, 4); + CHECK(!recencyOnly.contains(nearby)); + CHECK(recencyOnly.contains(distant)); +} + +VF_TEST(observerMoveRescoresResidentEntries) { + ChunkCache cache(1ull << 30, CachePolicy::DistanceWeighted); + cache.setObserver({0, 0, 0}); + storeMarked(cache, {0, 0, 0}, 4); + storeMarked(cache, {8, 0, 0}, 4); + CHECK(cache.evictionOrder().front() == (Vec3i{8, 0, 0})); + + cache.setObserver({8, 0, 0}); + CHECK(cache.observer() == (Vec3i{8, 0, 0})); + CHECK(cache.evictionOrder().front() == (Vec3i{0, 0, 0})); +} + +VF_TEST(blockEditDropsTheOwnerAndTheMeshesAcrossThatBorder) { + ChunkCache cache(1ull << 30, CachePolicy::Lru); + const Vec3i neighbours[] = {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; + storeMarked(cache, {0, 0, 0}, 8); + for (const Vec3i& coord : neighbours) storeMarked(cache, coord, 8); + std::uint64_t before = cache.bytesUsed(); + + std::size_t dropped = cache.invalidateBlock(chunkSize - 1, 5, 5); + CHECK_EQ(dropped, static_cast(1)); + CHECK(!cache.contains({0, 0, 0})); + CHECK(cache.contains({1, 0, 0})); + CHECK(!cache.hasMesh({1, 0, 0})); // its ambient occlusion read the edit + CHECK(cache.hasMesh({-1, 0, 0})); + CHECK(cache.hasMesh({0, 1, 0})); + CHECK(cache.hasMesh({0, 0, 1})); + + CHECK_EQ(cache.bytesUsed(), before - sizeOfEntry(8) - (sizeOfEntry(8) - sizeOfEntry(0))); + CacheStats counted = cache.stats(); + CHECK_EQ(counted.invalidations, static_cast(1)); + CHECK_EQ(counted.meshInvalidations, static_cast(1)); +} + +VF_TEST(cornerBlockEditCascadesToEverySharingNeighbour) { + ChunkCache cache(1ull << 30, CachePolicy::Lru); + storeMarked(cache, {0, 0, 0}, 3); + for (int dx = 0; dx <= 1; ++dx) + for (int dy = 0; dy <= 1; ++dy) + for (int dz = 0; dz <= 1; ++dz) + if (dx || dy || dz) storeMarked(cache, {dx, dy, dz}, 3); + storeMarked(cache, {-1, 0, 0}, 3); + + std::size_t dropped = cache.invalidateBlock(chunkSize - 1, chunkSize - 1, chunkSize - 1); + CHECK_EQ(dropped, static_cast(7)); + CHECK(cache.hasMesh({-1, 0, 0})); // opposite side, untouched + CHECK(!cache.hasMesh({1, 1, 1})); + CHECK(!cache.hasMesh({0, 0, 1})); + CHECK_EQ(cache.stats().meshInvalidations, static_cast(7)); +} + +VF_TEST(interiorBlockEditLeavesNeighbourMeshesAlone) { + ChunkCache cache(1ull << 30, CachePolicy::Lru); + storeMarked(cache, {0, 0, 0}, 5); + storeMarked(cache, {1, 0, 0}, 5); + storeMarked(cache, {0, 0, 1}, 5); + + std::size_t dropped = cache.invalidateBlock(5, 5, 5); + CHECK_EQ(dropped, static_cast(0)); + CHECK(!cache.contains({0, 0, 0})); + CHECK(cache.hasMesh({1, 0, 0})); + CHECK(cache.hasMesh({0, 0, 1})); + CHECK_EQ(cache.stats().meshInvalidations, static_cast(0)); +} + +VF_TEST(statsTrackHitsMissesAndHitRate) { + ChunkCache cache(1ull << 30, CachePolicy::Lru); + storeMarked(cache, {0, 0, 0}, 2); + + Chunk chunk; + ChunkMesh mesh; + CHECK(cache.fetchChunk({0, 0, 0}, chunk)); + CHECK(cache.fetchMesh({0, 0, 0}, mesh)); + CHECK(cache.fetchChunk({0, 0, 0}, chunk)); + CHECK(!cache.fetchChunk({7, 0, 0}, chunk)); + + CacheStats counted = cache.stats(); + CHECK_EQ(counted.hits, static_cast(3)); + CHECK_EQ(counted.misses, static_cast(1)); + CHECK_NEAR(counted.hitRate(), 0.75, 1e-9); + CHECK_EQ(counted.entries, static_cast(1)); + CHECK_EQ(counted.bytesUsed, sizeOfEntry(2)); + + CHECK_EQ(mesh.opaque.quadCount(), static_cast(2)); + cache.resetStats(); + CHECK_EQ(cache.stats().hits, static_cast(0)); + CHECK_NEAR(cache.stats().hitRate(), 0.0, 1e-9); +} + +VF_TEST(peekDoesNotDisturbRecencyOrStatistics) { + ChunkCache cache(1ull << 30, CachePolicy::Lru); + storeMarked(cache, {0, 0, 0}, 2); + storeMarked(cache, {1, 0, 0}, 2); + + Chunk chunk; + CHECK(cache.peekChunk({0, 0, 0}, chunk)); + CHECK(!cache.peekChunk({5, 0, 0}, chunk)); + + CacheStats counted = cache.stats(); + CHECK_EQ(counted.hits, static_cast(0)); + CHECK_EQ(counted.misses, static_cast(0)); + CHECK(cache.evictionOrder().front() == (Vec3i{0, 0, 0})); +} + +VF_TEST(storingOverAnEntryKeepsByteAccountingAndPinning) { + ChunkCache cache(1ull << 30, CachePolicy::Lru); + storeMarked(cache, {0, 0, 0}, 12); + cache.pin({0, 0, 0}); + CHECK_EQ(cache.bytesUsed(), sizeOfEntry(12)); + + storeMarked(cache, {0, 0, 0}, 3); + CHECK_EQ(cache.size(), static_cast(1)); + CHECK_EQ(cache.bytesUsed(), sizeOfEntry(3)); + CHECK(cache.isPinned({0, 0, 0})); + + CacheEntry entry; + CHECK(cache.entryFor({0, 0, 0}, entry)); + CHECK_EQ(entry.mesh.opaque.quadCount(), static_cast(3)); + CHECK(entry.hasMesh); + CHECK(entry.pinned); +} + +VF_TEST(clearEmptiesTheCache) { + ChunkCache cache(1ull << 30, CachePolicy::Lru); + for (int i = 0; i < 5; ++i) storeMarked(cache, {i, 0, 0}, 4); + CHECK_EQ(cache.size(), static_cast(5)); + + cache.clear(); + CHECK_EQ(cache.size(), static_cast(0)); + CHECK_EQ(cache.bytesUsed(), static_cast(0)); + CHECK(cache.residentCoords().empty()); + CHECK(!cache.contains({0, 0, 0})); +} + +VF_TEST(concurrentReadersNeverSeeAHalfWrittenEntry) { + ChunkCache cache(1ull << 30, CachePolicy::Lru); + const int coordCount = 8; + for (int i = 0; i < coordCount; ++i) storeMarked(cache, {i, 0, 0}, 4 + i); + + std::atomic mismatches{0}; + std::atomic observed{0}; + std::atomic stop{false}; + std::vector readers; + for (int reader = 0; reader < 4; ++reader) { + readers.emplace_back([&] { + while (!stop.load(std::memory_order_acquire)) { + for (int i = 0; i < coordCount; ++i) { + ChunkMesh mesh; + if (!cache.peekMesh({i, 0, 0}, mesh)) continue; + observed.fetch_add(1); + if (mesh.opaque.quadCount() != static_cast(4 + i)) + mismatches.fetch_add(1); + else if (mesh.opaque.vertices[0].px != static_cast(i)) + mismatches.fetch_add(1); + if (mesh.opaque.indices.size() != mesh.opaque.quadCount() * 6) + mismatches.fetch_add(1); + } + } + }); + } + + for (int round = 0; round < 150; ++round) + for (int i = 0; i < coordCount; ++i) storeMarked(cache, {i, 0, 0}, 4 + i); + + stop.store(true, std::memory_order_release); + for (std::thread& reader : readers) reader.join(); + + CHECK_EQ(mismatches.load(), 0); + CHECK(observed.load() > 0); + CHECK_EQ(cache.size(), static_cast(coordCount)); +}