diff --git a/core/src/ChunkCache.cpp b/core/src/ChunkCache.cpp new file mode 100644 index 0000000..1f0ca2b --- /dev/null +++ b/core/src/ChunkCache.cpp @@ -0,0 +1,159 @@ +#include "vf/ChunkCache.h" + +#include +#include + +namespace vf { + +namespace { + +bool coordLess(const Vec3i& a, const Vec3i& b) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + return a.z < b.z; +} + +std::uint64_t meshBytes(const ChunkMesh& mesh) { + std::uint64_t total = 0; + total += static_cast(mesh.opaque.vertices.size()) * sizeof(Vertex); + total += static_cast(mesh.opaque.indices.size()) * sizeof(std::uint32_t); + total += static_cast(mesh.transparent.vertices.size()) * sizeof(Vertex); + total += static_cast(mesh.transparent.indices.size()) * sizeof(std::uint32_t); + return total; +} + +} // namespace + +ChunkCache::ChunkCache(std::uint64_t byteBudget, CachePolicy policy) + : budget(byteBudget), replacement(policy) {} + +std::uint64_t ChunkCache::entrySizeBytes(const Chunk& chunk, const ChunkMesh& mesh) { + return static_cast(chunk.raw().size() * sizeof(Block)) + meshBytes(mesh); +} + +void ChunkCache::setByteBudget(std::uint64_t bytes) { + std::unique_lock lock(mutex); + budget = bytes; + enforceBudgetLocked(); +} + +void ChunkCache::setPolicy(CachePolicy policy) { + std::unique_lock lock(mutex); + replacement = policy; +} + +void ChunkCache::setDistanceWeight(std::uint64_t weight) { + std::unique_lock lock(mutex); + distanceWeight = weight; +} + +void ChunkCache::setObserver(const Vec3i& chunkCoord) { + std::unique_lock lock(mutex); + observerChunk = chunkCoord; + for (auto& slot : entries) slot.second.distanceSq = distanceSqTo(slot.first); +} + +std::uint64_t ChunkCache::byteBudget() const { + std::shared_lock lock(mutex); + return budget; +} + +CachePolicy ChunkCache::policy() const { + std::shared_lock lock(mutex); + return replacement; +} + +Vec3i ChunkCache::observer() const { + std::shared_lock lock(mutex); + return observerChunk; +} + +long long ChunkCache::distanceSqTo(const Vec3i& coord) const { + long long dx = coord.x - observerChunk.x; + long long dy = coord.y - observerChunk.y; + long long dz = coord.z - observerChunk.z; + return dx * dx + dy * dy + dz * dz; +} + +std::uint64_t ChunkCache::evictionScoreLocked(const CacheEntry& entry) const { + std::uint64_t age = clock - entry.lastUse; + if (replacement == CachePolicy::Lru) return age; + return age + distanceWeight * static_cast(entry.distanceSq); +} + +void ChunkCache::touchLocked(CacheEntry& entry) { + entry.lastUse = ++clock; +} + +void ChunkCache::dropMeshLocked(CacheEntry& entry) { + used -= meshBytes(entry.mesh); + entry.mesh = ChunkMesh{}; + entry.hasMesh = false; + entry.bytes = entrySizeBytes(entry.chunk, entry.mesh); +} + +void ChunkCache::eraseLocked(const Vec3i& coord) { + auto slot = entries.find(coord); + if (slot == entries.end()) return; + used -= slot->second.bytes; + entries.erase(slot); +} + +void ChunkCache::enforceBudgetLocked() { + while (used > budget) { + const CacheEntry* victim = nullptr; + std::uint64_t bestScore = 0; + for (const auto& slot : entries) { + if (slot.second.pinned) continue; + std::uint64_t score = evictionScoreLocked(slot.second); + if (victim == nullptr || score > bestScore || + (score == bestScore && coordLess(slot.first, victim->coord))) { + victim = &slot.second; + bestScore = score; + } + } + if (victim == nullptr) return; + Vec3i victimCoord = victim->coord; + eraseLocked(victimCoord); + ++counters.evictions; + } +} + +void ChunkCache::store(const Vec3i& coord, const Chunk& chunk) { + store(coord, chunk, ChunkMesh{}); +} + +void ChunkCache::store(const Vec3i& coord, const Chunk& chunk, const ChunkMesh& mesh) { + std::unique_lock lock(mutex); + auto slot = entries.find(coord); + bool wasPinned = slot != entries.end() && slot->second.pinned; + if (slot != entries.end()) used -= slot->second.bytes; + + CacheEntry entry; + entry.coord = coord; + entry.chunk = chunk; + entry.mesh = mesh; + entry.hasMesh = !mesh.empty(); + entry.pinned = wasPinned; + entry.distanceSq = distanceSqTo(coord); + entry.bytes = entrySizeBytes(chunk, mesh); + entry.lastUse = ++clock; + used += entry.bytes; + entries[coord] = std::move(entry); + + enforceBudgetLocked(); +} + +bool ChunkCache::fetchChunk(const Vec3i& coord, Chunk& out) { + std::unique_lock lock(mutex); + auto slot = entries.find(coord); + if (slot == entries.end()) { + ++counters.misses; + return false; + } + ++counters.hits; + touchLocked(slot->second); + out = slot->second.chunk; + return true; +} +