libsurfer/cxxrtl/
query_container.rs

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