1#![cfg_attr(not(target_arch = "wasm32"), deny(unused_crate_dependencies))]
2
3#[cfg(not(target_arch = "wasm32"))]
4mod main_impl {
5 use camino::Utf8PathBuf;
6 use clap::Parser;
7 use color_eyre::eyre::Context;
8 use color_eyre::Result;
9 use egui::Vec2;
10 use libsurfer::{
11 batch_commands::read_command_file,
12 file_watcher::FileWatcher,
13 logs,
14 message::Message,
15 run_egui,
16 wave_source::{string_to_wavesource, WaveSource},
17 StartupParams, SystemState,
18 };
19 use log::error;
20
21 #[derive(clap::Subcommand)]
22 enum Commands {
23 #[cfg(not(target_arch = "wasm32"))]
24 Server {
26 #[clap(long)]
28 port: Option<u16>,
29 #[clap(long)]
31 token: Option<String>,
32 #[arg(long)]
34 file: String,
35 },
36 }
37
38 #[derive(clap::Parser, Default)]
39 #[command(version, about)]
40 struct Args {
41 wave_file: Option<String>,
43 #[cfg(feature = "spade")]
44 #[clap(long)]
45 spade_state: Option<Utf8PathBuf>,
47 #[cfg(feature = "spade")]
48 #[clap(long)]
49 spade_top: Option<String>,
51 #[clap(long, short, verbatim_doc_comment)]
58 command_file: Option<Utf8PathBuf>,
59 #[clap(long)]
61 script: Option<Utf8PathBuf>,
62
63 #[clap(long, short)]
64 state_file: Option<Utf8PathBuf>,
66
67 #[clap(long, action)]
68 wcp_initiate: Option<u16>,
70
71 #[command(subcommand)]
72 command: Option<Commands>,
73 }
74
75 impl Args {
76 pub fn command_file(&self) -> &Option<Utf8PathBuf> {
77 if self.script.is_some() && self.command_file.is_some() {
78 error!("At most one of --command_file and --script can be used");
79 return &None;
80 }
81 if self.command_file.is_some() {
82 &self.command_file
83 } else {
84 &self.script
85 }
86 }
87 }
88
89 #[allow(dead_code)] fn startup_params_from_args(args: Args) -> StartupParams {
91 let startup_commands = if let Some(cmd_file) = args.command_file() {
92 read_command_file(cmd_file)
93 } else {
94 vec![]
95 };
96 StartupParams {
97 #[cfg(feature = "spade")]
98 spade_state: args.spade_state,
99 #[cfg(feature = "spade")]
100 spade_top: args.spade_top,
101 #[cfg(not(feature = "spade"))]
102 spade_state: None,
103 #[cfg(not(feature = "spade"))]
104 spade_top: None,
105 waves: args.wave_file.map(|s| string_to_wavesource(&s)),
106 wcp_initiate: args.wcp_initiate,
107 startup_commands,
108 }
109 }
110
111 #[cfg(not(target_arch = "wasm32"))]
112 pub(crate) fn main() -> Result<()> {
113 use libsurfer::{
114 state::UserState, translation::wasm_translator::discover_wasm_translators,
115 };
116
117 logs::start_logging()?;
118
119 let runtime = tokio::runtime::Builder::new_current_thread()
124 .worker_threads(1)
125 .enable_all()
126 .build()
127 .unwrap();
128
129 let args = Args::parse();
131 #[cfg(not(target_arch = "wasm32"))]
132 if let Some(Commands::Server { port, token, file }) = args.command {
133 let default_port = 8911; let res = runtime.block_on(surver::server_main(
135 port.unwrap_or(default_port),
136 token,
137 file,
138 None,
139 ));
140 return res;
141 }
142
143 let _enter = runtime.enter();
144
145 std::thread::spawn(move || {
146 runtime.block_on(async {
147 loop {
148 tokio::time::sleep(tokio::time::Duration::from_secs(3600)).await;
149 }
150 });
151 });
152
153 let state_file = args.state_file.clone();
154 let startup_params = startup_params_from_args(args);
155 let waves = startup_params.waves.clone();
156
157 let state = match &state_file {
158 Some(file) => std::fs::read_to_string(file)
159 .with_context(|| format!("Failed to read state from {file}"))
160 .and_then(|content| {
161 ron::from_str::<UserState>(&content)
162 .with_context(|| format!("Failed to decode state from {file}"))
163 })
164 .map(SystemState::from)
165 .map(|mut s| {
166 s.user.state_file = Some(file.into());
167 s
168 })
169 .or_else(|e| {
170 error!("Failed to read state file. Opening fresh session\n{e:#?}");
171 SystemState::new()
172 })?,
173 None => SystemState::new()?,
174 }
175 .with_params(startup_params);
176
177 let sender = state.channels.msg_sender.clone();
180 for message in discover_wasm_translators() {
181 sender.send(message).unwrap();
182 }
183
184 let _watcher = match waves {
187 Some(WaveSource::File(path)) => {
188 let sender = state.channels.msg_sender.clone();
189 FileWatcher::new(&path, move || {
190 match sender.send(Message::SuggestReloadWaveform) {
191 Ok(_) => {}
192 Err(err) => {
193 error!("Message ReloadWaveform did not send:\n{err}")
194 }
195 }
196 })
197 .inspect_err(|err| error!("Cannot set up the file watcher:\n{err}"))
198 .ok()
199 }
200 _ => None,
201 };
202
203 let options = eframe::NativeOptions {
204 viewport: egui::ViewportBuilder::default()
205 .with_app_id("org.surfer-project.surfer")
206 .with_title("Surfer")
207 .with_inner_size(Vec2::new(
208 state.user.config.layout.window_width as f32,
209 state.user.config.layout.window_height as f32,
210 )),
211 ..Default::default()
212 };
213
214 eframe::run_native("Surfer", options, Box::new(|cc| Ok(run_egui(cc, state)?))).unwrap();
215
216 Ok(())
217 }
218}
219
220#[cfg(target_arch = "wasm32")]
221mod main_impl {
222 use eframe::wasm_bindgen::JsCast;
223 use eframe::web_sys;
224 use libsurfer::wasm_api::WebHandle;
225
226 pub(crate) fn main() -> color_eyre::Result<()> {
229 let document = web_sys::window()
230 .expect("No window")
231 .document()
232 .expect("No document");
233 let canvas = document
234 .get_element_by_id("the_canvas_id")
235 .expect("Failed to find the_canvas_id")
236 .dyn_into::<web_sys::HtmlCanvasElement>()
237 .expect("the_canvas_id was not a HtmlCanvasElement");
238
239 wasm_bindgen_futures::spawn_local(async {
240 let wh = WebHandle::new();
241 wh.start(canvas).await.expect("Failed to start surfer");
242 });
243
244 Ok(())
245 }
246}
247
248fn main() -> color_eyre::Result<()> {
249 main_impl::main()
250}