feat(vf): add CacheEntry and CacheStats
This commit is contained in:
1 parent
3346ae571c
commit
4d571e1fe5
1 file changed
+131
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "vf/Chunk.h"
|
||||
#include "vf/Math.h"
|
||||
#include "vf/Mesh.h"
|
||||
#include "vf/World.h"
|
||||
|
||||
namespace vf {
|
||||
|
||||
// Lru drops the entry untouched for the longest time. DistanceWeighted mixes
|
||||
// staleness with the squared chunk distance to the observer, so a chunk far
|
||||
// behind the player loses to a stale one still under his feet.
|
||||
enum class CachePolicy : std::uint8_t {
|
||||
Lru = 0,
|
||||
DistanceWeighted = 1,
|
||||
};
|
||||
|
||||
struct CacheEntry {
|
||||
Vec3i coord;
|
||||
Chunk chunk;
|
||||
ChunkMesh mesh;
|
||||
std::uint64_t bytes = 0;
|
||||
std::uint64_t lastUse = 0;
|
||||
long long distanceSq = 0;
|
||||
bool hasMesh = false;
|
||||
bool pinned = false;
|
||||
};
|
||||
|
||||
struct CacheStats {
|
||||
std::uint64_t hits = 0;
|
||||
std::uint64_t misses = 0;
|
||||
std::uint64_t evictions = 0;
|
||||
std::uint64_t invalidations = 0;
|
||||
std::uint64_t meshInvalidations = 0;
|
||||
std::uint64_t bytesUsed = 0;
|
||||
std::size_t entries = 0;
|
||||
|
||||
double hitRate() const {
|
||||
std::uint64_t lookups = hits + misses;
|
||||
if (lookups == 0) return 0.0;
|
||||
return static_cast<double>(hits) / static_cast<double>(lookups);
|
||||
}
|
||||
};
|
||||
|
||||
// Resident chunks and their baked meshes under a hard byte budget. Entries are
|
||||
// evicted by policy once a store pushes the cache past the budget; pinned
|
||||
// entries are never candidates, which is how the pipeline keeps everything
|
||||
// inside the view radius alive. Every method is safe to call concurrently:
|
||||
// peek reads share the lock, everything that touches recency or the store takes
|
||||
// it exclusively.
|
||||
class ChunkCache {
|
||||
public:
|
||||
explicit ChunkCache(std::uint64_t byteBudget = 64ull * 1024ull * 1024ull,
|
||||
CachePolicy policy = CachePolicy::Lru);
|
||||
|
||||
ChunkCache(const ChunkCache&) = delete;
|
||||
ChunkCache& operator=(const ChunkCache&) = delete;
|
||||
|
||||
void setByteBudget(std::uint64_t bytes);
|
||||
void setPolicy(CachePolicy policy);
|
||||
void setDistanceWeight(std::uint64_t weight);
|
||||
void setObserver(const Vec3i& chunkCoord);
|
||||
|
||||
std::uint64_t byteBudget() const;
|
||||
CachePolicy policy() const;
|
||||
Vec3i observer() const;
|
||||
|
||||
void store(const Vec3i& coord, const Chunk& chunk);
|
||||
void store(const Vec3i& coord, const Chunk& chunk, const ChunkMesh& mesh);
|
||||
|
||||
// Counting lookups: they record a hit or a miss and refresh recency.
|
||||
bool fetchChunk(const Vec3i& coord, Chunk& out);
|
||||
bool fetchMesh(const Vec3i& coord, ChunkMesh& out);
|
||||
|
||||
// Silent lookups for the pipeline workers: no recency, no statistics, and
|
||||
// only a shared lock, so many stages can read at once.
|
||||
bool peekChunk(const Vec3i& coord, Chunk& out) const;
|
||||
bool peekMesh(const Vec3i& coord, ChunkMesh& out) const;
|
||||
bool contains(const Vec3i& coord) const;
|
||||
bool hasMesh(const Vec3i& coord) const;
|
||||
bool entryFor(const Vec3i& coord, CacheEntry& out) const;
|
||||
|
||||
void pin(const Vec3i& coord);
|
||||
void unpin(const Vec3i& coord);
|
||||
bool isPinned(const Vec3i& coord) const;
|
||||
|
||||
// Drops the owning chunk entirely and the meshes of every neighbour whose
|
||||
// ambient occlusion or light sampling can read the edited voxel across the
|
||||
// chunk border. Returns the number of neighbour meshes dropped.
|
||||
std::size_t invalidateBlock(int gx, int gy, int gz);
|
||||
void invalidateChunk(const Vec3i& coord);
|
||||
void invalidateMesh(const Vec3i& coord);
|
||||
void clear();
|
||||
|
||||
std::size_t size() const;
|
||||
std::uint64_t bytesUsed() const;
|
||||
CacheStats stats() const;
|
||||
void resetStats();
|
||||
|
||||
// Victims first. Pinned entries are excluded because they cannot be chosen.
|
||||
std::vector<Vec3i> evictionOrder() const;
|
||||
std::vector<Vec3i> residentCoords() const;
|
||||
|
||||
static std::uint64_t entrySizeBytes(const Chunk& chunk, const ChunkMesh& mesh);
|
||||
|
||||
private:
|
||||
long long distanceSqTo(const Vec3i& coord) const;
|
||||
std::uint64_t evictionScoreLocked(const CacheEntry& entry) const;
|
||||
void enforceBudgetLocked();
|
||||
void touchLocked(CacheEntry& entry);
|
||||
void dropMeshLocked(CacheEntry& entry);
|
||||
void eraseLocked(const Vec3i& coord);
|
||||
|
||||
mutable std::shared_mutex mutex;
|
||||
std::unordered_map<Vec3i, CacheEntry, Vec3iHash> entries;
|
||||
CacheStats counters;
|
||||
Vec3i observerChunk;
|
||||
std::uint64_t budget;
|
||||
std::uint64_t used = 0;
|
||||
std::uint64_t clock = 0;
|
||||
std::uint64_t distanceWeight = 64;
|
||||
CachePolicy replacement;
|
||||
};
|
||||
|
||||
} // namespace vf
|
||||
Reference in new issue
Block a user