Skip to main content

mimium_guitools/
lib.rs

1use std::sync::{Arc, Mutex};
2
3use egui::ahash::HashMap;
4use mimium_lang::{
5    ast::{Expr, Literal},
6    code, function,
7    interner::{ToSymbol, TypeNodeId},
8    interpreter::Value,
9    log, numeric,
10    pattern::TypedId,
11    plugin::{SysPluginSignature, SystemPlugin, SystemPluginFnType, SystemPluginMacroType},
12    runtime::vm::{Machine, ReturnCode},
13    string_t,
14    types::{PType, Type},
15};
16use plot_window::PlotApp;
17use ringbuf::{
18    HeapProd, HeapRb,
19    traits::{Producer, Split},
20};
21
22use crate::plot_window::FloatParameter;
23pub(crate) mod plot_ui;
24pub mod plot_window;
25
26pub struct GuiToolPlugin {
27    window: Arc<Mutex<PlotApp>>,
28    slider_instances: Vec<Arc<FloatParameter>>,
29    slider_namemap: HashMap<String, usize>,
30
31    probe_instances: Vec<HeapProd<f64>>,
32    probe_namemap: HashMap<String, usize>,
33}
34
35impl Default for GuiToolPlugin {
36    fn default() -> Self {
37        Self {
38            window: Arc::new(Mutex::new(PlotApp::default())),
39            slider_instances: Vec::new(),
40            slider_namemap: HashMap::default(),
41            probe_instances: Vec::new(),
42            probe_namemap: HashMap::default(),
43        }
44    }
45}
46
47impl GuiToolPlugin {
48    fn get_closure_type() -> TypeNodeId {
49        function!(vec![numeric!()], numeric!())
50    }
51    const GET_SLIDER: &'static str = "__get_slider";
52    const PROBE_INTERCEPT: &'static str = "__probe_intercept";
53
54    pub fn make_slider(&mut self, v: &[(Value, TypeNodeId)]) -> Value {
55        assert_eq!(v.len(), 4);
56        let (name, init, min, max, mut window) = match (
57            v[0].0.clone(),
58            v[1].0.clone(),
59            v[2].0.clone(),
60            v[3].0.clone(),
61            self.window.lock(),
62        ) {
63            (
64                Value::String(name),
65                Value::Number(init),
66                Value::Number(min),
67                Value::Number(max),
68                Ok(window),
69            ) => (name, init, min, max, window),
70            _ => {
71                log::error!("invalid argument");
72                return Value::Number(0.0);
73            }
74        };
75        let idx = if let Some(idx) = self.slider_namemap.get(name.as_str()).cloned() {
76            let p = self.slider_instances.get_mut(idx).unwrap();
77            p.set_range(min, max);
78            idx
79        } else {
80            let (p, idx) = window.add_slider(name.as_str(), init, min, max);
81            self.slider_instances.push(p);
82            self.slider_namemap.insert(name.to_string(), idx);
83            idx
84        };
85        Value::Code(
86            Expr::Apply(
87                Expr::Var(Self::GET_SLIDER.to_symbol()).into_id_without_span(),
88                vec![
89                    Expr::Literal(Literal::Float(idx.to_string().to_symbol()))
90                        .into_id_without_span(),
91                ],
92            )
93            .into_id_without_span(),
94        )
95    }
96
97    pub fn make_probe_macro(&mut self, v: &[(Value, TypeNodeId)]) -> Value {
98        assert_eq!(v.len(), 1);
99        let (name, mut window) = match (v[0].0.clone(), self.window.lock()) {
100            (Value::String(name), Ok(window)) => (name, window),
101            _ => {
102                log::error!("invalid argument for Probe macro type {}", v[0].1);
103                return Value::Code(
104                    Expr::Lambda(
105                        vec![TypedId::new(
106                            "x".to_symbol(),
107                            Type::Primitive(PType::Numeric).into_id(),
108                        )],
109                        None,
110                        Expr::Var("x".to_symbol()).into_id_without_span(),
111                    )
112                    .into_id_without_span(),
113                );
114            }
115        };
116        let probeid = self
117            .probe_namemap
118            .get(name.as_str())
119            .cloned()
120            .unwrap_or_else(|| {
121                let (prod, cons) = HeapRb::<f64>::new(4096).split();
122                window.add_plot(name.as_str(), cons);
123                let idx = self.probe_instances.len();
124                self.probe_instances.push(prod);
125                self.probe_namemap.insert(name.to_string(), idx);
126                idx
127            });
128
129        // Generate a lambda that calls probe_intercept with the fixed ID
130        Value::Code(
131            Expr::Lambda(
132                vec![TypedId::new(
133                    "x".to_symbol(),
134                    Type::Primitive(PType::Numeric).into_id(),
135                )],
136                None,
137                Expr::Apply(
138                    Expr::Var(Self::PROBE_INTERCEPT.to_symbol()).into_id_without_span(),
139                    vec![
140                        Expr::Var("x".to_symbol()).into_id_without_span(),
141                        Expr::Literal(Literal::Float(probeid.to_string().to_symbol()))
142                            .into_id_without_span(),
143                    ],
144                )
145                .into_id_without_span(),
146            )
147            .into_id_without_span(),
148        )
149    }
150    pub fn get_slider(&mut self, vm: &mut Machine) -> ReturnCode {
151        let slider_idx = Machine::get_as::<f64>(vm.get_stack(0)) as usize;
152
153        match self.slider_instances.get(slider_idx) {
154            Some(s) => {
155                vm.set_stack(0, Machine::to_value(s.get()));
156            }
157            None => {
158                log::error!("invalid slider index");
159                return 0;
160            }
161        };
162
163        1
164    }
165
166    pub fn probe_intercept(&mut self, vm: &mut Machine) -> ReturnCode {
167        let value = Machine::get_as::<f64>(vm.get_stack(0));
168        let probe_idx = Machine::get_as::<f64>(vm.get_stack(1)) as usize;
169
170        match self.probe_instances.get_mut(probe_idx) {
171            Some(prod) => {
172                let _ = prod.try_push(value);
173                // Do not modify any stack because we are returning the head of argument as is
174            }
175            None => {
176                log::error!("invalid probe index: {probe_idx}");
177            }
178        }
179
180        1
181    }
182}
183impl SystemPlugin for GuiToolPlugin {
184    fn try_get_main_loop(&mut self) -> Option<Box<dyn FnOnce()>> {
185        #[cfg(not(target_arch = "wasm32"))]
186        {
187            use crate::plot_window::AsyncPlotApp;
188            let app = Box::new(AsyncPlotApp {
189                window: self.window.clone(),
190            });
191            Some(Box::new(move || {
192                let native_options = eframe::NativeOptions {
193                    viewport: egui::ViewportBuilder::default()
194                        .with_inner_size([400.0, 300.0])
195                        .with_min_inner_size([300.0, 220.0])
196                        .with_icon(
197                            // NOTE: Adding an icon is optional
198                            eframe::icon_data::from_png_bytes(
199                                &include_bytes!("../assets/mimium_logo_256.png")[..],
200                            )
201                            .expect("Failed to load icon"),
202                        ),
203                    ..Default::default()
204                };
205                let _ =
206                    eframe::run_native("mimium guitools", native_options, Box::new(|_cc| Ok(app)))
207                        .inspect_err(|e| log::error!("{e}"));
208            }))
209        }
210        #[cfg(target_arch = "wasm32")]
211        None
212    }
213    fn gen_interfaces(&self) -> Vec<SysPluginSignature> {
214        let sliderf: SystemPluginMacroType<Self> = Self::make_slider;
215        let make_slider = SysPluginSignature::new_macro(
216            "Slider",
217            sliderf,
218            function!(
219                vec![string_t!(), numeric!(), numeric!(), numeric!()],
220                code!(numeric!())
221            ),
222        );
223
224        let getsliderf: SystemPluginFnType<Self> = Self::get_slider;
225        let get_slider = SysPluginSignature::new(
226            Self::GET_SLIDER,
227            getsliderf,
228            function!(vec![numeric!()], numeric!()),
229        );
230
231        // Replace make_probe function with Probe macro
232        let probe_macrof: SystemPluginMacroType<Self> = Self::make_probe_macro;
233        let probe_macro = SysPluginSignature::new_macro(
234            "Probe",
235            probe_macrof,
236            function!(
237                vec![string_t!()],
238                Type::Code(function!(vec![numeric!()], numeric!())).into_id()
239            ),
240        );
241        let probe_interceptf: SystemPluginFnType<Self> = Self::probe_intercept;
242        let probe_intercept = SysPluginSignature::new(
243            Self::PROBE_INTERCEPT,
244            probe_interceptf,
245            function!(vec![numeric!(), numeric!()], numeric!()),
246        );
247
248        vec![probe_macro, make_slider, get_slider, probe_intercept]
249    }
250}