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::{error, 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 Ok(scene) = lib.get_preview_func() else {
148 error!("Failed to get preview scene, available scenes:");
149 for scene in lib.scenes() {
150 info!("Scene: {:?} preview: {:?}", scene.name, scene.preview);
151 }
152 panic!("Failed to get preview scene");
153 };
154 let app = AppState::new_with_title(scene.constructor, scene.name.to_string());
155 let cmd_tx = app.cmd_tx.clone();
156
157 let res_rx = builder.res_rx.clone();
158 let (shutdown_tx, shutdown_rx) = bounded(1);
159 let daemon = thread::spawn(move || {
160 let mut lib = Some(lib);
161 loop {
162 if let Ok(events) = rx.try_recv() {
163 for event in events {
164 info!("{:?}: {:?}", event.kind, event.paths);
165 }
166 builder.start_build();
167 }
168 if let Ok(new_lib) = res_rx.try_recv()
169 && let Ok(new_lib) = new_lib
170 {
171 let Ok(scene) = new_lib.get_preview_func() else {
172 error!("Failed to get preview scene, available scenes:");
173 for scene in new_lib.scenes() {
174 info!("Scene: {:?} preview: {:?}", scene.name, scene.preview);
175 }
176 continue;
177 };
178
179 let (tx, rx) = bounded(1);
180 cmd_tx
181 .send_blocking(AppCmd::ReloadScene(Box::new(scene.constructor), tx))
182 .unwrap();
183 rx.recv_blocking().unwrap();
184 lib.replace(new_lib);
185 }
186 if shutdown_rx.try_recv().is_ok() {
187 info!("exiting event loop...");
188 break;
189 }
190 std::thread::sleep(Duration::from_millis(200));
191 }
192 });
193 ranim::app::run_app(app);
194 shutdown_tx.send_blocking(()).unwrap();
195 daemon.join().unwrap();
196}