Skip to main content

libsurfer/
state.rs

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/// The parts of the program state that need to be serialized when loading/saving state
40#[derive(Serialize, Deserialize)]
41#[serde(default)]
42pub struct UserState {
43    #[serde(skip)]
44    pub config: SurferConfig,
45
46    /// Overrides for the config show_* fields. Defaults to `config.show_*` if not present
47    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    /// Count argument for movements
89    pub(crate) count: Option<String>,
90
91    // Vector of translators which have failed at the `translates` function for a variable.
92    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    /// Show a confirmation dialog asking the user for confirmation
108    /// that surfer should reload changed files from disk.
109    #[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    //Sidepanel width
116    pub(crate) sidepanel_width: Option<f32>,
117    /// UI zoom factor if set by the user
118    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    // Path of last saved-to state file
135    // Do not serialize as this causes a few issues and doesn't help:
136    // - We need to set it on load of a state anyways since the file could have been renamed
137    // - Bad interoperatility story between native and wasm builds
138    // - Sequencing issue in serialization, due to us having to run that async
139    #[serde(skip)]
140    pub state_file: Option<PathBuf>,
141
142    pub(crate) show_annotation_list: bool,
143}
144
145// Impl needed since for loading we need to put State into a Message
146// Snip out the actual contents to not completely spam the terminal
147impl 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        // we turn the waveform argument and any startup command file into batch commands
237        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        // Update window title with waveform name
384        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            // Set time unit
403            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            // Possibly open state file load dialog
414            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        // first swap everything, fix special cases afterwards
556        mem::swap(&mut self.user, &mut loaded_state);
557
558        // swap back waves for inner, source, format since we want to keep the file
559        // fix up all wave references from paths if a wave is loaded
560        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            //load annotations
576            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        // reset drag to avoid confusion
603        self.user.drag_started = false;
604        self.user.drag_source_idx = None;
605        self.user.drag_target_idx = None;
606
607        // reset previous_waves & count to prevent unintuitive state here
608        self.user.previous_waves = None;
609        self.user.count = None;
610
611        // use just loaded path since path is not part of the export as it might have changed anyways
612        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    /// Returns true if the waveform and all requested signals have been loaded.
621    /// Used for testing to make sure the GUI is at its final state before taking a
622    /// snapshot.
623    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    /// Returns true if no analog caches are currently being built
631    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    /// Returns the current canvas state
639    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    /// Push the current canvas state to the undo stack
656    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        // TODO: Consider an unbounded channel?
683        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        // stop wcp server if there is one running
722
723        if self.wcp_server_address.is_some() && self.wcp_server_thread.is_some() {
724            // signal the server to stop
725            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}