mimium_lang/plugin/
system_plugin.rs

1//! Defines the plugin interface used by mimium's runtime.
2//!
3//! A system plugin can hook into the VM execution by providing callback
4//! functions.  Each plugin exposes its callbacks through [`SysPluginSignature`]
5//! values that are registered as external closures.
6
7use super::ExtClsInfo;
8use crate::{
9    compiler::EvalStage,
10    interner::{ToSymbol, TypeNodeId},
11    interpreter::Value,
12    plugin::MacroInfo,
13    runtime::{
14        Time,
15        vm::{Machine, ReturnCode},
16    },
17};
18use std::{
19    any::Any,
20    cell::{RefCell, UnsafeCell},
21    rc::Rc,
22    sync::Arc,
23};
24pub type SystemPluginFnType<T> = fn(&mut T, &mut Machine) -> ReturnCode;
25pub type SystemPluginMacroType<T> = fn(&mut T, &[(Value, TypeNodeId)]) -> Value;
26
27/// Metadata for a callback provided by a [`SystemPlugin`].
28///
29/// Each signature stores the callback name, erased function pointer and the
30/// type of the closure expected by the VM.
31pub struct SysPluginSignature {
32    name: &'static str,
33    /// The function internally implements `Fn(&mut T:SystemPlugin,&mut Machine)->ReturnCode`
34    /// but the type is erased for dynamic dispatching. later the function is downcasted into their own type.
35    fun: Rc<dyn Any>,
36    ty: TypeNodeId,
37    /// The stage at which the function is available.
38    /// This is used to determine whether the function can be called in a macro or
39    /// in the VM. Note that any persistent functions are not allowed to be used in `SystemPlugin`.
40    stage: EvalStage,
41}
42impl SysPluginSignature {
43    pub fn new<F, T>(name: &'static str, fun: F, ty: TypeNodeId) -> Self
44    where
45        F: Fn(&mut T, &mut Machine) -> ReturnCode + 'static,
46        T: SystemPlugin,
47    {
48        Self {
49            name,
50            fun: Rc::new(fun),
51            ty,
52            stage: EvalStage::Stage(1),
53        }
54    }
55    pub fn new_macro<F, T>(name: &'static str, fun: F, ty: TypeNodeId) -> Self
56    where
57        F: Fn(&mut T, &[(Value, TypeNodeId)]) -> Value + 'static,
58        T: SystemPlugin,
59    {
60        Self {
61            name,
62            fun: Rc::new(fun),
63            ty,
64            stage: EvalStage::Stage(0),
65        }
66    }
67}
68
69/// Trait implemented by runtime plugins.
70///
71/// The default implementations of the callback methods do nothing. Plugins can
72/// override these to perform setup in [`on_init`], teardown in [`after_main`],
73/// or per-sample processing in [`on_sample`].
74pub trait SystemPlugin {
75    fn generate_audioworker(&mut self) -> Option<Box<dyn SystemPluginAudioWorker>> {
76        None
77    }
78    fn on_init(&mut self, _machine: &mut Machine) -> ReturnCode {
79        0
80    }
81    fn after_main(&mut self, _machine: &mut Machine) -> ReturnCode {
82        0
83    }
84    fn gen_interfaces(&self) -> Vec<SysPluginSignature>;
85    fn try_get_main_loop(&mut self) -> Option<Box<dyn FnOnce()>> {
86        None
87    }
88}
89
90pub trait SystemPluginAudioWorker {
91    fn on_sample(&mut self, _time: Time, _machine: &mut Machine) -> ReturnCode {
92        0
93    }
94    fn gen_interfaces(&self) -> Vec<SysPluginSignature>;
95}
96
97/// A dynamically dispatched plugin wrapped in reference-counted storage.
98pub struct DynSystemPlugin {
99    pub inner: Arc<UnsafeCell<dyn SystemPlugin>>,
100    audioworker: Option<Box<dyn SystemPluginAudioWorker>>,
101    pub clsinfos: Vec<ExtClsInfo>,
102    pub macroinfos: Vec<MacroInfo>,
103}
104
105impl DynSystemPlugin{
106    pub fn take_audioworker(&mut self) -> Option<Box<dyn SystemPluginAudioWorker>> {
107        self.audioworker.take()
108    }
109}
110/// Convert a plugin into the VM-facing representation.
111///
112/// The returned [`DynSystemPlugin`] is stored by the runtime, while the
113/// accompanying `Vec<ExtClsInfo>` contains closures that expose the plugin's
114/// callback methods to mimium code.
115impl<T> From<T> for DynSystemPlugin
116where
117    T: SystemPlugin + Sized + 'static,
118{
119    fn from(mut plugin: T) -> Self {
120        let mut audioworker = plugin.generate_audioworker();
121
122        let ifs = plugin.gen_interfaces();
123        let inner = Arc::new(UnsafeCell::new(plugin));
124        let macroinfos = ifs
125            .iter()
126            .filter(|&SysPluginSignature { stage, .. }| matches!(stage, EvalStage::Stage(0)))
127            .map(|SysPluginSignature { name, fun, ty, .. }| {
128                let inner = inner.clone();
129                let fun = fun
130                    .clone()
131                    .downcast::<SystemPluginMacroType<T>>()
132                    .expect("invalid conversion applied in the system plugin resolution.");
133                MacroInfo::new(
134                    name.to_symbol(),
135                    *ty,
136                    Rc::new(RefCell::new(move |args: &[(Value, TypeNodeId)]| -> Value {
137                        // breaking double borrow rule at here!!!
138                        // Also here I do dirty downcasting because here the type of plugin is ensured as T.
139                        unsafe {
140                            let p = inner.get().as_mut().unwrap();
141                            fun(p, args)
142                        }
143                    })),
144                )
145            })
146            .collect();
147        let clsinfos = ifs
148            .into_iter()
149            .chain(
150                audioworker
151                    .as_mut()
152                    .map(|worker| worker.gen_interfaces())
153                    .into_iter()
154                    .flatten(),
155            )
156            .filter(|SysPluginSignature { stage, .. }| matches!(stage, EvalStage::Stage(1)))
157            .map(|SysPluginSignature { name, fun, ty, .. }| {
158                let inner = inner.clone();
159                let fun = fun
160                    .clone()
161                    .downcast::<SystemPluginFnType<T>>()
162                    .expect("invalid conversion applied in the system plugin resolution.");
163                let fun = Rc::new(RefCell::new(move |machine: &mut Machine| -> ReturnCode {
164                    // breaking double borrow rule at here!!!
165                    // Also here I do dirty downcasting because here the type of plugin is ensured as T.
166                    unsafe {
167                        let p = inner.get().as_mut().unwrap();
168                        fun(p, machine)
169                    }
170                }));
171                ExtClsInfo::new(name.to_symbol(), ty, fun)
172            })
173            .collect();
174        DynSystemPlugin {
175            inner,
176            audioworker,
177            clsinfos,
178            macroinfos,
179        }
180    }
181}
182