sim_lib_plugin_wasm/
router.rs1use std::path::Path;
4#[cfg(any(feature = "clap-host", all(feature = "lv2-host", target_os = "linux")))]
5use std::sync::Arc;
6
7use sim_kernel::{Error, Result};
8use sim_lib_plugin_core::{CapabilitySet, PluginInstance};
9
10use crate::WasmResourceLimits;
11
12#[cfg(feature = "clap-host")]
13type ClapRoute = dyn Fn(&Path, &CapabilitySet) -> Result<RoutedPlugin> + Send + Sync + 'static;
14#[cfg(all(feature = "lv2-host", target_os = "linux"))]
15type Lv2Route = dyn Fn(&Path, &CapabilitySet) -> Result<RoutedPlugin> + Send + Sync + 'static;
16
17pub type RoutedPlugin = Box<dyn PluginInstance>;
19
20#[derive(Clone)]
22pub struct PluginRouter {
23 limits: WasmResourceLimits,
24 #[cfg(feature = "clap-host")]
25 clap_route: Option<Arc<ClapRoute>>,
26 #[cfg(all(feature = "lv2-host", target_os = "linux"))]
27 lv2_route: Option<Arc<Lv2Route>>,
28}
29
30impl PluginRouter {
31 pub fn new(limits: WasmResourceLimits) -> Self {
34 PluginRouterBuilder::new(limits).build()
35 }
36
37 pub fn builder(limits: WasmResourceLimits) -> PluginRouterBuilder {
39 PluginRouterBuilder::new(limits)
40 }
41
42 pub fn load(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
50 match path
51 .extension()
52 .and_then(|extension| extension.to_str())
53 .map(str::to_ascii_lowercase)
54 .as_deref()
55 {
56 Some("wasm") => self.load_wasm(path, caps),
57 Some("clap") => self.load_clap(path, caps),
58 Some("lv2") => self.load_lv2(path, caps),
59 other => Err(Error::Eval(format!(
60 "unrecognised plugin extension {other:?}; expected .wasm, .clap, or .lv2"
61 ))),
62 }
63 }
64
65 #[cfg(feature = "wasm-plugin")]
66 fn load_wasm(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
67 let bytes = std::fs::read(path)
68 .map_err(|err| Error::Eval(format!("cannot read {}: {err}", path.display())))?;
69 let plugin = crate::load_wasm_plugin(caps, &bytes, self.limits)?;
70 Ok(Box::new(plugin))
71 }
72
73 #[cfg(not(feature = "wasm-plugin"))]
74 fn load_wasm(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
75 let _ = (path, caps, self.limits);
76 Err(Error::Eval("wasm-plugin feature not enabled".to_owned()))
77 }
78
79 #[cfg(feature = "clap-host")]
80 fn load_clap(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
81 let Some(route) = &self.clap_route else {
82 return Err(Error::Eval("clap-host provider not configured".to_owned()));
83 };
84 route(path, caps)
85 }
86
87 #[cfg(not(feature = "clap-host"))]
88 fn load_clap(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
89 let _ = (path, caps);
90 Err(Error::Eval("clap-host feature not enabled".to_owned()))
91 }
92
93 #[cfg(all(feature = "lv2-host", target_os = "linux"))]
94 fn load_lv2(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
95 let Some(route) = &self.lv2_route else {
96 return Err(Error::Eval("lv2-host provider not configured".to_owned()));
97 };
98 route(path, caps)
99 }
100
101 #[cfg(not(all(feature = "lv2-host", target_os = "linux")))]
102 fn load_lv2(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
103 let _ = (path, caps);
104 Err(Error::Eval(
105 "lv2-host feature not enabled or not Linux".to_owned(),
106 ))
107 }
108}
109
110pub struct PluginRouterBuilder {
112 limits: WasmResourceLimits,
113 #[cfg(feature = "clap-host")]
114 clap_route: Option<Arc<ClapRoute>>,
115 #[cfg(all(feature = "lv2-host", target_os = "linux"))]
116 lv2_route: Option<Arc<Lv2Route>>,
117}
118
119impl PluginRouterBuilder {
120 pub fn new(limits: WasmResourceLimits) -> Self {
122 Self {
123 limits,
124 #[cfg(feature = "clap-host")]
125 clap_route: None,
126 #[cfg(all(feature = "lv2-host", target_os = "linux"))]
127 lv2_route: None,
128 }
129 }
130
131 #[cfg(feature = "clap-host")]
133 pub fn with_clap_provider<P>(mut self, provider: P) -> Self
134 where
135 P: sim_lib_plugin_clap::native::ClapHostProvider + Send + Sync + 'static,
136 P::Plugin: 'static,
137 {
138 self.clap_route = Some(Arc::new(move |path, caps| {
139 let spec = sim_lib_plugin_core::PluginLoadSpec::new(
140 sim_lib_plugin_core::PluginFormat::Clap,
141 path.to_string_lossy().into_owned(),
142 )?;
143 let plugin = crate::native_fallback::load_clap_plugin(caps, &spec, &provider)?;
144 Ok(Box::new(plugin))
145 }));
146 self
147 }
148
149 #[cfg(all(feature = "lv2-host", target_os = "linux"))]
151 pub fn with_lv2_provider<P>(mut self, provider: P) -> Self
152 where
153 P: sim_lib_plugin_lv2::native::Lv2HostProvider + Send + Sync + 'static,
154 P::Plugin: 'static,
155 {
156 self.lv2_route = Some(Arc::new(move |path, caps| {
157 let plugin = crate::native_fallback::load_lv2_plugin(
158 caps,
159 path.to_string_lossy().as_ref(),
160 &provider,
161 )?;
162 Ok(Box::new(plugin))
163 }));
164 self
165 }
166
167 pub fn build(self) -> PluginRouter {
169 PluginRouter {
170 limits: self.limits,
171 #[cfg(feature = "clap-host")]
172 clap_route: self.clap_route,
173 #[cfg(all(feature = "lv2-host", target_os = "linux"))]
174 lv2_route: self.lv2_route,
175 }
176 }
177}