Drain pending file-change events. Returns the unique set of (handle, descriptor) pairs that need recompilation this frame. `notify` fires multiple events per save; we coalesce within a 120 ms window.
(&self)
| 134 | /// `notify` fires multiple events per save; we coalesce within a |
| 135 | /// 120 ms window. |
| 136 | pub fn drain_pending(&self) -> Vec<(u32, FileMaterialDesc)> { |
| 137 | let mut paths: Vec<PathBuf> = Vec::new(); |
| 138 | if let Ok(rx) = self.rx.lock() { |
| 139 | while let Ok(p) = rx.try_recv() { |
| 140 | paths.push(p); |
| 141 | } |
| 142 | } |
| 143 | if paths.is_empty() { return Vec::new(); } |
| 144 | |
| 145 | let now = Instant::now(); |
| 146 | let mut last = self.last_event.lock().expect("hot_reload poisoned"); |
| 147 | let mut out: Vec<(u32, FileMaterialDesc)> = Vec::new(); |
| 148 | let mut seen: HashSet<u32> = HashSet::new(); |
| 149 | for raw in paths { |
| 150 | // Resolve canonically so the keys we matched on register |
| 151 | // line up with the events. notify on macOS sometimes |
| 152 | // returns the realpath, sometimes the symlink path. |
| 153 | let p = std::fs::canonicalize(&raw).unwrap_or(raw); |
| 154 | if let Some(t) = last.get(&p) { |
| 155 | if now.duration_since(*t) < DEBOUNCE_WINDOW { continue; } |
| 156 | } |
| 157 | last.insert(p.clone(), now); |
| 158 | if let Some(handles) = self.by_path.get(&p) { |
| 159 | for h in handles { |
| 160 | if seen.insert(*h) { |
| 161 | if let Some(d) = self.descriptors.get(h) { |
| 162 | out.push((*h, d.clone())); |
| 163 | } |
| 164 | } |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | out |
| 169 | } |
| 170 | |
| 171 | /// Test-only: push a path through the channel as if `notify` had |
| 172 | /// fired a Modify event. Lets unit tests exercise the drain + |