1use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5use std::sync::{Arc, Mutex};
6use std::time::{Duration, Instant};
7
8#[derive(Debug, Clone)]
10pub struct HmrUpdate {
11 pub module: String,
13 pub timestamp: Instant,
15 pub is_updated: bool,
17}
18
19#[derive(Debug, Clone)]
21pub struct HmrContext {
22 modules: Arc<Mutex<HashMap<String, HmrUpdate>>>,
24 last_rebuild: Arc<Mutex<Option<Instant>>>,
26 project_root: PathBuf,
28}
29
30impl Default for HmrContext {
31 fn default() -> Self {
32 Self::new(PathBuf::from("."))
33 }
34}
35
36impl HmrContext {
37 pub fn new(project_root: PathBuf) -> Self {
39 Self {
40 modules: Arc::new(Mutex::new(HashMap::new())),
41 last_rebuild: Arc::new(Mutex::new(None)),
42 project_root,
43 }
44 }
45
46 pub fn record_file_change(&self, path: &Path) -> Option<String> {
48 let rel_path = path.strip_prefix(&self.project_root).ok()?;
49 let path_str = rel_path.to_string_lossy().replace('\\', "/");
50
51 if let Some(ext) = path.extension() {
53 let ext_str = ext.to_string_lossy();
54
55 let module = if ext_str == "rs" || ext_str == "orbit" {
56 if path_str.starts_with("src/") {
57 Some(
58 path_str
59 .replace("src/", "")
60 .replace(".rs", "")
61 .replace(".orbit", ""),
62 )
63 } else {
64 None
66 }
67 } else {
68 None
70 };
71
72 if let Some(module_path) = module {
73 let mut modules = self.modules.lock().unwrap();
74 modules.insert(
75 module_path.clone(),
76 HmrUpdate {
77 module: module_path.clone(),
78 timestamp: Instant::now(),
79 is_updated: false,
80 },
81 );
82 return Some(module_path);
83 }
84 }
85
86 None
87 }
88
89 pub fn mark_modules_updated(&self) {
91 let mut modules = self.modules.lock().unwrap();
92 for update in modules.values_mut() {
93 update.is_updated = true;
94 }
95 }
96
97 pub fn needs_update(&self) -> bool {
99 let modules = self.modules.lock().unwrap();
100 modules.values().any(|update| !update.is_updated)
101 }
102
103 pub fn get_pending_updates(&self) -> Vec<String> {
105 let modules = self.modules.lock().unwrap();
106 modules
107 .values()
108 .filter(|update| !update.is_updated)
109 .map(|update| update.module.clone())
110 .collect()
111 }
112
113 pub fn record_rebuild(&self) {
115 let mut last_rebuild = self.last_rebuild.lock().unwrap();
116 *last_rebuild = Some(Instant::now());
117
118 self.mark_modules_updated();
120 }
121 pub fn should_rebuild(&self, debounce_time: Duration) -> bool {
123 let last_rebuild = self.last_rebuild.lock().unwrap();
125 if let Some(instant) = *last_rebuild {
126 if instant.elapsed() < debounce_time {
127 return false;
128 }
129 }
130 drop(last_rebuild); self.needs_update()
134 }
135
136 pub fn clear(&self) {
138 let mut modules = self.modules.lock().unwrap();
139 modules.clear();
140 }
141
142 pub fn get_oldest_update_age(&self) -> Option<Duration> {
144 let modules = self.modules.lock().unwrap();
145 modules
146 .values()
147 .filter(|update| !update.is_updated)
148 .map(|update| update.timestamp.elapsed())
149 .max()
150 }
151
152 pub fn get_stale_updates(&self, max_age: Duration) -> Vec<String> {
154 let modules = self.modules.lock().unwrap();
155 modules
156 .values()
157 .filter(|update| !update.is_updated && update.timestamp.elapsed() > max_age)
158 .map(|update| update.module.clone())
159 .collect()
160 }
161
162 pub fn clear_stale_updates(&self, max_age: Duration) {
164 let mut modules = self.modules.lock().unwrap();
165 modules.retain(|_, update| update.is_updated || update.timestamp.elapsed() <= max_age);
166 }
167}