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