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