ranim_cli/cli/
preview.rs

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 anyhow::Result;
11use async_channel::{Receiver, bounded, unbounded};
12use log::{error, info, trace};
13use notify::RecursiveMode;
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    // All krates need to be watched, including the main package.
40    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    // Some more?
116
117    (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    // Get the target package
125    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 = args.target.clone().map(Target::from).unwrap_or_default();
130    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    // error!("Failed to get preview scene, available scenes:");
159    // for scene in lib.scenes() {
160    //     info!("- {:?}", scene.name);
161    // }
162    // panic!("Failed to get preview scene");
163    let mut app = AppState::new_with_title(scene.constructor, scene.name.to_string());
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(scene) => new_lib.scenes().find(|s| s.name == scene),
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 scene = scene.unwrap();
192
193                let (tx, rx) = bounded(1);
194                cmd_tx
195                    .send_blocking(AppCmd::ReloadScene(Box::new(scene.clone()), tx))
196                    .unwrap();
197                rx.recv_blocking().unwrap();
198                lib.replace(new_lib);
199            }
200            if shutdown_rx.try_recv().is_ok() {
201                info!("exiting event loop...");
202                break;
203            }
204            std::thread::sleep(Duration::from_millis(200));
205        }
206    });
207    ranim::app::run_app(app);
208    shutdown_tx.send_blocking(()).unwrap();
209    daemon.join().unwrap();
210    Ok(())
211}