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 egui::Vec2;
8 use eyre::Context;
9 use eyre::Result;
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 #[clap(long, short, verbatim_doc_comment)]
50 command_file: Option<Utf8PathBuf>,
51 #[clap(long)]
53 script: Option<Utf8PathBuf>,
54
55 #[clap(long, short)]
56 state_file: Option<Utf8PathBuf>,
58
59 #[clap(long, action)]
60 wcp_initiate: Option<u16>,
62
63 #[command(subcommand)]
64 command: Option<Commands>,
65 }
66
67 impl Args {
68 pub fn command_file(&self) -> &Option<Utf8PathBuf> {
69 if self.script.is_some() && self.command_file.is_some() {
70 error!("At most one of --command_file and --script can be used");
71 return &None;
72 }
73 if self.command_file.is_some() {
74 &self.command_file
75 } else {
76 &self.script
77 }
78 }
79 }
80
81 #[allow(dead_code)] fn startup_params_from_args(args: Args) -> StartupParams {
83 let startup_commands = if let Some(cmd_file) = args.command_file() {
84 read_command_file(cmd_file)
85 } else {
86 vec![]
87 };
88 StartupParams {
89 waves: args.wave_file.map(|s| string_to_wavesource(&s)),
90 wcp_initiate: args.wcp_initiate,
91 startup_commands,
92 }
93 }
94
95 #[cfg(not(target_arch = "wasm32"))]
96 pub(crate) fn main() -> Result<()> {
97 use libsurfer::{
98 state::UserState, translation::wasm_translator::discover_wasm_translators,
99 };
100 simple_eyre::install()?;
101
102 logs::start_logging()?;
103
104 let runtime = tokio::runtime::Builder::new_current_thread()
109 .worker_threads(1)
110 .enable_all()
111 .build()
112 .unwrap();
113
114 let args = Args::parse();
116 #[cfg(not(target_arch = "wasm32"))]
117 if let Some(Commands::Server { port, token, file }) = args.command {
118 let default_port = 8911; let res = runtime.block_on(surver::server_main(
120 port.unwrap_or(default_port),
121 token,
122 file,
123 None,
124 ));
125 return res;
126 }
127
128 let _enter = runtime.enter();
129
130 std::thread::spawn(move || {
131 runtime.block_on(async {
132 loop {
133 tokio::time::sleep(tokio::time::Duration::from_secs(3600)).await;
134 }
135 });
136 });
137
138 let state_file = args.state_file.clone();
139 let startup_params = startup_params_from_args(args);
140 let waves = startup_params.waves.clone();
141
142 let state = match &state_file {
143 Some(file) => std::fs::read_to_string(file)
144 .with_context(|| format!("Failed to read state from {file}"))
145 .and_then(|content| {
146 ron::from_str::<UserState>(&content)
147 .with_context(|| format!("Failed to decode state from {file}"))
148 })
149 .map(SystemState::from)
150 .map(|mut s| {
151 s.user.state_file = Some(file.into());
152 s
153 })
154 .or_else(|e| {
155 error!("Failed to read state file. Opening fresh session\n{e:#?}");
156 SystemState::new()
157 })?,
158 None => SystemState::new()?,
159 }
160 .with_params(startup_params);
161
162 let sender = state.channels.msg_sender.clone();
165 for message in discover_wasm_translators() {
166 sender.send(message).unwrap();
167 }
168
169 let _watcher = match waves {
172 Some(WaveSource::File(path)) => {
173 let sender = state.channels.msg_sender.clone();
174 FileWatcher::new(&path, move || {
175 match sender.send(Message::SuggestReloadWaveform) {
176 Ok(_) => {}
177 Err(err) => {
178 error!("Message ReloadWaveform did not send:\n{err}")
179 }
180 }
181 })
182 .inspect_err(|err| error!("Cannot set up the file watcher:\n{err}"))
183 .ok()
184 }
185 _ => None,
186 };
187
188 let options = eframe::NativeOptions {
189 viewport: egui::ViewportBuilder::default()
190 .with_app_id("org.surfer-project.surfer")
191 .with_title("Surfer")
192 .with_inner_size(Vec2::new(
193 state.user.config.layout.window_width as f32,
194 state.user.config.layout.window_height as f32,
195 )),
196 ..Default::default()
197 };
198
199 eframe::run_native("Surfer", options, Box::new(|cc| Ok(run_egui(cc, state)?))).unwrap();
200
201 Ok(())
202 }
203}
204
205#[cfg(target_arch = "wasm32")]
206mod main_impl {
207 use eframe::wasm_bindgen::JsCast;
208 use eframe::web_sys;
209 use libsurfer::wasm_api::WebHandle;
210
211 pub(crate) fn main() -> eyre::Result<()> {
214 simple_eyre::install()?;
215 let document = web_sys::window()
216 .expect("No window")
217 .document()
218 .expect("No document");
219 let canvas = document
220 .get_element_by_id("the_canvas_id")
221 .expect("Failed to find the_canvas_id")
222 .dyn_into::<web_sys::HtmlCanvasElement>()
223 .expect("the_canvas_id was not a HtmlCanvasElement");
224
225 wasm_bindgen_futures::spawn_local(async {
226 let wh = WebHandle::new();
227 wh.start(canvas).await.expect("Failed to start surfer");
228 });
229
230 Ok(())
231 }
232}
233
234fn main() -> eyre::Result<()> {
235 main_impl::main()
236}