use std::collections::{BTreeSet, HashMap};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::JoinHandle;
use std::time::Duration;
use crate::fs_watcher::{RootKind, Watcher};
use crate::state::{DirtySet, WatchSetHandle, lock_watchset};
const BRIDGE_POLL: Duration = Duration::from_millis(50);
#[derive(Default)]
pub struct WatchSet {
live: HashMap<(PathBuf, RootKind), Watcher>,
}
impl WatchSet {
pub fn new() -> Self {
Self::default()
}
pub fn reconcile(&mut self, desired: &[(PathBuf, RootKind)]) {
self.live.retain(|key, _| desired.contains(key));
for key in desired {
if !self.live.contains_key(key)
&& let Ok(watcher) = Watcher::with_kind(&key.0, key.1)
{
self.live.insert(key.clone(), watcher);
}
}
}
pub fn drain_dirty(&self) -> BTreeSet<PathBuf> {
let mut dirty = BTreeSet::new();
for ((root, _kind), watcher) in &self.live {
if !watcher.tick().is_empty() {
dirty.insert(root.clone());
}
}
dirty
}
pub fn len(&self) -> usize {
self.live.len()
}
pub fn is_empty(&self) -> bool {
self.live.is_empty()
}
pub fn watches(&self, root: &Path, kind: RootKind) -> bool {
self.live.contains_key(&(root.to_path_buf(), kind))
}
}
pub trait Repaint: Send {
fn request(&self);
}
pub struct EguiRepaint(pub egui::Context);
impl Repaint for EguiRepaint {
fn request(&self) {
self.0.request_repaint();
}
}
pub struct Bridge {
stop: Arc<AtomicBool>,
handle: Option<JoinHandle<()>>,
}
impl Bridge {
pub fn spawn(
watchset: WatchSetHandle,
dirty: DirtySet,
repaint: impl Repaint + 'static,
) -> Self {
let stop = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&stop);
let handle = std::thread::spawn(move || {
while !flag.load(Ordering::Relaxed) {
if pump(&watchset, &dirty) {
repaint.request();
}
std::thread::park_timeout(BRIDGE_POLL);
}
});
Self {
stop,
handle: Some(handle),
}
}
}
impl Drop for Bridge {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(handle) = self.handle.take() {
handle.thread().unpark();
let _ = handle.join();
}
}
}
fn pump(watchset: &WatchSetHandle, dirty: &DirtySet) -> bool {
let roots = lock_watchset(watchset).drain_dirty();
if roots.is_empty() {
return false;
}
dirty.mark_all(roots);
true
}
#[cfg(test)]
mod tests;