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::{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    // 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 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}