libsurfer/wcp/
wcp_handler.rs

1use crate::{
2    displayed_item::{DisplayedItem, DisplayedItemRef},
3    message::{Message, MessageTarget},
4    wave_container::{ScopeRefExt, VariableRef, VariableRefExt},
5    wave_data::WaveData,
6    wave_source::{string_to_wavesource, LoadOptions, WaveSource},
7    SystemState, WcpClientCapabilities,
8};
9
10use futures::executor::block_on;
11use itertools::Itertools;
12use log::{trace, warn};
13use std::sync::atomic::Ordering;
14use surfer_translation_types::ScopeRef;
15
16use super::proto::{ItemInfo, WcpCSMessage, WcpCommand, WcpResponse, WcpSCMessage};
17
18impl SystemState {
19    pub fn handle_wcp_commands(&mut self) {
20        let Some(receiver) = &mut self.channels.wcp_c2s_receiver else {
21            return;
22        };
23
24        let mut messages = vec![];
25        loop {
26            match receiver.try_recv() {
27                Ok(command) => {
28                    messages.push(command);
29                }
30                Err(tokio::sync::mpsc::error::TryRecvError::Empty) => break,
31                Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {
32                    trace!("WCP Command sender disconnected");
33                    break;
34                }
35            }
36        }
37        for message in messages {
38            self.handle_wcp_cs_message(&message);
39        }
40    }
41
42    fn handle_wcp_cs_message(&mut self, message: &WcpCSMessage) {
43        if !self.wcp_greeted_signal.load(Ordering::Relaxed) {
44            match message {
45                WcpCSMessage::greeting { .. } => (),
46                _ => {
47                    self.send_error("WCP server has not received greeting messages", vec![], "");
48                    return;
49                }
50            }
51        }
52        match message {
53            WcpCSMessage::command(command) => {
54                match command {
55                    WcpCommand::get_item_list => {
56                        if let Some(waves) = &self.user.waves {
57                            let ids: Vec<crate::wcp::proto::DisplayedItemRef> = self
58                                .get_displayed_items(waves)
59                                .iter()
60                                .map(|r| r.into())
61                                .collect_vec();
62                            self.send_response(WcpResponse::get_item_list { ids });
63                        } else {
64                            self.send_error("No waveform loaded", vec![], "No waveform loaded");
65                        }
66                    }
67                    WcpCommand::get_item_info { ids } => {
68                        let Some(waves) = &self.user.waves else {
69                            self.send_error("remove_items", vec![], "No waveform loaded");
70                            return;
71                        };
72                        let mut items: Vec<ItemInfo> = Vec::new();
73                        for id in ids {
74                            if let Some(item) = waves.displayed_items.get(&id.into()) {
75                                let (name, item_type) = match item {
76                                    DisplayedItem::Variable(var) => (
77                                        var.manual_name.clone().unwrap_or(var.display_name.clone()),
78                                        "Variable".to_string(),
79                                    ),
80                                    DisplayedItem::Divider(item) => (
81                                        item.name.clone().unwrap_or("Name not found!".to_string()),
82                                        "Divider".to_string(),
83                                    ),
84                                    DisplayedItem::Marker(item) => (
85                                        item.name.clone().unwrap_or("Name not found!".to_string()),
86                                        "Marker".to_string(),
87                                    ),
88                                    DisplayedItem::TimeLine(item) => (
89                                        item.name.clone().unwrap_or("Name not found!".to_string()),
90                                        "TimeLine".to_string(),
91                                    ),
92                                    DisplayedItem::Placeholder(item) => (
93                                        item.manual_name
94                                            .clone()
95                                            .unwrap_or("Name not found!".to_string()),
96                                        "Placeholder".to_string(),
97                                    ),
98                                    DisplayedItem::Stream(item) => (
99                                        item.manual_name
100                                            .clone()
101                                            .unwrap_or(item.display_name.clone()),
102                                        "Stream".to_string(),
103                                    ),
104                                    DisplayedItem::Group(item) => {
105                                        (item.name.clone(), "Group".to_string())
106                                    }
107                                };
108                                items.push(ItemInfo {
109                                    name,
110                                    t: item_type,
111                                    id: *id,
112                                });
113                            } else {
114                                self.send_error(
115                                    "get_item_info",
116                                    vec![],
117                                    &format!("No item with id {:?}", id),
118                                );
119                                return;
120                            }
121                        }
122                        self.send_response(WcpResponse::get_item_info { results: items });
123                    }
124                    WcpCommand::add_variables { variables } => {
125                        if self.user.waves.is_some() {
126                            self.save_current_canvas(format!("Add {} variables", variables.len()));
127                        }
128                        if let Some(waves) = self.user.waves.as_mut() {
129                            let variable_refs = variables
130                                .iter()
131                                .map(|n| VariableRef::from_hierarchy_string(n))
132                                .collect_vec();
133                            let (cmd, ids) =
134                                waves.add_variables(&self.translators, variable_refs, None, true);
135                            if let Some(cmd) = cmd {
136                                self.load_variables(cmd);
137                            }
138                            self.send_response(WcpResponse::add_variables {
139                                ids: ids.into_iter().map(|id| id.into()).collect_vec(),
140                            });
141                            self.invalidate_draw_commands();
142                        } else {
143                            self.send_error(
144                                "add_variables",
145                                vec![],
146                                "Can't add signals. No waveform loaded",
147                            )
148                        }
149                    }
150                    WcpCommand::add_scope { scope, recursive } => {
151                        if self.user.waves.is_some() {
152                            self.save_current_canvas(format!("Add scope {}", scope));
153                        }
154                        let scope = ScopeRef::from_hierarchy_string(scope);
155                        let variables = self.get_scope(scope, *recursive);
156                        if let Some(waves) = self.user.waves.as_mut() {
157                            let (cmd, ids) =
158                                waves.add_variables(&self.translators, variables, None, true);
159                            if let Some(cmd) = cmd {
160                                self.load_variables(cmd);
161                            }
162                            self.send_response(WcpResponse::add_scope {
163                                ids: ids.into_iter().map(|id| id.into()).collect_vec(),
164                            });
165                            self.invalidate_draw_commands();
166                        } else {
167                            self.send_error("scope_add", vec![], "No waveform loaded");
168                        }
169                    }
170                    WcpCommand::reload => {
171                        self.update(Message::ReloadWaveform(false));
172                        self.send_response(WcpResponse::ack);
173                    }
174                    WcpCommand::set_viewport_to { timestamp } => {
175                        self.update(Message::GoToTime(Some(timestamp.clone()), 0));
176                        self.send_response(WcpResponse::ack);
177                    }
178                    WcpCommand::set_item_color { id, color } => {
179                        let Some(waves) = &self.user.waves else {
180                            self.send_error("set_item_color", vec![], "No waveform loaded");
181                            return;
182                        };
183
184                        if let Some(idx) = waves.get_displayed_item_index(&id.into()) {
185                            self.update(Message::ItemColorChange(
186                                MessageTarget::Explicit(idx),
187                                Some(color.clone()),
188                            ));
189                            self.send_response(WcpResponse::ack);
190                        } else {
191                            self.send_error(
192                                "set_item_color",
193                                vec![],
194                                format!("Item {id:?} not found").as_str(),
195                            );
196                        }
197                    }
198                    WcpCommand::remove_items { ids } => {
199                        let Some(_) = self.user.waves.as_mut() else {
200                            self.send_error("remove_items", vec![], "No waveform loaded");
201                            return;
202                        };
203                        let msgs =
204                            vec![Message::RemoveItems(ids.iter().map(|d| d.into()).collect())];
205                        self.update(Message::Batch(msgs));
206
207                        self.send_response(WcpResponse::ack);
208                    }
209                    WcpCommand::focus_item { id } => {
210                        let Some(waves) = &self.user.waves else {
211                            self.send_error("remove_items", vec![], "No waveform loaded");
212                            return;
213                        };
214                        // TODO: Create a `.into` function here instead of unwrapping and wrapping
215                        // it to prevent future type errors
216                        if let Some(vidx) = waves.get_displayed_item_index(&id.into()) {
217                            self.update(Message::FocusItem(vidx));
218
219                            self.send_response(WcpResponse::ack);
220                        } else {
221                            self.send_error(
222                                "focus_item",
223                                vec![],
224                                format!("No item with ID {id:?}").as_str(),
225                            );
226                        }
227                    }
228                    WcpCommand::clear => {
229                        if let Some(wave) = &self.user.waves {
230                            self.update(Message::RemoveItems(self.get_displayed_items(wave)));
231                        }
232
233                        self.send_response(WcpResponse::ack);
234                    }
235                    WcpCommand::load { source } => {
236                        match string_to_wavesource(source) {
237                            WaveSource::Url(url) => {
238                                self.update(Message::LoadWaveformFileFromUrl(
239                                    url,
240                                    LoadOptions::clean(),
241                                ));
242                                self.send_response(WcpResponse::ack)
243                            }
244                            WaveSource::File(file) => {
245                                // FIXME add support for loading transaction files via Message::LoadTransactionFile
246                                let msg = Message::LoadFile(file, LoadOptions::clean());
247                                self.update(msg);
248                                self.send_response(WcpResponse::ack)
249                            }
250                            _ => {
251                                self.send_error(
252                                    "load",
253                                    vec![],
254                                    format!("{source} is not legal wave source").as_str(),
255                                );
256                            }
257                        }
258                    }
259                    WcpCommand::zoom_to_fit { viewport_idx } => {
260                        self.update(Message::ZoomToFit {
261                            viewport_idx: *viewport_idx,
262                        });
263                        self.send_response(WcpResponse::ack);
264                    }
265                    WcpCommand::shutdowmn => {
266                        warn!("WCP Shutdown message should not reach this place")
267                    }
268                };
269            }
270            WcpCSMessage::greeting { version, commands } => {
271                if version != "0" {
272                    self.send_error(
273                        "greeting",
274                        vec![],
275                        &format!(
276                            "Surfer only supports WCP version 0, client requested {}",
277                            version
278                        ),
279                    )
280                } else {
281                    self.wcp_client_capabilities = WcpClientCapabilities::new();
282                    if commands.iter().any(|s| s == "waveforms_loaded") {
283                        self.wcp_client_capabilities.waveforms_loaded = true;
284                    }
285                    if commands.iter().any(|s| s == "goto_declaration") {
286                        self.wcp_client_capabilities.goto_declaration = true;
287                    }
288                    if commands.iter().any(|s| s == "add_drivers") {
289                        self.wcp_client_capabilities.add_drivers = true;
290                    }
291                    if commands.iter().any(|s| s == "add_loads") {
292                        self.wcp_client_capabilities.add_loads = true;
293                    }
294                    self.wcp_greeted_signal.store(true, Ordering::Relaxed);
295                    self.wcp_greeted_signal.store(true, Ordering::Relaxed);
296                    self.send_greeting()
297                }
298            }
299        }
300    }
301
302    fn send_greeting(&self) {
303        let commands = vec![
304            "add_variables",
305            "set_viewport_to",
306            "cursor_set",
307            "reload",
308            "add_scope",
309            "get_item_list",
310            "set_item_color",
311            "get_item_info",
312            "clear_item",
313            "focus_item",
314            "clear",
315            "load",
316            "zoom_to_fit",
317        ]
318        .into_iter()
319        .map(str::to_string)
320        .collect_vec();
321
322        let greeting = WcpSCMessage::create_greeting(0, commands);
323
324        self.channels
325            .wcp_s2c_sender
326            .as_ref()
327            .map(|ch| block_on(ch.send(greeting)));
328    }
329
330    fn send_response(&self, result: WcpResponse) {
331        self.channels
332            .wcp_s2c_sender
333            .as_ref()
334            .map(|ch| block_on(ch.send(WcpSCMessage::response(result))));
335    }
336
337    fn send_error(&self, error: &str, arguments: Vec<String>, message: &str) {
338        self.channels.wcp_s2c_sender.as_ref().map(|ch| {
339            block_on(ch.send(WcpSCMessage::create_error(
340                error.to_string(),
341                arguments,
342                message.to_string(),
343            )))
344        });
345    }
346
347    fn get_displayed_items(&self, waves: &WaveData) -> Vec<DisplayedItemRef> {
348        // TODO check call sites since visible items may now differ from loaded items
349        waves
350            .items_tree
351            .iter_visible()
352            .map(|node| node.item_ref)
353            .collect_vec()
354    }
355}