1use std::{
2 thread::{self},
3 time::Duration,
4};
5
6use krates::Kid;
7use notify_debouncer_full::{DebouncedEvent, Debouncer};
8use ranim::app::{AppCmd, AppState};
9
10use async_channel::{Receiver, bounded, unbounded};
11use log::{info, trace};
12use notify::RecursiveMode;
13
14use crate::{
15 RanimUserLibraryBuilder,
16 cli::Args,
17 workspace::{Workspace, get_target_package},
18};
19
20fn watch_krate(
21 workspace: &Workspace,
22 kid: &Kid,
23) -> (
24 Debouncer<notify::RecommendedWatcher, notify_debouncer_full::RecommendedCache>,
25 Receiver<Vec<DebouncedEvent>>,
26) {
27 let (tx, rx) = unbounded();
28
29 let mut debouncer =
30 notify_debouncer_full::new_debouncer(Duration::from_millis(500), None, move |evt| {
31 let Ok(evt) = evt else {
32 return;
33 };
34 _ = tx.try_send(evt)
35 })
36 .expect("Failed to create debounced watcher");
37
38 let mut watch_krates = vec![];
40 if let krates::Node::Krate { krate, .. } = workspace.krates.node_for_kid(kid).unwrap() {
41 watch_krates.push(krate);
42 }
43 watch_krates.extend(
44 workspace
45 .krates
46 .get_deps(workspace.krates.nid_for_kid(kid).unwrap())
47 .filter_map(|(dep, _)| {
48 let krate = match dep {
49 krates::Node::Krate { krate, .. } => krate,
50 krates::Node::Feature { krate_index, .. } => {
51 &workspace.krates[krate_index.index()]
52 }
53 };
54 if krate
55 .manifest_path
56 .components()
57 .any(|c| c.as_str() == ".cargo")
58 {
59 None
60 } else {
61 Some(krate)
62 }
63 }),
64 );
65
66 let watch_krate_roots = watch_krates
67 .into_iter()
68 .map(|krate| {
69 krate
70 .manifest_path
71 .parent()
72 .unwrap()
73 .to_path_buf()
74 .into_std_path_buf()
75 })
76 .collect::<Vec<_>>();
77
78 let mut watch_paths = vec![];
79 for krate_root in &watch_krate_roots {
80 trace!("Adding watched dir for krate root {krate_root:?}");
81 let ignore_builder = ignore::gitignore::GitignoreBuilder::new(krate_root);
82 let ignore = ignore_builder.build().unwrap();
83
84 for entry in krate_root
85 .read_dir()
86 .into_iter()
87 .flatten()
88 .filter_map(|entry| entry.ok())
89 .filter(|entry| {
90 !ignore
91 .matched(entry.path(), entry.path().is_dir())
92 .is_ignore()
93 })
94 .filter(|entry| {
95 !workspace
96 .ignore
97 .matched(entry.path(), entry.path().is_dir())
98 .is_ignore()
99 })
100 {
101 trace!("Watching path {:?}", entry.path());
102 watch_paths.push(entry.path().to_path_buf());
103 }
104 }
105 watch_paths.dedup();
106
107 for path in &watch_paths {
108 trace!("Watching path {path:?}");
109 debouncer
110 .watch(path, RecursiveMode::Recursive)
111 .expect("Failed to watch path");
112 }
113
114 (debouncer, rx)
117}
118
119pub fn preview_command(args: &Args) {
120 info!("Loading workspace...");
121 let workspace = Workspace::current().unwrap();
122
123 info!("Getting target package...");
125 let (kid, package_name) = get_target_package(&workspace, args);
126 info!("Target package name: {package_name}");
127
128 info!("Watching package...");
129 let (_watcher, rx) = watch_krate(&workspace, &kid);
130
131 let current_dir = std::env::current_dir().expect("Failed to get current directory");
132 let mut builder = RanimUserLibraryBuilder::new(
133 workspace.clone(),
134 package_name.clone(),
135 args.clone(),
136 current_dir.clone(),
137 );
138
139 info!("Initial build");
140 builder.start_build();
141 let lib = builder
142 .res_rx
143 .recv_blocking()
144 .unwrap()
145 .expect("Failed on initial build");
146
147 let scene = lib.get_preview_func();
148 let app = AppState::new_with_title(scene.constructor, scene.name.to_string());
149 let cmd_tx = app.cmd_tx.clone();
150
151 let res_rx = builder.res_rx.clone();
152 let (shutdown_tx, shutdown_rx) = bounded(1);
153 let daemon = thread::spawn(move || {
154 let mut lib = Some(lib);
155 loop {
156 if let Ok(events) = rx.try_recv() {
157 for event in events {
158 info!("{:?}: {:?}", event.kind, event.paths);
159 }
160 builder.start_build();
161 }
162 if let Ok(new_lib) = res_rx.try_recv()
163 && let Ok(new_lib) = new_lib
164 {
165 let scene = new_lib.get_preview_func();
166
167 let (tx, rx) = bounded(1);
168 cmd_tx
169 .send_blocking(AppCmd::ReloadScene(Box::new(scene.constructor), tx))
170 .unwrap();
171 rx.recv_blocking().unwrap();
172 lib.replace(new_lib);
173 }
174 if shutdown_rx.try_recv().is_ok() {
175 info!("exiting event loop...");
176 break;
177 }
178 std::thread::sleep(Duration::from_millis(200));
179 }
180 });
181 ranim::app::run_app(app);
182 shutdown_tx.send_blocking(()).unwrap();
183 daemon.join().unwrap();
184}