1use std::{
2 collections::{HashMap, HashSet, VecDeque},
3 mem,
4 path::PathBuf,
5};
6
7use crate::{
8 CanvasState, StartupParams,
9 annotation_list::AnnotationGroup,
10 clock_highlighting::ClockHighlightType,
11 config::{ArrowKeyBindings, AutoLoad, PrimaryMouseDrag, SurferConfig, TransitionValue},
12 data_container::DataContainer,
13 dialog::{OpenSiblingStateFileDialog, ReloadWaveformDialog},
14 displayed_item_tree::{DisplayedItemTree, VisibleItemIndex},
15 frame_buffer::FrameBufferSettings,
16 hierarchy::{HierarchyStyle, ParameterDisplayLocation},
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};
27use egui::{
28 Visuals,
29 style::{Selection, WidgetVisuals, Widgets},
30};
31use epaint::{CornerRadius, Stroke};
32use eyre::{Result, WrapErr as _};
33use itertools::Itertools;
34use serde::{Deserialize, Serialize};
35use surfer_translation_types::Translator;
36use surver::SurverFileInfo;
37use tracing::{error, info, trace, warn};
38
39#[derive(Serialize, Deserialize)]
41#[serde(default)]
42pub struct UserState {
43 #[serde(skip)]
44 pub config: SurferConfig,
45
46 pub(crate) show_hierarchy: Option<bool>,
48 pub(crate) show_menu: Option<bool>,
49 pub(crate) show_ticks: Option<bool>,
50 pub(crate) show_toolbar: Option<bool>,
51 pub(crate) show_tooltip: Option<bool>,
52 pub(crate) show_scope_tooltip: Option<bool>,
53 pub(crate) show_default_timeline: Option<bool>,
54 pub(crate) show_overview: Option<bool>,
55 pub(crate) show_statusbar: Option<bool>,
56 pub(crate) align_names_right: Option<bool>,
57 pub(crate) show_variable_indices: Option<bool>,
58 pub(crate) show_variable_direction: Option<bool>,
59 pub(crate) show_empty_scopes: Option<bool>,
60 pub(crate) show_hierarchy_icons: Option<bool>,
61 pub(crate) show_parameters_in_scopes: Option<bool>,
62 #[serde(default)]
63 pub(crate) parameter_display_location: Option<ParameterDisplayLocation>,
64 #[serde(default)]
65 pub(crate) highlight_focused: Option<bool>,
66 #[serde(default)]
67 pub(crate) fill_high_values: Option<bool>,
68 #[serde(default)]
69 pub(crate) primary_button_drag_behavior: Option<PrimaryMouseDrag>,
70 #[serde(default)]
71 pub(crate) arrow_key_bindings: Option<ArrowKeyBindings>,
72 #[serde(default)]
73 pub(crate) clock_highlight_type: Option<ClockHighlightType>,
74 #[serde(default)]
75 pub(crate) hierarchy_style: Option<HierarchyStyle>,
76 #[serde(default)]
77 pub(crate) autoload_sibling_state_files: Option<AutoLoad>,
78 #[serde(default)]
79 pub(crate) autoreload_files: Option<AutoLoad>,
80
81 pub(crate) waves: Option<WaveData>,
82 pub(crate) drag_started: bool,
83 pub(crate) drag_source_idx: Option<VisibleItemIndex>,
84 pub(crate) drag_target_idx: Option<crate::displayed_item_tree::TargetPosition>,
85
86 pub(crate) previous_waves: Option<WaveData>,
87
88 pub(crate) count: Option<String>,
90
91 pub(crate) blacklisted_translators: HashSet<(VariableRef, String)>,
93
94 pub(crate) show_about: bool,
95 pub(crate) show_keys: bool,
96 pub(crate) show_gestures: bool,
97 pub(crate) show_quick_start: bool,
98 pub(crate) show_license: bool,
99 pub(crate) show_performance: bool,
100 pub(crate) show_logs: bool,
101 pub(crate) show_cursor_window: bool,
102 #[serde(default)]
103 pub(crate) frame_buffer: FrameBufferSettings,
104 pub(crate) wanted_timeunit: TimeUnit,
105 pub(crate) time_string_format: Option<TimeStringFormatting>,
106 pub(crate) show_url_entry: bool,
107 #[serde(skip, default)]
110 pub(crate) show_reload_suggestion: Option<ReloadWaveformDialog>,
111 #[serde(skip, default)]
112 pub(crate) show_open_sibling_state_file_suggestion: Option<OpenSiblingStateFileDialog>,
113 pub(crate) variable_name_filter_focused: bool,
114 pub(crate) variable_filter: VariableFilter,
115 pub(crate) sidepanel_width: Option<f32>,
117 pub(crate) ui_zoom_factor: Option<f32>,
119 #[serde(default)]
120 pub(crate) animation_enabled: Option<bool>,
121 #[serde(default)]
122 pub(crate) use_dinotrace_style: Option<bool>,
123 #[serde(skip, default)]
124 pub(crate) show_server_file_window: bool,
125 #[serde(skip, default)]
126 pub(crate) selected_server_file_index: Option<usize>,
127 #[serde(skip, default)]
128 pub(crate) surver_file_infos: Option<Vec<SurverFileInfo>>,
129 #[serde(skip, default)]
130 pub(crate) surver_url: Option<String>,
131 #[serde(default)]
132 pub(crate) transition_value: Option<TransitionValue>,
133
134 #[serde(skip)]
140 pub state_file: Option<PathBuf>,
141
142 pub(crate) show_annotation_list: bool,
143}
144
145impl std::fmt::Debug for UserState {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 write!(f, "SharedState {{ <snipped> }}")
150 }
151}
152
153impl UserState {
154 pub fn new(force_default_config: bool) -> Result<UserState> {
155 let config = SurferConfig::new(force_default_config)
156 .with_context(|| "Failed to load config file")?;
157
158 Ok(UserState {
159 config,
160 ..Default::default()
161 })
162 }
163}
164
165impl Default for UserState {
166 fn default() -> Self {
167 Self {
168 config: SurferConfig::default(),
169 show_hierarchy: None,
170 show_menu: None,
171 show_ticks: None,
172 show_toolbar: None,
173 show_tooltip: None,
174 show_scope_tooltip: None,
175 show_default_timeline: None,
176 show_overview: None,
177 show_statusbar: None,
178 align_names_right: None,
179 show_variable_indices: None,
180 show_variable_direction: None,
181 show_empty_scopes: None,
182 show_hierarchy_icons: None,
183 show_parameters_in_scopes: None,
184 parameter_display_location: None,
185 highlight_focused: None,
186 fill_high_values: None,
187 primary_button_drag_behavior: None,
188 arrow_key_bindings: None,
189 clock_highlight_type: None,
190 hierarchy_style: None,
191 autoload_sibling_state_files: None,
192 autoreload_files: None,
193 waves: None,
194 drag_started: false,
195 drag_source_idx: None,
196 drag_target_idx: None,
197 previous_waves: None,
198 count: None,
199 blacklisted_translators: HashSet::new(),
200 show_about: false,
201 show_keys: false,
202 show_gestures: false,
203 show_quick_start: false,
204 show_license: false,
205 show_performance: false,
206 show_logs: false,
207 show_cursor_window: false,
208 frame_buffer: FrameBufferSettings::default(),
209 wanted_timeunit: TimeUnit::None,
210 time_string_format: None,
211 show_url_entry: false,
212 show_reload_suggestion: None,
213 show_open_sibling_state_file_suggestion: None,
214 variable_name_filter_focused: false,
215 variable_filter: VariableFilter::new(),
216 sidepanel_width: None,
217 ui_zoom_factor: None,
218 state_file: None,
219 animation_enabled: None,
220 use_dinotrace_style: None,
221 selected_server_file_index: None,
222 show_server_file_window: false,
223 surver_file_infos: None,
224 surver_url: None,
225 transition_value: None,
226 show_annotation_list: false,
227 }
228 }
229}
230
231impl SystemState {
232 pub fn with_params(mut self, args: StartupParams) -> Self {
233 self.user.previous_waves = self.user.waves;
234 self.user.waves = None;
235
236 self.batch_messages = VecDeque::new();
238
239 match args.waves {
240 Some(WaveSource::Url(url)) => {
241 self.add_batch_message(Message::LoadWaveformFileFromUrl(url, LoadOptions::KeepAll));
242 }
243 Some(WaveSource::File(file)) => {
244 self.add_batch_message(Message::LoadFile(file, LoadOptions::KeepAll));
245 }
246 Some(WaveSource::Data) => error!("Attempted to load data at startup"),
247 Some(WaveSource::Cxxrtl(url)) => {
248 self.add_batch_message(Message::SetupCxxrtl(url));
249 }
250 Some(WaveSource::DragAndDrop(_)) => {
251 error!("Attempted to load from drag and drop at startup (how?)");
252 }
253 None => {}
254 }
255
256 if let Some(port) = args.wcp_initiate {
257 let addr = format!("127.0.0.1:{port}");
258 self.add_batch_message(Message::StartWcpServer {
259 address: Some(addr),
260 initiate: true,
261 });
262 }
263
264 self.add_batch_commands(args.startup_commands);
265
266 self
267 }
268
269 pub fn wcp(&mut self) {
270 self.handle_wcp_commands();
271 }
272
273 pub(crate) fn get_scope(&mut self, scope: ScopeRef, recursive: bool) -> Vec<VariableRef> {
274 let Some(waves) = self.user.waves.as_mut() else {
275 return vec![];
276 };
277
278 let wave_cont = waves.inner.as_waves().unwrap();
279
280 let children = wave_cont.child_scopes(&scope);
281 let mut variables = wave_cont
282 .variables_in_scope(&scope)
283 .iter()
284 .sorted_by(|a, b| numeric_sort::cmp(&a.name, &b.name))
285 .cloned()
286 .collect_vec();
287
288 if recursive && let Ok(children) = children {
289 for child in children {
290 variables.append(&mut self.get_scope(child, true));
291 }
292 }
293
294 variables
295 }
296
297 pub(crate) fn on_waves_loaded(
298 &mut self,
299 filename: WaveSource,
300 format: WaveFormat,
301 new_waves: Box<WaveContainer>,
302 load_options: LoadOptions,
303 ) {
304 let filename_for_title = filename.clone();
305 info!("{format} file loaded");
306 let viewport = Viewport::new();
307 let viewports = [viewport].to_vec();
308
309 for translator in self.translators.all_translators() {
310 translator.set_wave_source(Some(filename.into_translation_type()));
311 }
312
313 let ((new_wave, load_commands), is_reload) =
314 if load_options != LoadOptions::Clear && self.user.waves.is_some() {
315 (
316 self.user.waves.take().unwrap().update_with_waves(
317 new_waves,
318 filename,
319 format,
320 &self.translators,
321 load_options == LoadOptions::KeepAll,
322 ),
323 true,
324 )
325 } else if let Some(old) = self.user.previous_waves.take() {
326 (
327 old.update_with_waves(
328 new_waves,
329 filename,
330 format,
331 &self.translators,
332 load_options == LoadOptions::KeepAll,
333 ),
334 true,
335 )
336 } else {
337 (
338 (
339 WaveData {
340 inner: DataContainer::Waves(*new_waves),
341 source: filename,
342 format,
343 active_scope: None,
344 items_tree: DisplayedItemTree::default(),
345 displayed_items: HashMap::new(),
346 viewports,
347 cursor: None,
348 markers: HashMap::new(),
349 annotations: Vec::new(),
350 selected_annotation: None,
351 annotation_counter: 0,
352 last_active_viewport_idx: 0,
353 annotation_menu_pos: None,
354 annotation_menu_time: None,
355 focused_item: None,
356 focused_transaction: (None, None),
357 default_variable_name_type: self.user.config.default_variable_name_type,
358 display_variable_indices: self.show_variable_indices(),
359 scroll_offset: 0.,
360 drawing_infos: vec![],
361 top_item_draw_offset: 0.,
362 total_height: 0.,
363 display_item_ref_counter: 0,
364 old_num_timestamps: None,
365 graphics: HashMap::new(),
366 cache_generation: 0,
367 inflight_caches: HashMap::new(),
368 annotation_groups: vec![],
369 annotation_list_visible: false,
370 },
371 None,
372 ),
373 false,
374 )
375 };
376
377 if let Some(cmd) = load_commands {
378 self.load_variables(cmd);
379 }
380 self.invalidate_draw_commands();
381
382 self.user.waves = Some(new_wave);
383 let title = match &filename_for_title {
385 WaveSource::File(path) => {
386 if let Some(name) = path.file_name() {
387 format!("Surfer - {name}")
388 } else {
389 "Surfer".to_string()
390 }
391 }
392 WaveSource::Url(url) => format!("Surfer - {url}"),
393 _ => "Surfer".to_string(),
394 };
395 if let Some(ctx) = self.context.as_ref() {
396 ctx.send_viewport_cmd(egui::ViewportCommand::Title(title));
397 }
398
399 self.record_file_history(&filename_for_title);
400
401 if !is_reload && let Some(waves) = &mut self.user.waves {
402 self.user.wanted_timeunit = waves.inner.metadata().timescale.unit;
404
405 let ungrouped = AnnotationGroup {
406 name: String::from("Ungrouped"),
407 cycle_counter: 0,
408 annotations: Vec::new(),
409 };
410
411 waves.annotation_groups.push(ungrouped);
412
413 if waves.source.sibling_state_file().is_some() {
415 self.update(Message::SuggestOpenSiblingStateFile);
416 }
417 }
418 }
419
420 pub(crate) fn on_transaction_streams_loaded(
421 &mut self,
422 filename: WaveSource,
423 format: WaveFormat,
424 new_ftr: TransactionContainer,
425 _loaded_options: LoadOptions,
426 ) {
427 info!("Transaction streams are loaded.");
428 self.record_file_history(&filename);
429
430 let viewport = Viewport::new();
431 let viewports = [viewport].to_vec();
432
433 let new_transaction_streams = WaveData {
434 inner: DataContainer::Transactions(new_ftr),
435 source: filename,
436 format,
437 active_scope: None,
438 items_tree: DisplayedItemTree::default(),
439 displayed_items: HashMap::new(),
440 viewports,
441 cursor: None,
442 markers: HashMap::new(),
443 annotations: Vec::new(),
444 selected_annotation: None,
445 annotation_counter: 1,
446 last_active_viewport_idx: 0,
447 annotation_menu_pos: None,
448 annotation_menu_time: None,
449 focused_item: None,
450 focused_transaction: (None, None),
451 default_variable_name_type: self.user.config.default_variable_name_type,
452 display_variable_indices: self.show_variable_indices(),
453 scroll_offset: 0.,
454 drawing_infos: vec![],
455 top_item_draw_offset: 0.,
456 total_height: 0.,
457 display_item_ref_counter: 0,
458 old_num_timestamps: None,
459 graphics: HashMap::new(),
460 cache_generation: 0,
461 inflight_caches: HashMap::new(),
462 annotation_groups: vec![],
463 annotation_list_visible: false,
464 };
465
466 self.invalidate_draw_commands();
467
468 self.user.config.theme.alt_frequency = 0;
469 self.user.wanted_timeunit = new_transaction_streams.inner.metadata().timescale.unit;
470 self.user.waves = Some(new_transaction_streams);
471 }
472
473 fn record_file_history(&mut self, source: &WaveSource) {
474 if let Some(path) = source.path() {
475 self.file_history.add(path.clone());
476 }
477 }
478
479 #[cfg(test)]
480 pub(crate) fn handle_async_messages(&mut self) {
481 let mut msgs = vec![];
482 loop {
483 match self.channels.msg_receiver.try_recv() {
484 Ok(msg) => msgs.push(msg),
485 Err(std::sync::mpsc::TryRecvError::Empty) => break,
486 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
487 trace!("Message sender disconnected");
488 break;
489 }
490 }
491 }
492
493 while let Some(msg) = msgs.pop() {
494 self.update(msg);
495 }
496 }
497
498 pub(crate) fn push_async_messages(&mut self, msgs: &mut Vec<Message>) {
499 loop {
500 match self.channels.msg_receiver.try_recv() {
501 Ok(msg) => msgs.push(msg),
502 Err(std::sync::mpsc::TryRecvError::Empty) => break,
503 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
504 trace!("Message sender disconnected");
505 break;
506 }
507 }
508 }
509 }
510
511 pub fn get_visuals(&self) -> Visuals {
512 let widget_style = WidgetVisuals {
513 bg_fill: self.user.config.theme.secondary_ui_color.background,
514 fg_stroke: Stroke {
515 color: self.user.config.theme.secondary_ui_color.foreground,
516 width: 1.0,
517 },
518 weak_bg_fill: self.user.config.theme.secondary_ui_color.background,
519 bg_stroke: Stroke {
520 color: self.user.config.theme.border_color,
521 width: 1.0,
522 },
523 corner_radius: CornerRadius::same(2),
524 expansion: 0.0,
525 };
526
527 Visuals {
528 override_text_color: Some(self.user.config.theme.foreground),
529 extreme_bg_color: self.user.config.theme.secondary_ui_color.background,
530 panel_fill: self.user.config.theme.secondary_ui_color.background,
531 window_fill: self.user.config.theme.primary_ui_color.background,
532 window_stroke: Stroke {
533 width: 1.0,
534 color: self.user.config.theme.border_color,
535 },
536 selection: Selection {
537 bg_fill: self.user.config.theme.selected_elements_colors.background,
538 stroke: Stroke {
539 color: self.user.config.theme.selected_elements_colors.foreground,
540 width: 1.0,
541 },
542 },
543 widgets: Widgets {
544 noninteractive: widget_style,
545 inactive: widget_style,
546 hovered: widget_style,
547 active: widget_style,
548 open: widget_style,
549 },
550 ..Visuals::dark()
551 }
552 }
553
554 pub(crate) fn load_state(&mut self, mut loaded_state: Box<UserState>, path: Option<PathBuf>) {
555 mem::swap(&mut self.user, &mut loaded_state);
557
558 mem::swap(&mut loaded_state.waves, &mut self.user.waves);
561 let load_commands = if let (Some(waves), Some(new_waves)) =
562 (&mut self.user.waves, &mut loaded_state.waves)
563 {
564 mem::swap(&mut waves.active_scope, &mut new_waves.active_scope);
565 let items = std::mem::take(&mut new_waves.displayed_items);
566 let items_tree = std::mem::take(&mut new_waves.items_tree);
567 let load_commands = waves.update_with_items(&items, items_tree, &self.translators);
568
569 mem::swap(&mut waves.viewports, &mut new_waves.viewports);
570 mem::swap(&mut waves.cursor, &mut new_waves.cursor);
571 mem::swap(&mut waves.markers, &mut new_waves.markers);
572 mem::swap(&mut waves.focused_item, &mut new_waves.focused_item);
573
574 mem::swap(&mut waves.annotations, &mut new_waves.annotations);
575 mem::swap(
577 &mut waves.annotation_groups,
578 &mut new_waves.annotation_groups,
579 );
580 mem::swap(
581 &mut waves.annotation_list_visible,
582 &mut new_waves.annotation_list_visible,
583 );
584 mem::swap(
585 &mut waves.annotation_counter,
586 &mut new_waves.annotation_counter,
587 );
588 mem::swap(
589 &mut waves.selected_annotation,
590 &mut new_waves.selected_annotation,
591 );
592 waves.default_variable_name_type = new_waves.default_variable_name_type;
593 waves.scroll_offset = new_waves.scroll_offset;
594 load_commands
595 } else {
596 None
597 };
598 if let Some(load_commands) = load_commands {
599 self.load_variables(load_commands);
600 }
601
602 self.user.drag_started = false;
604 self.user.drag_source_idx = None;
605 self.user.drag_target_idx = None;
606
607 self.user.previous_waves = None;
609 self.user.count = None;
610
611 self.user.state_file = path;
613
614 self.invalidate_draw_commands();
615 if let Some(waves) = &mut self.user.waves {
616 waves.update_viewports();
617 }
618 }
619
620 pub fn waves_fully_loaded(&self) -> bool {
624 self.user
625 .waves
626 .as_ref()
627 .is_some_and(|w| w.inner.is_fully_loaded())
628 }
629
630 pub fn analog_caches_ready(&self) -> bool {
632 self.user
633 .waves
634 .as_ref()
635 .is_none_or(|w| w.inflight_caches.is_empty())
636 }
637
638 pub(crate) fn current_canvas_state(waves: &WaveData, message: String) -> CanvasState {
640 CanvasState {
641 message,
642 focused_item: waves.focused_item,
643 focused_transaction: waves.focused_transaction.clone(),
644 items_tree: waves.items_tree.clone(),
645 displayed_items: waves.displayed_items.clone(),
646 markers: waves.markers.clone(),
647 annotations: waves.annotations.clone(),
648 annotation_group: waves.annotation_groups.clone(),
649 annotation_list: waves.annotation_list_visible,
650 selected_annotation: waves.selected_annotation,
651 annotation_counter: waves.annotation_counter,
652 }
653 }
654
655 pub(crate) fn save_current_canvas(&mut self, message: String) {
657 if let Some(waves) = &self.user.waves {
658 self.undo_stack
659 .push(SystemState::current_canvas_state(waves, message));
660
661 if self.undo_stack.len() > self.user.config.undo_stack_size {
662 self.undo_stack.remove(0);
663 }
664 self.redo_stack.clear();
665 }
666 }
667
668 #[cfg(not(target_arch = "wasm32"))]
669 pub(crate) fn start_wcp_server(&mut self, address: Option<String>, initiate: bool) {
670 use wcp::wcp_server::WcpServer;
671
672 use crate::wcp;
673
674 if self.wcp_server_thread.as_ref().is_some()
675 || self
676 .wcp_running_signal
677 .load(std::sync::atomic::Ordering::Relaxed)
678 {
679 warn!("WCP HTTP server is already running");
680 return;
681 }
682 let (wcp_s2c_sender, wcp_s2c_receiver) = tokio::sync::mpsc::channel(100);
684 let (wcp_c2s_sender, wcp_c2s_receiver) = tokio::sync::mpsc::channel(100);
685
686 self.channels.wcp_c2s_receiver = Some(wcp_c2s_receiver);
687 self.channels.wcp_s2c_sender = Some(wcp_s2c_sender);
688 let stop_signal_copy = self.wcp_stop_signal.clone();
689 stop_signal_copy.store(false, std::sync::atomic::Ordering::Relaxed);
690 let running_signal_copy = self.wcp_running_signal.clone();
691 running_signal_copy.store(true, std::sync::atomic::Ordering::Relaxed);
692 let greeted_signal_copy = self.wcp_greeted_signal.clone();
693 greeted_signal_copy.store(true, std::sync::atomic::Ordering::Relaxed);
694
695 let ctx = self.context.clone();
696 let address = address.unwrap_or(self.user.config.wcp.address.clone());
697 self.wcp_server_address = Some(address.clone());
698 self.wcp_server_thread = Some(tokio::spawn(async move {
699 let server = WcpServer::new(
700 address,
701 initiate,
702 wcp_c2s_sender,
703 wcp_s2c_receiver,
704 stop_signal_copy,
705 running_signal_copy,
706 greeted_signal_copy,
707 ctx,
708 )
709 .await;
710 match server {
711 Ok(mut server) => server.run().await,
712 Err(m) => {
713 error!("Could not start WCP server. {m:?}");
714 }
715 }
716 }));
717 }
718
719 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
720 pub(crate) fn stop_wcp_server(&mut self) {
721 if self.wcp_server_address.is_some() && self.wcp_server_thread.is_some() {
724 self.wcp_stop_signal
726 .store(true, std::sync::atomic::Ordering::Relaxed);
727
728 self.wcp_server_thread = None;
729 self.wcp_server_address = None;
730 self.channels.wcp_s2c_sender = None;
731 self.channels.wcp_c2s_receiver = None;
732 info!("Stopped WCP server");
733 }
734 }
735}