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