Skip to main content

par_term_config/shader_metadata/
cache.rs

1//! Generic metadata cache for parsed shader YAML blocks.
2//!
3//! [`MetadataCache<T>`] wraps an in-memory `HashMap` keyed by shader filename
4//! and re-parses from disk only on cache misses or explicit invalidation.
5//! The two concrete types used by par-term are exposed as type aliases:
6//!
7//! - [`ShaderMetadataCache`] — background shaders
8//! - [`CursorShaderMetadataCache`] — cursor shaders
9
10use super::parsing::extract_yaml_block;
11use crate::types::{CursorShaderMetadata, ShaderMetadata};
12use std::collections::HashMap;
13use std::path::PathBuf;
14
15/// Generic cache for parsed shader metadata.
16///
17/// Avoids re-parsing shader files on every access while still allowing
18/// invalidation for hot reload scenarios.
19///
20/// `T` must be deserializable from YAML via serde. Use the type aliases
21/// [`ShaderMetadataCache`] and [`CursorShaderMetadataCache`] for the two
22/// concrete cache types used by par-term.
23#[derive(Debug)]
24pub struct MetadataCache<T>
25where
26    T: for<'de> serde::Deserialize<'de>,
27{
28    /// Cached metadata by shader filename (not full path).
29    cache: HashMap<String, Option<T>>,
30    /// The shaders directory path.
31    shaders_dir: Option<PathBuf>,
32}
33
34impl<T> Default for MetadataCache<T>
35where
36    T: for<'de> serde::Deserialize<'de>,
37{
38    fn default() -> Self {
39        Self {
40            cache: HashMap::new(),
41            shaders_dir: None,
42        }
43    }
44}
45
46impl<T> MetadataCache<T>
47where
48    T: for<'de> serde::Deserialize<'de>,
49{
50    /// Create a new empty metadata cache.
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Create a new metadata cache with a specific shaders directory.
56    pub fn with_shaders_dir(shaders_dir: PathBuf) -> Self {
57        Self {
58            cache: HashMap::new(),
59            shaders_dir: Some(shaders_dir),
60        }
61    }
62
63    /// Set the shaders directory path.
64    pub fn set_shaders_dir(&mut self, shaders_dir: PathBuf) {
65        self.shaders_dir = Some(shaders_dir);
66    }
67
68    /// Get metadata for a shader, loading and caching if necessary.
69    ///
70    /// # Arguments
71    /// * `shader_name` - Filename of the shader (e.g., "crt.glsl")
72    ///
73    /// # Returns
74    /// * `Some(&T)` if metadata was found
75    /// * `None` if no metadata was found or the shader couldn't be read
76    pub fn get(&mut self, shader_name: &str) -> Option<&T>
77    where
78        T: std::fmt::Debug,
79    {
80        if self.cache.contains_key(shader_name) {
81            return self.cache.get(shader_name).and_then(|m| m.as_ref());
82        }
83
84        let metadata = self.load_metadata(shader_name);
85        self.cache.insert(shader_name.to_string(), metadata);
86        self.cache.get(shader_name).and_then(|m| m.as_ref())
87    }
88
89    /// Get metadata without caching (always reads from disk).
90    ///
91    /// Useful for hot reload scenarios where you want fresh data.
92    pub fn get_fresh(&self, shader_name: &str) -> Option<T> {
93        self.load_metadata(shader_name)
94    }
95
96    /// Load metadata from a shader file by parsing YAML from its embedded block.
97    fn load_metadata(&self, shader_name: &str) -> Option<T> {
98        let path = self.resolve_shader_path(shader_name)?;
99        let source = std::fs::read_to_string(&path)
100            .map_err(|e| {
101                log::warn!("Failed to read shader file '{}': {}", path.display(), e);
102            })
103            .ok()?;
104        let yaml_trimmed = extract_yaml_block(&source)?;
105        match serde_yaml_ng::from_str(yaml_trimmed) {
106            Ok(metadata) => Some(metadata),
107            Err(e) => {
108                log::warn!(
109                    "Failed to parse shader metadata YAML from '{}': {}",
110                    path.display(),
111                    e
112                );
113                None
114            }
115        }
116    }
117
118    /// Resolve a shader name to its full path.
119    fn resolve_shader_path(&self, shader_name: &str) -> Option<PathBuf> {
120        let shader_path = PathBuf::from(shader_name);
121
122        if shader_path.is_absolute() && shader_path.exists() {
123            return Some(shader_path);
124        }
125
126        if let Some(ref shaders_dir) = self.shaders_dir {
127            let full_path = shaders_dir.join(shader_name);
128            if full_path.exists() {
129                return Some(full_path);
130            }
131        }
132
133        let default_path = crate::config::Config::shader_path(shader_name);
134        if default_path.exists() {
135            return Some(default_path);
136        }
137
138        None
139    }
140
141    /// Invalidate cached metadata for a specific shader.
142    ///
143    /// Call this when a shader file has been modified (hot reload).
144    pub fn invalidate(&mut self, shader_name: &str) {
145        self.cache.remove(shader_name);
146        log::debug!("Invalidated metadata cache for: {}", shader_name);
147    }
148
149    /// Invalidate all cached metadata.
150    ///
151    /// Call this when the shaders directory might have changed.
152    pub fn invalidate_all(&mut self) {
153        self.cache.clear();
154        log::debug!("Invalidated all metadata cache entries");
155    }
156
157    /// Check if metadata is cached for a shader.
158    pub fn is_cached(&self, shader_name: &str) -> bool {
159        self.cache.contains_key(shader_name)
160    }
161
162    /// Get the number of cached entries.
163    pub fn cache_size(&self) -> usize {
164        self.cache.len()
165    }
166}
167
168// ============================================================================
169// Type aliases — preserve the original public API
170// ============================================================================
171
172/// Cache for parsed background shader metadata.
173///
174/// See [`MetadataCache`] for full documentation.
175pub type ShaderMetadataCache = MetadataCache<ShaderMetadata>;
176
177/// Cache for parsed cursor shader metadata.
178///
179/// See [`MetadataCache`] for full documentation.
180pub type CursorShaderMetadataCache = MetadataCache<CursorShaderMetadata>;