uqa_analysis/
resources.rs1use std::sync::{Arc, OnceLock};
10
11use parking_lot::Mutex;
12
13use crate::{
14 cache::Cache, AnalysisResult, Analyzer, AnalyzerDescriptor, AnalyzerFingerprint,
15 AnalyzerLimits, CompiledAnalyzer, TokenLengthPolicy,
16};
17
18struct Inner {
19 limits: AnalyzerLimits,
20 cache: Mutex<Cache<AnalyzerFingerprint, CompiledAnalyzer>>,
21 #[cfg(feature = "nori")]
22 nori: crate::nori::NoriResources,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct AnalyzerCacheStats {
28 pub analyzers: usize,
29 pub descriptor_bytes: usize,
30}
31
32#[derive(Clone)]
45pub struct AnalyzerResources(Arc<Inner>);
46
47impl Default for AnalyzerResources {
48 fn default() -> Self {
49 static RESOURCES: OnceLock<AnalyzerResources> = OnceLock::new();
50 RESOURCES
51 .get_or_init(|| Self::new(AnalyzerLimits::default()))
52 .clone()
53 }
54}
55
56impl AnalyzerResources {
57 pub fn new(limits: AnalyzerLimits) -> Self {
59 Self(Arc::new(Inner {
60 limits,
61 cache: Mutex::new(Cache::default()),
62 #[cfg(feature = "nori")]
63 nori: crate::nori::NoriResources::default(),
64 }))
65 }
66
67 #[cfg(feature = "nori")]
69 pub fn with_nori_resources(limits: AnalyzerLimits, nori: crate::nori::NoriResources) -> Self {
70 Self(Arc::new(Inner {
71 limits,
72 cache: Mutex::new(Cache::default()),
73 nori,
74 }))
75 }
76
77 #[cfg(feature = "nori")]
78 pub fn nori_resources(&self) -> &crate::nori::NoriResources {
79 &self.0.nori
80 }
81
82 pub fn limits(&self) -> AnalyzerLimits {
83 self.0.limits
84 }
85
86 pub fn cache_stats(&self) -> AnalyzerCacheStats {
87 let cache = self.0.cache.lock();
88 AnalyzerCacheStats {
89 analyzers: cache.len(),
90 descriptor_bytes: cache.weight(),
91 }
92 }
93
94 pub fn compile(&self, config: &Analyzer) -> AnalysisResult<Arc<CompiledAnalyzer>> {
95 let policy = if config.uses_korean_stages() {
96 TokenLengthPolicy::DiscountOverlaps
97 } else {
98 TokenLengthPolicy::EmittedTokens
99 };
100 self.compile_with_length_policy(config, policy)
101 }
102
103 pub fn compile_with_length_policy(
104 &self,
105 config: &Analyzer,
106 policy: TokenLengthPolicy,
107 ) -> AnalysisResult<Arc<CompiledAnalyzer>> {
108 let resolved = AnalyzerDescriptor::resolve_inputs(
110 config,
111 policy,
112 self.0.limits,
113 #[cfg(feature = "nori")]
114 &self.0.nori,
115 )?;
116 self.publish(
117 resolved.descriptor,
118 #[cfg(feature = "nori")]
119 resolved.nori,
120 )
121 }
122
123 pub fn restore(
125 &self,
126 descriptor: Arc<AnalyzerDescriptor>,
127 ) -> AnalysisResult<Arc<CompiledAnalyzer>> {
128 descriptor.validate_limits(self.0.limits)?;
129 if let Some(compiled) = self.0.cache.lock().get(&descriptor.fingerprint()) {
130 return Ok(compiled);
131 }
132 #[cfg(feature = "nori")]
133 let nori = {
134 let mut config = descriptor.configuration()?;
135 crate::nori::pipeline::check_resolved(&config)?;
136 crate::nori::pipeline::ResolvedNoriPipeline::resolve(&mut config, &self.0.nori)?
137 };
138 self.publish(
139 descriptor,
140 #[cfg(feature = "nori")]
141 nori,
142 )
143 }
144
145 fn publish(
146 &self,
147 descriptor: Arc<AnalyzerDescriptor>,
148 #[cfg(feature = "nori")] nori: crate::nori::pipeline::ResolvedNoriPipeline,
149 ) -> AnalysisResult<Arc<CompiledAnalyzer>> {
150 descriptor.validate_limits(self.0.limits)?;
151 let fingerprint = descriptor.fingerprint();
152 let weight = descriptor.canonical_json().len();
153 let mut cache = self.0.cache.lock();
154 if let Some(compiled) = cache.get(&fingerprint) {
155 return Ok(compiled);
156 }
157 let compiled = Arc::new(CompiledAnalyzer::prepare(
159 descriptor,
160 #[cfg(feature = "nori")]
161 nori,
162 )?);
163 cache.insert(
164 fingerprint,
165 compiled.clone(),
166 weight,
167 self.0.limits.max_cached_analyzers,
168 self.0.limits.max_cached_descriptor_bytes,
169 );
170 Ok(compiled)
171 }
172
173 pub fn restore_json(&self, json: &str) -> AnalysisResult<Arc<CompiledAnalyzer>> {
175 self.restore(AnalyzerDescriptor::from_json(json, self.0.limits)?)
176 }
177}