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