bevy_sensor/
cache.rs

1//! Model caching system for efficient multi-viewpoint rendering.
2//!
3//! Caches loaded mesh and texture assets to avoid redundant disk I/O and
4//! asset loading when rendering multiple viewpoints of the same object.
5//!
6//! # Performance
7//!
8//! Typical speedup when rendering the same object multiple times:
9//! - First render: 100% (loads from disk)
10//! - Subsequent renders: 2-3x faster (cache hits)
11//!
12//! # Example
13//!
14//! ```rust,no_run
15//! use bevy_sensor::{
16//!     cache::ModelCache,
17//!     render_to_buffer_cached,
18//!     RenderConfig, ViewpointConfig, ObjectRotation,
19//! };
20//! use std::path::PathBuf;
21//!
22//! let mut cache = ModelCache::new();
23//! let object_dir = PathBuf::from("/tmp/ycb/003_cracker_box");
24//! let config = RenderConfig::tbp_default();
25//! let viewpoints = bevy_sensor::generate_viewpoints(&ViewpointConfig::default());
26//!
27//! // First render: loads from disk
28//! let output1 = render_to_buffer_cached(
29//!     &object_dir,
30//!     &viewpoints[0],
31//!     &ObjectRotation::identity(),
32//!     &config,
33//!     &mut cache,
34//! ).unwrap();
35//!
36//! // Subsequent renders: uses cache (much faster)
37//! for viewpoint in &viewpoints[1..] {
38//!     let output = render_to_buffer_cached(
39//!         &object_dir,
40//!         viewpoint,
41//!         &ObjectRotation::identity(),
42//!         &config,
43//!         &mut cache,
44//!     ).unwrap();
45//! }
46//! ```
47
48use std::collections::HashMap;
49use std::path::{Path, PathBuf};
50
51/// Cache for loaded mesh and texture assets.
52///
53/// Stores `Handle<Scene>` and `Handle<Image>` by file path to avoid
54/// redundant loading during multi-viewpoint rendering.
55#[derive(Debug, Clone, Default)]
56pub struct ModelCache {
57    /// Cached scene meshes by path
58    scenes: HashMap<PathBuf, bool>, // true = cached
59    /// Cached texture images by path
60    textures: HashMap<PathBuf, bool>, // true = cached
61}
62
63impl ModelCache {
64    /// Create a new empty cache.
65    pub fn new() -> Self {
66        Self {
67            scenes: HashMap::new(),
68            textures: HashMap::new(),
69        }
70    }
71
72    /// Check if a mesh is cached.
73    pub fn has_scene(&self, path: &Path) -> bool {
74        self.scenes.contains_key(path)
75    }
76
77    /// Check if a texture is cached.
78    pub fn has_texture(&self, path: &Path) -> bool {
79        self.textures.contains_key(path)
80    }
81
82    /// Cache a scene path.
83    pub fn cache_scene(&mut self, path: PathBuf) {
84        self.scenes.insert(path, true);
85    }
86
87    /// Cache a texture path.
88    pub fn cache_texture(&mut self, path: PathBuf) {
89        self.textures.insert(path, true);
90    }
91
92    /// Get number of cached scenes.
93    pub fn scene_count(&self) -> usize {
94        self.scenes.len()
95    }
96
97    /// Get number of cached textures.
98    pub fn texture_count(&self) -> usize {
99        self.textures.len()
100    }
101
102    /// Clear all cached items.
103    pub fn clear(&mut self) {
104        self.scenes.clear();
105        self.textures.clear();
106    }
107
108    /// Get total cache size information.
109    pub fn stats(&self) -> ModelCacheStats {
110        ModelCacheStats {
111            cached_scenes: self.scenes.len(),
112            cached_textures: self.textures.len(),
113        }
114    }
115}
116
117/// Statistics about cache usage.
118#[derive(Debug, Clone, Default, PartialEq, Eq)]
119pub struct ModelCacheStats {
120    /// Number of unique scenes in cache
121    pub cached_scenes: usize,
122    /// Number of unique textures in cache
123    pub cached_textures: usize,
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_new_cache_is_empty() {
132        let cache = ModelCache::new();
133        assert_eq!(cache.scene_count(), 0);
134        assert_eq!(cache.texture_count(), 0);
135    }
136
137    #[test]
138    fn test_cache_scene() {
139        let mut cache = ModelCache::new();
140        let path = PathBuf::from("/tmp/ycb/003_cracker_box/google_16k/textured.obj");
141
142        assert!(!cache.has_scene(&path));
143        cache.cache_scene(path.clone());
144        assert!(cache.has_scene(&path));
145        assert_eq!(cache.scene_count(), 1);
146    }
147
148    #[test]
149    fn test_cache_texture() {
150        let mut cache = ModelCache::new();
151        let path = PathBuf::from("/tmp/ycb/003_cracker_box/google_16k/texture_map.png");
152
153        assert!(!cache.has_texture(&path));
154        cache.cache_texture(path.clone());
155        assert!(cache.has_texture(&path));
156        assert_eq!(cache.texture_count(), 1);
157    }
158
159    #[test]
160    fn test_cache_multiple_items() {
161        let mut cache = ModelCache::new();
162
163        cache.cache_scene(PathBuf::from("scene1.obj"));
164        cache.cache_scene(PathBuf::from("scene2.obj"));
165        cache.cache_texture(PathBuf::from("texture1.png"));
166
167        assert_eq!(cache.scene_count(), 2);
168        assert_eq!(cache.texture_count(), 1);
169    }
170
171    #[test]
172    fn test_clear_cache() {
173        let mut cache = ModelCache::new();
174
175        cache.cache_scene(PathBuf::from("scene.obj"));
176        cache.cache_texture(PathBuf::from("texture.png"));
177        assert_eq!(cache.scene_count(), 1);
178        assert_eq!(cache.texture_count(), 1);
179
180        cache.clear();
181        assert_eq!(cache.scene_count(), 0);
182        assert_eq!(cache.texture_count(), 0);
183    }
184
185    #[test]
186    fn test_cache_stats() {
187        let mut cache = ModelCache::new();
188
189        cache.cache_scene(PathBuf::from("scene.obj"));
190        cache.cache_texture(PathBuf::from("texture.png"));
191
192        let stats = cache.stats();
193        assert_eq!(stats.cached_scenes, 1);
194        assert_eq!(stats.cached_textures, 1);
195    }
196
197    #[test]
198    fn test_duplicate_cache_entry() {
199        let mut cache = ModelCache::new();
200        let path = PathBuf::from("scene.obj");
201
202        cache.cache_scene(path.clone());
203        cache.cache_scene(path.clone());
204
205        // HashMap deduplicates, so count should still be 1
206        assert_eq!(cache.scene_count(), 1);
207    }
208}