typf_core/cache_config.rs
1//! Global cache configuration
2//!
3//! Provides a simple way to enable/disable all caching globally.
4//! **Caching is disabled by default** and must be explicitly enabled
5//! via `set_caching_enabled(true)` or the `TYPF_CACHE=1` environment variable.
6//!
7//! # Example
8//!
9//! ```
10//! use typf_core::cache_config;
11//!
12//! // Enable caching (disabled by default)
13//! cache_config::set_caching_enabled(true);
14//!
15//! // Check if caching is enabled
16//! if cache_config::is_caching_enabled() {
17//! println!("Caching is ON");
18//! }
19//!
20//! // Disable caching again
21//! cache_config::set_caching_enabled(false);
22//! ```
23//!
24//! # Environment Variable
25//!
26//! Set `TYPF_CACHE=1` to enable caching at startup:
27//!
28//! ```bash
29//! TYPF_CACHE=1 ./my_app
30//! ```
31
32// this_file: crates/typf-core/src/cache_config.rs
33
34use std::sync::atomic::{AtomicBool, Ordering};
35use std::sync::Mutex;
36use std::sync::OnceLock;
37
38use crate::cache::get_cache_manager;
39
40/// Global flag for whether caching is enabled (disabled by default)
41static CACHING_ENABLED: AtomicBool = AtomicBool::new(false);
42
43/// Serialize mutations of the global caching flag.
44///
45/// This is primarily for correctness in tests (which run in parallel by default), but also makes
46/// `set_caching_enabled` deterministic for consumers that want scoped control.
47static CACHING_CONFIG_LOCK: Mutex<()> = Mutex::new(());
48
49/// Whether the env var has been checked
50static ENV_CHECKED: OnceLock<()> = OnceLock::new();
51
52/// Check environment variable and initialize the global cache state
53fn check_env() {
54 ENV_CHECKED.get_or_init(|| {
55 if let Ok(val) = std::env::var("TYPF_CACHE") {
56 let enabled = matches!(val.to_lowercase().as_str(), "1" | "true" | "yes" | "on");
57 if enabled {
58 CACHING_ENABLED.store(true, Ordering::SeqCst);
59 log::info!("Typf caching enabled via TYPF_CACHE env var");
60 }
61 }
62 });
63}
64
65/// Check if caching is globally enabled
66///
67/// This checks the `TYPF_CACHE` environment variable on first call,
68/// then returns the cached result. Runtime changes via `set_caching_enabled`
69/// are also respected.
70///
71/// **Note:** Caching is disabled by default. Enable it via
72/// `set_caching_enabled(true)` or `TYPF_CACHE=1` environment variable.
73///
74/// # Returns
75///
76/// `true` if caching is enabled, `false` if disabled (default).
77pub fn is_caching_enabled() -> bool {
78 check_env();
79 CACHING_ENABLED.load(Ordering::SeqCst)
80}
81
82fn set_caching_enabled_unlocked(enabled: bool) {
83 check_env(); // Ensure env is checked first
84 CACHING_ENABLED.store(enabled, Ordering::SeqCst);
85 log::debug!(
86 "Typf caching {} via runtime call",
87 if enabled { "enabled" } else { "disabled" }
88 );
89}
90
91/// Enable or disable caching globally at runtime
92///
93/// This overrides the environment variable setting.
94///
95/// # Arguments
96///
97/// * `enabled` - `true` to enable caching, `false` to disable
98pub fn set_caching_enabled(enabled: bool) {
99 let _lock = CACHING_CONFIG_LOCK
100 .lock()
101 .unwrap_or_else(|poisoned| poisoned.into_inner());
102 set_caching_enabled_unlocked(enabled);
103}
104
105/// Scoped override for the global caching flag.
106///
107/// Holds an internal lock for the lifetime of the guard, preventing other threads from flipping
108/// the flag via [`set_caching_enabled`] while the guard is alive. On drop, restores the previous
109/// value.
110pub fn scoped_caching_enabled(enabled: bool) -> ScopedCachingEnabled {
111 let lock = CACHING_CONFIG_LOCK
112 .lock()
113 .unwrap_or_else(|poisoned| poisoned.into_inner());
114 let previous = is_caching_enabled();
115 set_caching_enabled_unlocked(enabled);
116 ScopedCachingEnabled {
117 _lock: lock,
118 previous,
119 }
120}
121
122pub struct ScopedCachingEnabled {
123 _lock: std::sync::MutexGuard<'static, ()>,
124 previous: bool,
125}
126
127impl Drop for ScopedCachingEnabled {
128 fn drop(&mut self) {
129 set_caching_enabled_unlocked(self.previous);
130 }
131}
132
133/// Clear all caches (convenience function)
134///
135/// This doesn't disable caching, just clears existing cached data.
136/// For shapers/renderers with their own caches, call their `clear_cache()` methods.
137pub fn clear_all_caches() {
138 if is_caching_enabled() {
139 get_cache_manager().clear_all();
140 log::debug!("All global caches cleared.");
141 } else {
142 log::debug!("Caching is disabled, no caches to clear.");
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 // Single test to avoid race conditions with other tests that use the global flag.
151 // Tests that set_caching_enabled correctly updates the value returned by is_caching_enabled.
152
153 #[test]
154 fn test_set_caching_roundtrip() {
155 let _lock = CACHING_CONFIG_LOCK
156 .lock()
157 .unwrap_or_else(|poisoned| poisoned.into_inner());
158 let previous = is_caching_enabled();
159
160 set_caching_enabled_unlocked(true);
161 let after_enable = is_caching_enabled();
162
163 set_caching_enabled_unlocked(false);
164 let after_disable = is_caching_enabled();
165
166 set_caching_enabled_unlocked(previous);
167
168 // Assert after all operations to reduce race window
169 assert!(
170 after_enable,
171 "caching should be enabled after set_caching_enabled(true)"
172 );
173 assert!(
174 !after_disable,
175 "caching should be disabled after set_caching_enabled(false)"
176 );
177 }
178
179 #[test]
180 fn test_clear_all_caches_clears_enabled_caches() {
181 // Ensure caching is enabled for this test
182 let _guard = scoped_caching_enabled(true);
183
184 let manager = get_cache_manager();
185
186 // Insert dummy data into shaping cache
187 manager.cache_shaped(
188 crate::cache::ShapingCacheKey {
189 text_hash: 1,
190 font_id: "font1".to_string(),
191 params_hash: 1,
192 },
193 std::sync::Arc::new(vec![1, 2, 3]),
194 );
195 // Insert dummy data into glyph cache
196 manager.cache_glyph(
197 crate::cache::GlyphCacheKey {
198 font_id: "font1".to_string(),
199 glyph_id: 100,
200 size: 16,
201 params_hash: 2,
202 },
203 std::sync::Arc::new(vec![4, 5, 6]),
204 );
205
206 // Ensure caches are not empty
207 assert!(
208 !manager.shaping_cache.is_empty(),
209 "Shaping cache should not be empty before clear"
210 );
211 assert!(
212 !manager.glyph_cache.is_empty(),
213 "Glyph cache should not be empty before clear"
214 );
215
216 // Clear all caches
217 clear_all_caches();
218
219 // Ensure caches are empty after clearing
220 manager.shaping_cache.sync(); // Force pending operations
221 manager.glyph_cache.sync(); // Force pending operations
222 assert!(
223 manager.shaping_cache.is_empty(),
224 "Shaping cache should be empty after clear"
225 );
226 assert!(
227 manager.glyph_cache.is_empty(),
228 "Glyph cache should be empty after clear"
229 );
230 }
231}