use crate::handler::WatcherEventHandler;
use crate::task_fs_event_handler::TaskFsEventHandler;
use crate::watch_coordinator::WatchCoordinator;
use crate::watch_task::{WatchTask, WatchTaskIdx};
use crate::watcher_msg::WatcherMsg;
use anyhow::Result;
use futures::FutureExt;
use futures::future::Shared;
use oxc_index::IndexVec;
use rolldown::BundlerConfig;
use rolldown_error::BuildResult;
use rolldown_fs_watcher::FsWatcherConfig;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use tokio::sync::{Notify, mpsc};
const DEFAULT_DEBOUNCE_MS: u64 = 0;
#[derive(Debug, Clone, Default)]
pub struct WatcherConfig {
pub debounce: Option<std::time::Duration>,
pub use_polling: bool,
pub poll_interval: Option<u64>,
pub compare_contents_for_polling: bool,
pub use_debounce: bool,
pub debounce_delay: Option<u64>,
pub debounce_tick_rate: Option<u64>,
}
impl WatcherConfig {
pub fn debounce_duration(&self) -> std::time::Duration {
self.debounce.unwrap_or(std::time::Duration::from_millis(DEFAULT_DEBOUNCE_MS))
}
fn to_fs_watcher_config(&self) -> FsWatcherConfig {
let mut config = FsWatcherConfig::default();
if let Some(poll_interval) = self.poll_interval {
config.poll_interval = poll_interval;
}
config.compare_contents_for_polling = self.compare_contents_for_polling;
config.use_polling = self.use_polling;
config.use_debounce = self.use_debounce;
if let Some(debounce_delay) = self.debounce_delay {
config.debounce_delay = debounce_delay;
}
config.debounce_tick_rate = self.debounce_tick_rate;
config
}
}
type CoordinatorFuture = Shared<Pin<Box<dyn Future<Output = ()> + Send>>>;
struct CoordinatorState {
coordinator: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
handle: Option<CoordinatorFuture>,
}
pub struct Watcher {
coordinator_state: std::sync::Mutex<CoordinatorState>,
tx: mpsc::UnboundedSender<WatcherMsg>,
closed: Arc<AtomicBool>,
close_notify: Arc<Notify>,
}
impl Watcher {
pub fn new<H: WatcherEventHandler + 'static>(
configs: Vec<BundlerConfig>,
handler: H,
watcher_config: &WatcherConfig,
) -> BuildResult<Self> {
let (tx, rx) = mpsc::unbounded_channel();
let closed = Arc::new(AtomicBool::new(false));
let close_notify = Arc::new(Notify::new());
let tasks = Self::create_tasks(configs, watcher_config, &tx, &closed)?;
let coordinator = WatchCoordinator::new(
rx,
handler,
tasks,
watcher_config,
Arc::clone(&closed),
Arc::clone(&close_notify),
);
let coordinator_future: Pin<Box<dyn Future<Output = ()> + Send>> = Box::pin(coordinator.run());
Ok(Self {
coordinator_state: std::sync::Mutex::new(CoordinatorState {
coordinator: Some(coordinator_future),
handle: None,
}),
tx,
closed,
close_notify,
})
}
pub fn run(&self) {
let mut state = self.coordinator_state.lock().unwrap();
if let Some(coordinator) = state.coordinator.take() {
let join_handle = tokio::spawn(coordinator);
let handle: Pin<Box<dyn Future<Output = ()> + Send>> = Box::pin(async move {
let _ = join_handle.await;
});
state.handle = Some(handle.shared());
}
}
pub async fn wait_for_close(&self) {
let handle = self.coordinator_state.lock().unwrap().handle.clone();
if let Some(handle) = handle {
handle.await;
}
}
pub async fn close(&self) -> Result<()> {
self.closed.store(true, std::sync::atomic::Ordering::Relaxed);
self.close_notify.notify_one();
let _ = self.tx.send(WatcherMsg::Close);
self.wait_for_close().await;
Ok(())
}
fn create_tasks(
configs: Vec<BundlerConfig>,
watcher_config: &WatcherConfig,
tx: &mpsc::UnboundedSender<WatcherMsg>,
closed: &Arc<AtomicBool>,
) -> BuildResult<IndexVec<WatchTaskIdx, WatchTask>> {
let fs_watcher_config = watcher_config.to_fs_watcher_config();
let mut tasks = IndexVec::with_capacity(configs.len());
for (index, config) in configs.into_iter().enumerate() {
let task_index = WatchTaskIdx::from_usize(index);
let fs_handler = TaskFsEventHandler { task_index, tx: tx.clone() };
let fs_watcher =
rolldown_fs_watcher::create_fs_watcher(fs_handler, fs_watcher_config.clone())?;
let task = WatchTask::new(config, fs_watcher, closed)?;
tasks.push(task);
}
Ok(tasks)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_watcher_config_default_debounce() {
let config = WatcherConfig::default();
assert_eq!(config.debounce_duration(), Duration::from_millis(DEFAULT_DEBOUNCE_MS));
}
#[test]
fn test_watcher_config_custom_debounce() {
let config = WatcherConfig { debounce: Some(Duration::from_millis(500)), ..Default::default() };
assert_eq!(config.debounce_duration(), Duration::from_millis(500));
}
#[test]
fn test_fs_watcher_config_defaults() {
let config = WatcherConfig::default();
let fs_config = config.to_fs_watcher_config();
assert_eq!(fs_config.poll_interval, 100);
}
#[test]
fn test_fs_watcher_config_with_poll_interval() {
let config = WatcherConfig { poll_interval: Some(250), ..Default::default() };
let fs_config = config.to_fs_watcher_config();
assert_eq!(fs_config.poll_interval, 250);
}
}