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 file_watcher::FileWatcher, logs, message::Message, run_egui,
12 wave_source::string_to_wavesource, wave_source::WaveSource, StartupParams, SystemState,
13 };
14 use log::error;
15
16 #[derive(clap::Subcommand)]
17 enum Commands {
18 #[cfg(not(target_arch = "wasm32"))]
19 Server {
21 #[clap(long)]
23 port: Option<u16>,
24 #[clap(long)]
26 token: Option<String>,
27 #[arg(long)]
29 file: String,
30 },
31 }
32
33 #[derive(clap::Parser, Default)]
34 #[command(version, about)]
35 struct Args {
36 wave_file: Option<String>,
38 #[cfg(feature = "spade")]
39 #[clap(long)]
40 spade_state: Option<Utf8PathBuf>,
42 #[cfg(feature = "spade")]
43 #[clap(long)]
44 spade_top: 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 std::fs::read_to_string(cmd_file)
88 .map_err(|e| error!("Failed to read commands from {cmd_file}. {e:#?}"))
89 .ok()
90 .map(|file_content| {
91 file_content
92 .lines()
93 .map(std::string::ToString::to_string)
94 .collect()
95 })
96 .unwrap_or_default()
97 } else {
98 vec![]
99 };
100 StartupParams {
101 #[cfg(feature = "spade")]
102 spade_state: args.spade_state,
103 #[cfg(feature = "spade")]
104 spade_top: args.spade_top,
105 #[cfg(not(feature = "spade"))]
106 spade_state: None,
107 #[cfg(not(feature = "spade"))]
108 spade_top: None,
109 waves: args.wave_file.map(|s| string_to_wavesource(&s)),
110 wcp_initiate: args.wcp_initiate,
111 startup_commands,
112 }
113 }
114
115 #[cfg(not(target_arch = "wasm32"))]
116 pub(crate) fn main() -> Result<()> {
117 use libsurfer::state::UserState;
118
119 logs::start_logging()?;
120
121 let runtime = tokio::runtime::Builder::new_current_thread()
126 .worker_threads(1)
127 .enable_all()
128 .build()
129 .unwrap();
130
131 let args = Args::parse();
133 #[cfg(not(target_arch = "wasm32"))]
134 if let Some(Commands::Server { port, token, file }) = args.command {
135 let default_port = 8911; let res = runtime.block_on(surver::server_main(
137 port.unwrap_or(default_port),
138 token,
139 file,
140 None,
141 ));
142 return res;
143 }
144
145 let _enter = runtime.enter();
146
147 std::thread::spawn(move || {
148 runtime.block_on(async {
149 loop {
150 tokio::time::sleep(tokio::time::Duration::from_secs(3600)).await;
151 }
152 });
153 });
154
155 let state_file = args.state_file.clone();
156 let startup_params = startup_params_from_args(args);
157 let waves = startup_params.waves.clone();
158
159 let state = match &state_file {
160 Some(file) => std::fs::read_to_string(file)
161 .with_context(|| format!("Failed to read state from {file}"))
162 .and_then(|content| {
163 ron::from_str::<UserState>(&content)
164 .with_context(|| format!("Failed to decode state from {file}"))
165 })
166 .map(SystemState::from)
167 .map(|mut s| {
168 s.user.state_file = Some(file.into());
169 s
170 })
171 .or_else(|e| {
172 error!("Failed to read state file. Opening fresh session\n{e:#?}");
173 SystemState::new()
174 })?,
175 None => SystemState::new()?,
176 }
177 .with_params(startup_params);
178
179 let _watcher = match waves {
182 Some(WaveSource::File(path)) => {
183 let sender = state.channels.msg_sender.clone();
184 FileWatcher::new(&path, move || {
185 match sender.send(Message::SuggestReloadWaveform) {
186 Ok(_) => {}
187 Err(err) => {
188 error!("Message ReloadWaveform did not send:\n{err}")
189 }
190 }
191 })
192 .inspect_err(|err| error!("Cannot set up the file watcher:\n{err}"))
193 .ok()
194 }
195 _ => None,
196 };
197
198 let options = eframe::NativeOptions {
199 viewport: egui::ViewportBuilder::default()
200 .with_title("Surfer")
201 .with_inner_size(Vec2::new(
202 state.user.config.layout.window_width as f32,
203 state.user.config.layout.window_height as f32,
204 )),
205 ..Default::default()
206 };
207
208 eframe::run_native("Surfer", options, Box::new(|cc| Ok(run_egui(cc, state)?))).unwrap();
209
210 Ok(())
211 }
212}
213
214#[cfg(target_arch = "wasm32")]
215mod main_impl {
216 use eframe::wasm_bindgen::JsCast;
217 use eframe::web_sys;
218 use libsurfer::wasm_api::WebHandle;
219
220 pub(crate) fn main() -> color_eyre::Result<()> {
223 let document = web_sys::window()
224 .expect("No window")
225 .document()
226 .expect("No document");
227 let canvas = document
228 .get_element_by_id("the_canvas_id")
229 .expect("Failed to find the_canvas_id")
230 .dyn_into::<web_sys::HtmlCanvasElement>()
231 .expect("the_canvas_id was not a HtmlCanvasElement");
232
233 wasm_bindgen_futures::spawn_local(async {
234 let wh = WebHandle::new();
235 wh.start(canvas).await.expect("Failed to start surfer");
236 });
237
238 Ok(())
239 }
240}
241
242fn main() -> color_eyre::Result<()> {
243 main_impl::main()
244}