1use std::{
2 collections::{HashMap, HashSet, VecDeque},
3 mem,
4 path::PathBuf,
5};
6
7use crate::displayed_item_tree::VisibleItemIndex;
8#[cfg(feature = "spade")]
9use crate::translation::spade::SpadeTranslator;
10use crate::{
11 async_util::perform_work,
12 config::SurferConfig,
13 data_container::DataContainer,
14 dialog::OpenSiblingStateFileDialog,
15 dialog::ReloadWaveformDialog,
16 displayed_item_tree::DisplayedItemTree,
17 message::Message,
18 system_state::SystemState,
19 time::{TimeStringFormatting, TimeUnit},
20 transaction_container::TransactionContainer,
21 variable_filter::VariableFilter,
22 viewport::Viewport,
23 wave_container::{ScopeRef, VariableRef, WaveContainer},
24 wave_data::WaveData,
25 wave_source::{LoadOptions, WaveFormat, WaveSource},
26 CanvasState, StartupParams,
27};
28use egui::{
29 style::{Selection, WidgetVisuals, Widgets},
30 CornerRadius, Stroke, Visuals,
31};
32use itertools::Itertools;
33use log::{error, info, trace, warn};
34use serde::{Deserialize, Serialize};
35use surfer_translation_types::Translator;
36
37#[derive(Serialize, Deserialize)]
39pub struct UserState {
40 #[serde(skip)]
41 pub config: SurferConfig,
42
43 pub(crate) show_hierarchy: Option<bool>,
45 pub(crate) show_menu: Option<bool>,
46 pub(crate) show_ticks: Option<bool>,
47 pub(crate) show_toolbar: Option<bool>,
48 pub(crate) show_tooltip: Option<bool>,
49 pub(crate) show_scope_tooltip: Option<bool>,
50 pub(crate) show_default_timeline: Option<bool>,
51 pub(crate) show_overview: Option<bool>,
52 pub(crate) show_statusbar: Option<bool>,
53 pub(crate) align_names_right: Option<bool>,
54 pub(crate) show_variable_indices: Option<bool>,
55 pub(crate) show_variable_direction: Option<bool>,
56 pub(crate) show_empty_scopes: Option<bool>,
57 pub(crate) show_parameters_in_scopes: Option<bool>,
58 #[serde(default)]
59 pub(crate) highlight_focused: Option<bool>,
60 #[serde(default)]
61 pub(crate) fill_high_values: Option<bool>,
62
63 pub(crate) waves: Option<WaveData>,
64 pub(crate) drag_started: bool,
65 pub(crate) drag_source_idx: Option<VisibleItemIndex>,
66 pub(crate) drag_target_idx: Option<crate::displayed_item_tree::TargetPosition>,
67
68 pub(crate) previous_waves: Option<WaveData>,
69
70 pub(crate) count: Option<String>,
72
73 pub(crate) blacklisted_translators: HashSet<(VariableRef, String)>,
75
76 pub(crate) show_about: bool,
77 pub(crate) show_keys: bool,
78 pub(crate) show_gestures: bool,
79 pub(crate) show_quick_start: bool,
80 pub(crate) show_license: bool,
81 pub(crate) show_performance: bool,
82 pub(crate) show_logs: bool,
83 pub(crate) show_cursor_window: bool,
84 pub(crate) wanted_timeunit: TimeUnit,
85 pub(crate) time_string_format: Option<TimeStringFormatting>,
86 pub(crate) show_url_entry: bool,
87 #[serde(skip, default)]
90 pub(crate) show_reload_suggestion: Option<ReloadWaveformDialog>,
91 #[serde(skip, default)]
92 pub(crate) show_open_sibling_state_file_suggestion: Option<OpenSiblingStateFileDialog>,
93 pub(crate) variable_name_filter_focused: bool,
94 pub(crate) variable_filter: VariableFilter,
95 pub(crate) rename_target: Option<VisibleItemIndex>,
96 pub(crate) sidepanel_width: Option<f32>,
98 pub(crate) ui_zoom_factor: Option<f32>,
100
101 #[serde(skip)]
107 pub state_file: Option<PathBuf>,
108}
109
110impl std::fmt::Debug for UserState {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 write!(f, "SharedState {{ <snipped> }}")
115 }
116}
117
118impl SystemState {
119 pub fn with_params(mut self, args: StartupParams) -> Self {
120 self.user.previous_waves = self.user.waves;
121 self.user.waves = None;
122
123 {
125 #[cfg(feature = "spade")]
126 let sender = self.channels.msg_sender.clone();
127 #[cfg(not(feature = "spade"))]
128 let _ = self.channels.msg_sender.clone();
129 let waves = args.waves.clone();
130 perform_work(move || {
131 #[cfg(feature = "spade")]
132 SpadeTranslator::load(&waves, &args.spade_top, &args.spade_state, sender);
133 #[cfg(not(feature = "spade"))]
134 if let (Some(_), Some(_)) = (args.spade_top, args.spade_state) {
135 info!("Surfer is not compiled with spade support, ignoring spade_top and spade_state");
136 }
137 });
138 }
139
140 self.batch_messages = VecDeque::new();
142
143 match args.waves {
144 Some(WaveSource::Url(url)) => {
145 self.add_batch_message(Message::LoadWaveformFileFromUrl(url, LoadOptions::clean()));
146 }
147 Some(WaveSource::File(file)) => {
148 self.add_batch_message(Message::LoadFile(file, LoadOptions::clean()));
149 }
150 Some(WaveSource::Data) => error!("Attempted to load data at startup"),
151 Some(WaveSource::Cxxrtl(url)) => {
152 self.add_batch_message(Message::SetupCxxrtl(url));
153 }
154 Some(WaveSource::DragAndDrop(_)) => {
155 error!("Attempted to load from drag and drop at startup (how?)");
156 }
157 None => {}
158 }
159
160 if let Some(port) = args.wcp_initiate {
161 let addr = format!("127.0.0.1:{}", port);
162 self.add_batch_message(Message::StartWcpServer {
163 address: Some(addr),
164 initiate: true,
165 });
166 }
167
168 self.add_batch_commands(args.startup_commands);
169
170 self
171 }
172
173 pub fn wcp(&mut self) {
174 self.handle_wcp_commands();
175 }
176
177 pub(crate) fn get_scope(&mut self, scope: ScopeRef, recursive: bool) -> Vec<VariableRef> {
178 let Some(waves) = self.user.waves.as_mut() else {
179 return vec![];
180 };
181
182 let wave_cont = waves.inner.as_waves().unwrap();
183
184 let children = wave_cont.child_scopes(&scope);
185 let mut variables = wave_cont
186 .variables_in_scope(&scope)
187 .iter()
188 .sorted_by(|a, b| numeric_sort::cmp(&a.name, &b.name))
189 .cloned()
190 .collect_vec();
191
192 if recursive {
193 if let Ok(children) = children {
194 for child in children {
195 variables.append(&mut self.get_scope(child, true));
196 }
197 }
198 }
199
200 variables
201 }
202
203 pub(crate) fn on_waves_loaded(
204 &mut self,
205 filename: WaveSource,
206 format: WaveFormat,
207 new_waves: Box<WaveContainer>,
208 load_options: LoadOptions,
209 ) {
210 info!("{format} file loaded");
211 let viewport = Viewport::new();
212 let viewports = [viewport].to_vec();
213
214 for translator in self.translators.all_translators() {
215 translator.set_wave_source(Some(filename.into_translation_type()));
216 }
217
218 let ((new_wave, load_commands), is_reload) =
219 if load_options.keep_variables && self.user.waves.is_some() {
220 (
221 self.user.waves.take().unwrap().update_with_waves(
222 new_waves,
223 filename,
224 format,
225 &self.translators,
226 load_options.keep_unavailable,
227 ),
228 true,
229 )
230 } else if let Some(old) = self.user.previous_waves.take() {
231 (
232 old.update_with_waves(
233 new_waves,
234 filename,
235 format,
236 &self.translators,
237 load_options.keep_unavailable,
238 ),
239 true,
240 )
241 } else {
242 (
243 (
244 WaveData {
245 inner: DataContainer::Waves(*new_waves),
246 source: filename,
247 format,
248 active_scope: None,
249 items_tree: DisplayedItemTree::default(),
250 displayed_items: HashMap::new(),
251 viewports,
252 cursor: None,
253 markers: HashMap::new(),
254 focused_item: None,
255 focused_transaction: (None, None),
256 default_variable_name_type: self.user.config.default_variable_name_type,
257 display_variable_indices: self.show_variable_indices(),
258 scroll_offset: 0.,
259 drawing_infos: vec![],
260 top_item_draw_offset: 0.,
261 total_height: 0.,
262 display_item_ref_counter: 0,
263 old_num_timestamps: None,
264 graphics: HashMap::new(),
265 },
266 None,
267 ),
268 false,
269 )
270 };
271
272 if let Some(cmd) = load_commands {
273 self.load_variables(cmd);
274 }
275 self.invalidate_draw_commands();
276
277 self.user.waves = Some(new_wave);
278
279 if !is_reload {
280 if let Some(waves) = &mut self.user.waves {
281 self.user.wanted_timeunit = waves.inner.metadata().timescale.unit;
283 if waves.source.sibling_state_file().is_some() {
285 self.update(Message::SuggestOpenSiblingStateFile);
286 }
287 }
288 }
289 }
290
291 pub(crate) fn on_transaction_streams_loaded(
292 &mut self,
293 filename: WaveSource,
294 format: WaveFormat,
295 new_ftr: TransactionContainer,
296 _loaded_options: LoadOptions,
297 ) {
298 info!("Transaction streams are loaded.");
299
300 let viewport = Viewport::new();
301 let viewports = [viewport].to_vec();
302
303 let new_transaction_streams = WaveData {
304 inner: DataContainer::Transactions(new_ftr),
305 source: filename,
306 format,
307 active_scope: None,
308 items_tree: DisplayedItemTree::default(),
309 displayed_items: HashMap::new(),
310 viewports,
311 cursor: None,
312 markers: HashMap::new(),
313 focused_item: None,
314 focused_transaction: (None, None),
315 default_variable_name_type: self.user.config.default_variable_name_type,
316 display_variable_indices: self.show_variable_indices(),
317 scroll_offset: 0.,
318 drawing_infos: vec![],
319 top_item_draw_offset: 0.,
320 total_height: 0.,
321 display_item_ref_counter: 0,
322 old_num_timestamps: None,
323 graphics: HashMap::new(),
324 };
325
326 self.invalidate_draw_commands();
327
328 self.user.config.theme.alt_frequency = 0;
329 self.user.wanted_timeunit = new_transaction_streams.inner.metadata().timescale.unit;
330 self.user.waves = Some(new_transaction_streams);
331 }
332
333 pub(crate) fn handle_async_messages(&mut self) {
334 let mut msgs = vec![];
335 loop {
336 match self.channels.msg_receiver.try_recv() {
337 Ok(msg) => msgs.push(msg),
338 Err(std::sync::mpsc::TryRecvError::Empty) => break,
339 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
340 trace!("Message sender disconnected");
341 break;
342 }
343 }
344 }
345
346 while let Some(msg) = msgs.pop() {
347 self.update(msg);
348 }
349 }
350
351 pub fn get_visuals(&self) -> Visuals {
352 let widget_style = WidgetVisuals {
353 bg_fill: self.user.config.theme.secondary_ui_color.background,
354 fg_stroke: Stroke {
355 color: self.user.config.theme.secondary_ui_color.foreground,
356 width: 1.0,
357 },
358 weak_bg_fill: self.user.config.theme.secondary_ui_color.background,
359 bg_stroke: Stroke {
360 color: self.user.config.theme.border_color,
361 width: 1.0,
362 },
363 corner_radius: CornerRadius::same(2),
364 expansion: 0.0,
365 };
366
367 Visuals {
368 override_text_color: Some(self.user.config.theme.foreground),
369 extreme_bg_color: self.user.config.theme.secondary_ui_color.background,
370 panel_fill: self.user.config.theme.secondary_ui_color.background,
371 window_fill: self.user.config.theme.primary_ui_color.background,
372 window_stroke: Stroke {
373 width: 1.0,
374 color: self.user.config.theme.border_color,
375 },
376 selection: Selection {
377 bg_fill: self.user.config.theme.selected_elements_colors.background,
378 stroke: Stroke {
379 color: self.user.config.theme.selected_elements_colors.foreground,
380 width: 1.0,
381 },
382 },
383 widgets: Widgets {
384 noninteractive: widget_style,
385 inactive: widget_style,
386 hovered: widget_style,
387 active: widget_style,
388 open: widget_style,
389 },
390 ..Visuals::dark()
391 }
392 }
393
394 pub(crate) fn load_state(&mut self, mut loaded_state: Box<UserState>, path: Option<PathBuf>) {
395 mem::swap(&mut self.user, &mut loaded_state);
397
398 mem::swap(&mut loaded_state.waves, &mut self.user.waves);
401 let load_commands = if let (Some(waves), Some(new_waves)) =
402 (&mut self.user.waves, &mut loaded_state.waves)
403 {
404 mem::swap(&mut waves.active_scope, &mut new_waves.active_scope);
405 let items = std::mem::take(&mut new_waves.displayed_items);
406 let items_tree = std::mem::take(&mut new_waves.items_tree);
407 let load_commands = waves.update_with_items(&items, items_tree, &self.translators);
408
409 mem::swap(&mut waves.viewports, &mut new_waves.viewports);
410 mem::swap(&mut waves.cursor, &mut new_waves.cursor);
411 mem::swap(&mut waves.markers, &mut new_waves.markers);
412 mem::swap(&mut waves.focused_item, &mut new_waves.focused_item);
413 waves.default_variable_name_type = new_waves.default_variable_name_type;
414 waves.scroll_offset = new_waves.scroll_offset;
415 load_commands
416 } else {
417 None
418 };
419 if let Some(load_commands) = load_commands {
420 self.load_variables(load_commands);
421 };
422
423 self.user.drag_started = false;
425 self.user.drag_source_idx = None;
426 self.user.drag_target_idx = None;
427
428 self.user.previous_waves = None;
430 self.user.count = None;
431
432 self.user.state_file = path;
434 self.user.rename_target = None;
435
436 self.invalidate_draw_commands();
437 if let Some(waves) = &mut self.user.waves {
438 waves.update_viewports();
439 }
440 }
441
442 pub fn waves_fully_loaded(&self) -> bool {
446 self.user
447 .waves
448 .as_ref()
449 .is_some_and(|w| w.inner.is_fully_loaded())
450 }
451
452 pub(crate) fn current_canvas_state(waves: &WaveData, message: String) -> CanvasState {
454 CanvasState {
455 message,
456 focused_item: waves.focused_item,
457 focused_transaction: waves.focused_transaction.clone(),
458 items_tree: waves.items_tree.clone(),
459 displayed_items: waves.displayed_items.clone(),
460 markers: waves.markers.clone(),
461 }
462 }
463
464 pub(crate) fn save_current_canvas(&mut self, message: String) {
466 if let Some(waves) = &self.user.waves {
467 self.undo_stack
468 .push(SystemState::current_canvas_state(waves, message));
469
470 if self.undo_stack.len() > self.user.config.undo_stack_size {
471 self.undo_stack.remove(0);
472 }
473 self.redo_stack.clear();
474 }
475 }
476
477 #[cfg(not(target_arch = "wasm32"))]
478 pub(crate) fn start_wcp_server(&mut self, address: Option<String>, initiate: bool) {
479 use wcp::wcp_server::WcpServer;
480
481 use crate::wcp;
482
483 if self.wcp_server_thread.as_ref().is_some()
484 || self
485 .wcp_running_signal
486 .load(std::sync::atomic::Ordering::Relaxed)
487 {
488 warn!("WCP HTTP server is already running");
489 return;
490 }
491 let (wcp_s2c_sender, wcp_s2c_receiver) = tokio::sync::mpsc::channel(100);
493 let (wcp_c2s_sender, wcp_c2s_receiver) = tokio::sync::mpsc::channel(100);
494
495 self.channels.wcp_c2s_receiver = Some(wcp_c2s_receiver);
496 self.channels.wcp_s2c_sender = Some(wcp_s2c_sender);
497 let stop_signal_copy = self.wcp_stop_signal.clone();
498 stop_signal_copy.store(false, std::sync::atomic::Ordering::Relaxed);
499 let running_signal_copy = self.wcp_running_signal.clone();
500 running_signal_copy.store(true, std::sync::atomic::Ordering::Relaxed);
501 let greeted_signal_copy = self.wcp_greeted_signal.clone();
502 greeted_signal_copy.store(true, std::sync::atomic::Ordering::Relaxed);
503
504 let ctx = self.context.clone();
505 let address = address.unwrap_or(self.user.config.wcp.address.clone());
506 self.wcp_server_address = Some(address.clone());
507 self.wcp_server_thread = Some(tokio::spawn(async move {
508 let server = WcpServer::new(
509 address,
510 initiate,
511 wcp_c2s_sender,
512 wcp_s2c_receiver,
513 stop_signal_copy,
514 running_signal_copy,
515 greeted_signal_copy,
516 ctx,
517 )
518 .await;
519 match server {
520 Ok(mut server) => server.run().await,
521 Err(m) => {
522 error!("Could not start WCP server. {m:?}")
523 }
524 }
525 }));
526 }
527
528 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
529 pub(crate) fn stop_wcp_server(&mut self) {
530 if self.wcp_server_address.is_some() && self.wcp_server_thread.is_some() {
533 self.wcp_stop_signal
535 .store(true, std::sync::atomic::Ordering::Relaxed);
536
537 self.wcp_server_thread = None;
538 self.wcp_server_address = None;
539 self.channels.wcp_s2c_sender = None;
540 self.channels.wcp_c2s_receiver = None;
541 info!("Stopped WCP server");
542 }
543 }
544}