1pub mod chunked;
37pub use chunked::{ChunkCoord, ChunkedCacheConfig, ChunkedMeshCache};
38
39use std::collections::HashMap;
40use std::hash::{Hash, Hasher};
41use std::path::PathBuf;
42use std::sync::{Arc, RwLock};
43
44use crate::mesh::Mesh;
45use crate::types::SdfNode;
46
47#[derive(Debug, Clone)]
58pub struct CacheConfig {
59 pub max_entries: usize,
65 #[deprecated(
71 since = "5.0.0",
72 note = "not implemented: MeshCache never touches the disk; persist with crate::io::save_abm or ChunkedMeshCache"
73 )]
74 pub disk_cache_dir: Option<PathBuf>,
75 #[deprecated(
79 since = "5.0.0",
80 note = "not implemented: evicted meshes are dropped; persist with crate::io::save_abm or ChunkedMeshCache"
81 )]
82 pub persist_on_evict: bool,
83}
84
85#[allow(deprecated)] impl Default for CacheConfig {
87 fn default() -> Self {
88 Self {
89 max_entries: 64,
90 disk_cache_dir: None,
91 persist_on_evict: false,
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Hash)]
101pub struct MeshCacheKey {
102 pub sdf_hash: u64,
104 pub resolution: u32,
106 pub bounds_hash: u64,
108}
109
110struct CacheEntry {
112 mesh: Arc<Mesh>,
113 size_bytes: usize,
115}
116
117struct LruMap {
121 map: HashMap<MeshCacheKey, CacheEntry>,
122 order: Vec<MeshCacheKey>,
124}
125
126impl LruMap {
127 fn new() -> Self {
128 Self {
129 map: HashMap::new(),
130 order: Vec::new(),
131 }
132 }
133
134 #[inline(always)]
136 fn touch(&mut self, key: &MeshCacheKey) {
137 if let Some(pos) = self.order.iter().position(|k| k == key) {
138 self.order.remove(pos);
139 self.order.push(key.clone());
140 }
141 }
142
143 fn insert(
145 &mut self,
146 key: MeshCacheKey,
147 entry: CacheEntry,
148 max_entries: usize,
149 ) -> Option<MeshCacheKey> {
150 if self.map.contains_key(&key) {
152 self.map.insert(key.clone(), entry);
153 self.touch(&key);
154 return None;
155 }
156
157 let evicted = if self.map.len() >= max_entries && max_entries > 0 {
159 if let Some(oldest_key) = self.order.first().cloned() {
160 self.map.remove(&oldest_key);
161 self.order.remove(0);
162 Some(oldest_key)
163 } else {
164 None
165 }
166 } else {
167 None
168 };
169
170 self.map.insert(key.clone(), entry);
171 self.order.push(key);
172 evicted
173 }
174
175 fn remove(&mut self, key: &MeshCacheKey) -> Option<Arc<Mesh>> {
177 if let Some(entry) = self.map.remove(key) {
178 if let Some(pos) = self.order.iter().position(|k| k == key) {
179 self.order.remove(pos);
180 }
181 Some(entry.mesh)
182 } else {
183 None
184 }
185 }
186
187 fn get(&mut self, key: &MeshCacheKey) -> Option<Arc<Mesh>> {
189 if self.map.contains_key(key) {
190 self.touch(key);
191 Some(Arc::clone(&self.map[key].mesh))
192 } else {
193 None
194 }
195 }
196
197 fn clear(&mut self) {
199 self.map.clear();
200 self.order.clear();
201 }
202
203 #[inline(always)]
205 fn len(&self) -> usize {
206 self.map.len()
207 }
208
209 fn memory_usage(&self) -> usize {
211 self.map.values().map(|e| e.size_bytes).sum()
212 }
213}
214
215pub struct MeshCache {
221 config: CacheConfig,
222 entries: RwLock<LruMap>,
223}
224
225impl MeshCache {
226 pub fn new(config: CacheConfig) -> Self {
228 Self {
229 config,
230 entries: RwLock::new(LruMap::new()),
231 }
232 }
233
234 pub fn get(&self, key: &MeshCacheKey) -> Option<Arc<Mesh>> {
238 let mut entries = self
240 .entries
241 .write()
242 .expect("MeshCache: RwLock poisoned on entries.write() in get()");
243 entries.get(key)
244 }
245
246 pub fn insert(&self, key: MeshCacheKey, mesh: Mesh) -> Arc<Mesh> {
250 let size_bytes = estimate_mesh_size(&mesh);
251 let arc_mesh = Arc::new(mesh);
252 let entry = CacheEntry {
253 mesh: Arc::clone(&arc_mesh),
254 size_bytes,
255 };
256
257 let mut entries = self
258 .entries
259 .write()
260 .expect("MeshCache: RwLock poisoned on entries.write() in insert()");
261 let _evicted = entries.insert(key, entry, self.config.max_entries);
262
263 arc_mesh
264 }
265
266 pub fn remove(&self, key: &MeshCacheKey) -> Option<Arc<Mesh>> {
268 let mut entries = self
269 .entries
270 .write()
271 .expect("MeshCache: RwLock poisoned on entries.write() in remove()");
272 entries.remove(key)
273 }
274
275 pub fn clear(&self) {
277 let mut entries = self
278 .entries
279 .write()
280 .expect("MeshCache: RwLock poisoned on entries.write() in clear()");
281 entries.clear();
282 }
283
284 #[inline(always)]
286 pub fn len(&self) -> usize {
287 let entries = self
288 .entries
289 .read()
290 .expect("MeshCache: RwLock poisoned on entries.read() in len()");
291 entries.len()
292 }
293
294 #[inline(always)]
296 pub fn is_empty(&self) -> bool {
297 self.len() == 0
298 }
299
300 pub fn memory_usage(&self) -> usize {
302 let entries = self
303 .entries
304 .read()
305 .expect("MeshCache: RwLock poisoned on entries.read() in memory_usage()");
306 entries.memory_usage()
307 }
308
309 pub fn get_or_generate<F>(&self, key: MeshCacheKey, generator: F) -> Arc<Mesh>
318 where
319 F: FnOnce() -> Mesh,
320 {
321 {
323 let mut entries = self
324 .entries
325 .write()
326 .expect("MeshCache: RwLock poisoned on entries.write() in get_or_generate()");
327 if let Some(mesh) = entries.get(&key) {
328 return mesh;
329 }
330 }
331
332 let mesh = generator();
334
335 let size_bytes = estimate_mesh_size(&mesh);
337 let arc_mesh = Arc::new(mesh);
338 let entry = CacheEntry {
339 mesh: Arc::clone(&arc_mesh),
340 size_bytes,
341 };
342
343 let mut entries = self
344 .entries
345 .write()
346 .expect("MeshCache: RwLock poisoned on entries.write() in get_or_generate()");
347 if let Some(existing) = entries.get(&key) {
350 return existing;
351 }
352 entries.insert(key, entry, self.config.max_entries);
353
354 arc_mesh
355 }
356}
357
358pub fn hash_sdf_node(node: &SdfNode) -> u64 {
363 let mut hasher = std::collections::hash_map::DefaultHasher::new();
364 serde_json::to_string(node)
365 .unwrap_or_default()
366 .hash(&mut hasher);
367 hasher.finish()
368}
369
370pub fn compute_cache_key(
375 node: &SdfNode,
376 min_bounds: glam::Vec3,
377 max_bounds: glam::Vec3,
378 resolution: usize,
379) -> MeshCacheKey {
380 let sdf_hash = hash_sdf_node(node);
381 let bounds_hash = hash_bounds(min_bounds, max_bounds);
382 MeshCacheKey {
383 sdf_hash,
384 resolution: resolution as u32,
385 bounds_hash,
386 }
387}
388
389#[inline(always)]
391fn hash_bounds(min_bounds: glam::Vec3, max_bounds: glam::Vec3) -> u64 {
392 let mut hasher = std::collections::hash_map::DefaultHasher::new();
393 quantize_f32(min_bounds.x).hash(&mut hasher);
394 quantize_f32(min_bounds.y).hash(&mut hasher);
395 quantize_f32(min_bounds.z).hash(&mut hasher);
396 quantize_f32(max_bounds.x).hash(&mut hasher);
397 quantize_f32(max_bounds.y).hash(&mut hasher);
398 quantize_f32(max_bounds.z).hash(&mut hasher);
399 hasher.finish()
400}
401
402#[inline(always)]
404fn quantize_f32(value: f32) -> i32 {
405 (value * 1000.0) as i32
406}
407
408#[inline(always)]
410fn estimate_mesh_size(mesh: &Mesh) -> usize {
411 let vertex_size = std::mem::size_of::<crate::mesh::Vertex>();
412 mesh.vertices.len() * vertex_size + mesh.indices.len() * std::mem::size_of::<u32>()
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418 use crate::mesh::Vertex;
419 use glam::Vec3;
420
421 fn make_test_mesh(num_verts: usize, num_tris: usize) -> Mesh {
423 let vertices: Vec<Vertex> = (0..num_verts)
424 .map(|i| {
425 let f = i as f32;
426 Vertex::new(Vec3::new(f, f, f), Vec3::Y)
427 })
428 .collect();
429 let indices: Vec<u32> = (0..num_tris * 3).map(|i| (i % num_verts) as u32).collect();
430 Mesh { vertices, indices }
431 }
432
433 #[test]
434 fn test_insert_and_retrieve() {
435 let cache = MeshCache::new(CacheConfig {
436 max_entries: 4,
437 ..Default::default()
438 });
439
440 let key = MeshCacheKey {
441 sdf_hash: 42,
442 resolution: 32,
443 bounds_hash: 100,
444 };
445 let mesh = make_test_mesh(8, 4);
446 let expected_verts = mesh.vertices.len();
447
448 cache.insert(key.clone(), mesh);
449
450 let retrieved = cache.get(&key);
451 assert!(retrieved.is_some());
452 assert_eq!(retrieved.unwrap().vertex_count(), expected_verts);
453 }
454
455 #[test]
456 fn test_lru_eviction() {
457 let cache = MeshCache::new(CacheConfig {
458 max_entries: 3,
459 ..Default::default()
460 });
461
462 for i in 0..3u64 {
464 let key = MeshCacheKey {
465 sdf_hash: i,
466 resolution: 16,
467 bounds_hash: 0,
468 };
469 cache.insert(key, make_test_mesh(4, 2));
470 }
471 assert_eq!(cache.len(), 3);
472
473 let key4 = MeshCacheKey {
475 sdf_hash: 99,
476 resolution: 16,
477 bounds_hash: 0,
478 };
479 cache.insert(key4, make_test_mesh(4, 2));
480 assert_eq!(cache.len(), 3);
481
482 let evicted_key = MeshCacheKey {
484 sdf_hash: 0,
485 resolution: 16,
486 bounds_hash: 0,
487 };
488 assert!(cache.get(&evicted_key).is_none());
489
490 for i in 1..3u64 {
492 let key = MeshCacheKey {
493 sdf_hash: i,
494 resolution: 16,
495 bounds_hash: 0,
496 };
497 assert!(cache.get(&key).is_some());
498 }
499 let key99 = MeshCacheKey {
500 sdf_hash: 99,
501 resolution: 16,
502 bounds_hash: 0,
503 };
504 assert!(cache.get(&key99).is_some());
505 }
506
507 #[test]
508 fn test_lru_access_updates_order() {
509 let cache = MeshCache::new(CacheConfig {
510 max_entries: 3,
511 ..Default::default()
512 });
513
514 for i in 0..3u64 {
516 let key = MeshCacheKey {
517 sdf_hash: i,
518 resolution: 16,
519 bounds_hash: 0,
520 };
521 cache.insert(key, make_test_mesh(4, 2));
522 }
523
524 let key0 = MeshCacheKey {
526 sdf_hash: 0,
527 resolution: 16,
528 bounds_hash: 0,
529 };
530 assert!(cache.get(&key0).is_some());
531
532 let key3 = MeshCacheKey {
534 sdf_hash: 3,
535 resolution: 16,
536 bounds_hash: 0,
537 };
538 cache.insert(key3, make_test_mesh(4, 2));
539
540 let evicted_key = MeshCacheKey {
541 sdf_hash: 1,
542 resolution: 16,
543 bounds_hash: 0,
544 };
545 assert!(cache.get(&evicted_key).is_none());
546
547 assert!(cache.get(&key0).is_some());
549 }
550
551 #[test]
552 fn test_get_or_generate_caches() {
553 let cache = MeshCache::new(CacheConfig::default());
554 let key = MeshCacheKey {
555 sdf_hash: 1,
556 resolution: 16,
557 bounds_hash: 0,
558 };
559
560 let call_count = std::sync::atomic::AtomicU32::new(0);
561
562 let mesh1 = cache.get_or_generate(key.clone(), || {
564 call_count.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
565 make_test_mesh(10, 5)
566 });
567 assert_eq!(call_count.load(std::sync::atomic::Ordering::SeqCst), 1);
568 assert_eq!(mesh1.vertex_count(), 10);
569
570 let mesh2 = cache.get_or_generate(key, || {
572 call_count.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
573 make_test_mesh(20, 10)
574 });
575 assert_eq!(call_count.load(std::sync::atomic::Ordering::SeqCst), 1);
576 assert_eq!(mesh2.vertex_count(), 10); }
578
579 #[test]
580 fn test_cache_key_determinism() {
581 let node = SdfNode::sphere(1.0);
582 let hash1 = hash_sdf_node(&node);
583 let hash2 = hash_sdf_node(&node);
584 assert_eq!(hash1, hash2, "Same SDF tree must produce the same hash");
585
586 let node2 = SdfNode::sphere(2.0);
588 let hash3 = hash_sdf_node(&node2);
589 assert_ne!(
590 hash1, hash3,
591 "Different SDF trees should produce different hashes"
592 );
593 }
594
595 #[test]
596 fn test_compute_cache_key_determinism() {
597 let node = SdfNode::sphere(1.0).subtract(SdfNode::box3d(0.5, 0.5, 0.5));
598 let min = Vec3::splat(-2.0);
599 let max = Vec3::splat(2.0);
600
601 let key1 = compute_cache_key(&node, min, max, 32);
602 let key2 = compute_cache_key(&node, min, max, 32);
603 assert_eq!(key1, key2);
604
605 let key3 = compute_cache_key(&node, min, max, 64);
607 assert_ne!(key1, key3);
608
609 let key4 = compute_cache_key(&node, Vec3::splat(-3.0), max, 32);
611 assert_ne!(key1, key4);
612 }
613
614 #[test]
615 fn test_thread_safety_concurrent_reads() {
616 use std::sync::Arc as StdArc;
617
618 let cache = StdArc::new(MeshCache::new(CacheConfig {
619 max_entries: 16,
620 ..Default::default()
621 }));
622
623 for i in 0..8u64 {
625 let key = MeshCacheKey {
626 sdf_hash: i,
627 resolution: 16,
628 bounds_hash: 0,
629 };
630 cache.insert(key, make_test_mesh(4, 2));
631 }
632
633 let mut handles = Vec::new();
635 for i in 0..8u64 {
636 let cache_clone = StdArc::clone(&cache);
637 handles.push(std::thread::spawn(move || {
638 let key = MeshCacheKey {
639 sdf_hash: i,
640 resolution: 16,
641 bounds_hash: 0,
642 };
643 let result = cache_clone.get(&key);
644 assert!(result.is_some(), "Key {} should be in cache", i);
645 result.unwrap().vertex_count()
646 }));
647 }
648
649 for handle in handles {
650 let count = handle.join().unwrap();
651 assert_eq!(count, 4);
652 }
653 }
654
655 #[test]
656 fn test_clear() {
657 let cache = MeshCache::new(CacheConfig::default());
658 let key = MeshCacheKey {
659 sdf_hash: 1,
660 resolution: 16,
661 bounds_hash: 0,
662 };
663 cache.insert(key.clone(), make_test_mesh(4, 2));
664 assert_eq!(cache.len(), 1);
665
666 cache.clear();
667 assert_eq!(cache.len(), 0);
668 assert!(cache.is_empty());
669 assert!(cache.get(&key).is_none());
670 }
671
672 #[test]
673 fn test_remove() {
674 let cache = MeshCache::new(CacheConfig::default());
675
676 let key1 = MeshCacheKey {
677 sdf_hash: 1,
678 resolution: 16,
679 bounds_hash: 0,
680 };
681 let key2 = MeshCacheKey {
682 sdf_hash: 2,
683 resolution: 16,
684 bounds_hash: 0,
685 };
686 cache.insert(key1.clone(), make_test_mesh(4, 2));
687 cache.insert(key2.clone(), make_test_mesh(8, 4));
688 assert_eq!(cache.len(), 2);
689
690 let removed = cache.remove(&key1);
691 assert!(removed.is_some());
692 assert_eq!(removed.unwrap().vertex_count(), 4);
693 assert_eq!(cache.len(), 1);
694 assert!(cache.get(&key1).is_none());
695 assert!(cache.get(&key2).is_some());
696
697 let removed_none = cache.remove(&key1);
699 assert!(removed_none.is_none());
700 }
701
702 #[test]
703 fn test_memory_usage() {
704 let cache = MeshCache::new(CacheConfig::default());
705
706 let key = MeshCacheKey {
707 sdf_hash: 1,
708 resolution: 16,
709 bounds_hash: 0,
710 };
711 let mesh = make_test_mesh(100, 50);
712 let expected_size = mesh.vertices.len() * std::mem::size_of::<Vertex>()
713 + mesh.indices.len() * std::mem::size_of::<u32>();
714
715 cache.insert(key, mesh);
716
717 assert_eq!(cache.memory_usage(), expected_size);
718 }
719
720 #[test]
721 fn test_empty_cache() {
722 let cache = MeshCache::new(CacheConfig::default());
723 assert!(cache.is_empty());
724 assert_eq!(cache.len(), 0);
725 assert_eq!(cache.memory_usage(), 0);
726 }
727
728 #[test]
729 fn test_insert_duplicate_key_updates() {
730 let cache = MeshCache::new(CacheConfig {
731 max_entries: 4,
732 ..Default::default()
733 });
734
735 let key = MeshCacheKey {
736 sdf_hash: 42,
737 resolution: 32,
738 bounds_hash: 100,
739 };
740
741 cache.insert(key.clone(), make_test_mesh(4, 2));
742 assert_eq!(cache.len(), 1);
743
744 cache.insert(key.clone(), make_test_mesh(16, 8));
746 assert_eq!(cache.len(), 1); let retrieved = cache.get(&key).unwrap();
749 assert_eq!(retrieved.vertex_count(), 16); }
751}