synvael/crates/server/src/chunk_cache.rs

67 lines
2 KiB
Rust

// SPDX-License-Identifier: AGPL-3.0-only
//! A bounded cache of regenerated chunk baselines, shared across the worker pool.
use std::num::NonZeroUsize;
use std::sync::{Arc, Mutex};
use lru::LruCache;
use shared::generator::VoxelGenerator;
use shared::world::{Chunk, ChunkPos};
/// A least-recently-used cache of chunk baselines, cloneable so every worker shares one store.
#[derive(Clone)]
pub struct ChunkCache {
/// The shared LRU store.
inner: Arc<Mutex<LruCache<ChunkPos, Chunk>>>,
}
impl ChunkCache {
/// Creates an empty cache holding at most `capacity` baselines before evicting the least-recently-used entry.
#[must_use]
pub fn new(capacity: NonZeroUsize) -> Self {
Self {
inner: Arc::new(Mutex::new(LruCache::new(capacity))),
}
}
/// Returns the baseline for `pos`, generating and caching it on a miss.
#[must_use]
pub fn get_or_generate(&self, pos: ChunkPos, generator: &VoxelGenerator) -> Chunk {
// A poisoned lock cannot yield a usable cache; recovering the guard lets generation proceed rather than propagating a panic across every worker that shares this cache.
let mut guard = self
.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(hit) = guard.get(&pos) {
return hit.clone();
}
let chunk = generator.generate_chunk(pos);
guard.put(pos, chunk.clone());
chunk
}
}
#[cfg(test)]
impl ChunkCache {
/// Number of baselines currently resident. Test-only introspection.
fn len(&self) -> usize {
self.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.len()
}
/// Whether a baseline for `pos` is currently resident. Test-only introspection.
fn contains(&self, pos: ChunkPos) -> bool {
self.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.contains(&pos)
}
}
#[cfg(test)]
#[path = "tests/chunk_cache.rs"]
mod tests;