Skip to main content

chaud_hot/workspace/
worker.rs

1use super::graph::Graph;
2use super::watcher::Watcher;
3use crate::cargo::Builder;
4use crate::cargo::metadata::ManifestPath;
5use crate::util::minilog;
6use crate::{cycle, dylib};
7use anyhow::{Context as _, Result};
8use core::time::Duration;
9use parking_lot::Once;
10use std::thread;
11use std::time::Instant;
12
13const DEBOUNCE: Duration = Duration::from_millis(350);
14
15/// Launch the worker thread.
16///
17/// This function is idempotent.
18///
19/// If [`log`] has not been initialized yet, a minimal logger will be installed.
20pub fn launch(root_pkg_manifest: &str, feature_flags: Option<&'static str>) {
21    static INIT: Once = Once::new();
22
23    let root_mani = ManifestPath::new(root_pkg_manifest);
24
25    INIT.call_once(move || {
26        minilog::init();
27
28        log::trace!("Launching worker thread");
29
30        let spawn_result = thread::Builder::new()
31            .name("chaud-worker".to_owned())
32            .spawn(move || work(root_mani, feature_flags));
33
34        if let Err(e) = spawn_result {
35            log::error!("Failed to spawn Chaud worker: {e:#}");
36        }
37    });
38}
39
40fn work(root_mani: ManifestPath, feature_flags: Option<&'static str>) {
41    log::debug!("Chaud worker thread is running");
42
43    let worker = match init(root_mani, feature_flags) {
44        Ok(val) => val,
45        Err(e) => {
46            log::error!("Initialization failed, shutting down worker thread: {e:#}");
47            return;
48        }
49    };
50
51    cycle::init_done();
52
53    main(worker);
54}
55
56struct Worker {
57    graph: &'static Graph,
58    builder: Builder,
59    watcher: Watcher,
60    epoch: u32,
61}
62
63fn init(root_mani: ManifestPath, feature_flags: Option<&'static str>) -> Result<Worker> {
64    let graph = Graph::new(root_mani, feature_flags)?;
65    let builder = Builder::init(graph.env())?;
66    let watcher = Watcher::new(graph)?;
67    Ok(Worker { graph, builder, watcher, epoch: 0 })
68}
69
70fn main(mut w: Worker) {
71    log::debug!("Initialization successful, starting main work loop");
72
73    loop {
74        if let Err(e) = main_one(&mut w) {
75            log::error!("Work failed (will try again on next file change): {e:#}");
76        }
77    }
78}
79
80fn main_one(Worker { graph, builder, watcher, epoch }: &mut Worker) -> Result<()> {
81    let env = graph.env();
82
83    log::debug!("Waiting for watcher...");
84    let mut last = watcher.wait();
85
86    'has_dirty: loop {
87        debounce(&mut last, watcher);
88
89        log::debug!("Preparing & building...");
90
91        if let Err(e) = builder.build() {
92            log::info!("{e:#}");
93            // `cargo build` failing is expected, so don't return an error.
94            return Ok(());
95        }
96
97        if let Some(l) = watcher.check() {
98            log::debug!("Dirty after build, starting over");
99            last = l;
100            continue 'has_dirty;
101        }
102
103        *epoch = epoch.checked_add(1).context("Epoch overflowed")?;
104        let dst = env
105            .chaud_dir()
106            .join(format!("{}.{epoch}.hot", env.bin().as_str()));
107        builder.link_latest(&dst)?;
108
109        log::debug!("Loading {dst:?}...");
110        dylib::load(&dst)?;
111
112        log::info!("Reload complete");
113        cycle::did_reload();
114
115        return Ok(());
116    }
117}
118
119#[expect(clippy::needless_continue, reason = "intentionally explicit")]
120fn debounce(last: &mut Instant, watcher: &mut Watcher) {
121    log::trace!("Debouncing...");
122    loop {
123        match DEBOUNCE.checked_sub(last.elapsed()) {
124            Some(remaining) => {
125                thread::sleep(remaining);
126                // Check for any updates while we slept.
127                match watcher.check() {
128                    Some(l) => {
129                        *last = l;
130                        continue;
131                    }
132                    None => return,
133                }
134            }
135            None => return,
136        };
137    }
138}