formualizer_eval/engine/
warmup.rs

1//! Warmup executor for pre-building artifacts (flats removed; masks scaffold only)
2
3use crate::engine::cache::{CriteriaKey, CriteriaMaskCache};
4use crate::engine::masks::DenseMask;
5use crate::engine::metrics::{WarmupMetrics, WarmupTimer};
6use crate::engine::pass_planner::PassWarmupPlan;
7use crate::engine::tuning::WarmupConfig;
8use crate::traits::FunctionContext;
9use std::collections::HashSet;
10use std::sync::{Arc, Condvar, Mutex};
11use std::time::{Duration, Instant};
12
13/// Per-key build coordination to avoid duplicate work
14#[derive(Default)]
15struct BuildCoordinator {
16    /// Keys currently being built (in-flight)
17    in_flight: Mutex<HashSet<String>>,
18    /// Condition variable for waiting on in-flight builds
19    cv: Condvar,
20}
21
22impl BuildCoordinator {
23    /// Try to claim a key for building. Returns true if claimed, false if already in-flight.
24    fn try_claim(&self, key: &str) -> bool {
25        let mut in_flight = self.in_flight.lock().unwrap();
26        if in_flight.contains(key) {
27            false
28        } else {
29            in_flight.insert(key.to_string());
30            true
31        }
32    }
33
34    /// Wait for a key to be completed
35    fn wait_for(&self, key: &str, timeout: Duration) -> bool {
36        let start = Instant::now();
37        let mut in_flight = self.in_flight.lock().unwrap();
38
39        while in_flight.contains(key) {
40            let remaining = timeout.saturating_sub(start.elapsed());
41            if remaining.is_zero() {
42                return false; // Timeout
43            }
44
45            let (guard, timeout_result) = self.cv.wait_timeout(in_flight, remaining).unwrap();
46            in_flight = guard;
47
48            if timeout_result.timed_out() {
49                return false;
50            }
51        }
52        true
53    }
54
55    /// Release a key after building (success or failure)
56    fn release(&self, key: &str) {
57        let mut in_flight = self.in_flight.lock().unwrap();
58        in_flight.remove(key);
59        self.cv.notify_all();
60    }
61}
62
63/// Context for a single evaluation pass
64pub struct PassContext {
65    pub mask_cache: CriteriaMaskCache,
66    pub metrics: Arc<WarmupMetrics>,
67    /// Coordinator for in-flight builds
68    mask_coordinator: Arc<BuildCoordinator>,
69}
70
71impl PassContext {
72    pub fn new(config: &WarmupConfig) -> Self {
73        Self {
74            mask_cache: CriteriaMaskCache::new(config.mask_cache_entries_cap),
75            metrics: Arc::new(WarmupMetrics::new()),
76            mask_coordinator: Arc::new(BuildCoordinator::default()),
77        }
78    }
79
80    /// Clear all pass-scoped caches
81    pub fn clear(&mut self) {
82        self.mask_cache.clear();
83        self.metrics.reset();
84    }
85
86    /// Try to get or build a mask for criteria (Phase 3)
87    pub fn get_or_build_mask<C: FunctionContext>(
88        &mut self,
89        key: &CriteriaKey,
90        _context: &C,
91        _config: &WarmupConfig,
92    ) -> Option<DenseMask> {
93        // Check cache first
94        if let Some(mask) = self.mask_cache.get(key) {
95            self.metrics.record_mask_reuse();
96            return Some(mask);
97        }
98
99        // Phase 3: Full mask building will be implemented here
100        // For now, return None to use fallback path
101        None
102    }
103}
104
105/// Warmup executor
106pub struct WarmupExecutor {
107    config: WarmupConfig,
108}
109
110impl WarmupExecutor {
111    pub fn new(config: WarmupConfig) -> Self {
112        Self { config }
113    }
114
115    /// Execute warmup plan
116    pub fn execute<C: FunctionContext>(
117        &self,
118        plan: &PassWarmupPlan,
119        pass_ctx: &mut PassContext,
120        context: &C,
121    ) -> Result<(), String> {
122        if !self.config.warmup_enabled {
123            return Ok(());
124        }
125
126        let timer = WarmupTimer::start();
127        // No-op for flats (removed). Mask warmup to be implemented later.
128
129        // Record timing
130        pass_ctx.metrics.record_warmup_time(timer.elapsed());
131
132        Ok(())
133    }
134}
135
136// Flats removed: no HotReference reconstruction required