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 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    // All krates need to be watched, including the main package.
39    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    // Some more?
115
116    (debouncer, rx)
117}
118
119pub fn preview_command(args: &Args) {
120    info!("Loading workspace...");
121    let workspace = Workspace::current().unwrap();
122
123    // Get the target package
124    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}