Skip to main content

libsurfer/cxxrtl/
query_container.rs

1use std::{
2    collections::{BTreeMap, HashMap},
3    sync::Arc,
4};
5
6use base64::{Engine as _, prelude::BASE64_STANDARD};
7use futures::executor::block_on;
8use num::{BigInt, BigUint, bigint::ToBigInt as _};
9use rayon::iter::{IntoParallelRefIterator as _, ParallelIterator as _};
10use surfer_translation_types::VariableValue;
11use tokio::sync::RwLock;
12
13use crate::{
14    EGUI_CONTEXT,
15    cxxrtl_container::CxxrtlItem,
16    message::Message,
17    wave_container::{QueryResult, VariableRef},
18};
19
20use super::sc_message::CxxrtlSample;
21
22type ValueList = Arc<RwLock<BTreeMap<BigInt, HashMap<VariableRef, VariableValue>>>>;
23
24pub struct QueryContainer {
25    variable_values: ValueList,
26}
27
28impl QueryContainer {
29    #[must_use]
30    pub fn empty() -> Self {
31        QueryContainer {
32            variable_values: Arc::new(RwLock::new(BTreeMap::new())),
33        }
34    }
35
36    pub fn populate(
37        &mut self,
38        variables: Vec<VariableRef>,
39        item_info: Arc<HashMap<VariableRef, CxxrtlItem>>,
40        data: Vec<CxxrtlSample>,
41        msg_sender: std::sync::mpsc::Sender<Message>,
42    ) {
43        let variable_values = self.variable_values.clone();
44
45        let task = fill_variable_values(variables, item_info, data, variable_values, msg_sender);
46        #[cfg(target_arch = "wasm32")]
47        wasm_bindgen_futures::spawn_local(task);
48        #[cfg(not(target_arch = "wasm32"))]
49        tokio::task::spawn(task);
50    }
51
52    #[must_use]
53    pub fn query(&self, var: &VariableRef, query_time: BigInt) -> QueryResult {
54        let values = block_on(self.variable_values.read());
55
56        if let Some((time, value_map)) = values.range(..query_time.clone()).next_back() {
57            match (time.to_biguint(), value_map.get(var)) {
58                (Some(time), Some(value)) => {
59                    let next = values
60                        .range(query_time..)
61                        .next()
62                        .and_then(|(k, _)| k.to_biguint());
63                    QueryResult {
64                        current: Some((time.clone(), value.clone())),
65                        next,
66                    }
67                }
68                _ => QueryResult::default(),
69            }
70        } else {
71            QueryResult::default()
72        }
73    }
74}
75
76async fn fill_variable_values(
77    variables: Vec<VariableRef>,
78    item_info: Arc<HashMap<VariableRef, CxxrtlItem>>,
79    data: Vec<CxxrtlSample>,
80    variable_values: ValueList,
81    msg_sender: std::sync::mpsc::Sender<Message>,
82) {
83    let work = move || {
84        // Once we base64 decode the cxxrtl data, we'll end up with a bunch of u32s, where
85        // the variables are packed next to each other. We'll start off computing the offset
86        // of each variable for later use
87        let mut offset = 0;
88        let mut ranges = vec![];
89        for variable in &variables {
90            let this_size_bits = &item_info[variable].width;
91            let this_size_u32 = 1 + ((this_size_bits - 1) / 32);
92            ranges.push((offset * 4) as usize..((offset + this_size_u32) * 4) as usize);
93            offset += this_size_u32;
94        }
95
96        data.par_iter().for_each(|sample| {
97            let u8s = BASE64_STANDARD
98                .decode(&sample.item_values)
99                .map_err(|e| {
100                    panic!(
101                        "Got non-base64 data from cxxrtl at time {}. {e}",
102                        sample.time
103                    )
104                })
105                .unwrap();
106
107            let values = ranges
108                .iter()
109                .zip(&variables)
110                .map(|(range, var)| {
111                    let value = BigUint::from_bytes_le(&u8s[range.clone()]);
112
113                    // FIXME: Probably shouldn't have this indexed by the variable ref here so we can
114                    // avoid the clone
115                    (var.clone(), VariableValue::BigUint(value))
116                })
117                .collect::<HashMap<_, _>>();
118
119            block_on(variable_values.write())
120                .insert(sample.time.as_femtoseconds().to_bigint().unwrap(), values);
121            msg_sender
122                .send(Message::InvalidateDrawCommands)
123                .expect("Message receiver disconnected");
124        });
125
126        if let Some(ctx) = EGUI_CONTEXT.read().unwrap().as_ref() {
127            ctx.request_repaint();
128        }
129    };
130    // Since this is a purely CPU bound operation, we'll spawn a blocking task to
131    // perform it. We can't do this on wasm though, so there we'll just run it normally
132    // for now
133    #[cfg(target_arch = "wasm32")]
134    work();
135    #[cfg(not(target_arch = "wasm32"))]
136    tokio::task::spawn_blocking(work);
137}