chaud_hot/workspace/
worker.rs1use 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
15pub 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 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 match watcher.check() {
128 Some(l) => {
129 *last = l;
130 continue;
131 }
132 None => return,
133 }
134 }
135 None => return,
136 };
137 }
138}