use std::path::Path;
#[cfg(any(feature = "clap-host", all(feature = "lv2-host", target_os = "linux")))]
use std::sync::Arc;
use sim_kernel::{Error, Result};
use sim_lib_plugin_core::{CapabilitySet, PluginInstance};
use crate::WasmResourceLimits;
#[cfg(feature = "clap-host")]
type ClapRoute = dyn Fn(&Path, &CapabilitySet) -> Result<RoutedPlugin> + Send + Sync + 'static;
#[cfg(all(feature = "lv2-host", target_os = "linux"))]
type Lv2Route = dyn Fn(&Path, &CapabilitySet) -> Result<RoutedPlugin> + Send + Sync + 'static;
pub type RoutedPlugin = Box<dyn PluginInstance>;
#[derive(Clone)]
pub struct PluginRouter {
limits: WasmResourceLimits,
#[cfg(feature = "clap-host")]
clap_route: Option<Arc<ClapRoute>>,
#[cfg(all(feature = "lv2-host", target_os = "linux"))]
lv2_route: Option<Arc<Lv2Route>>,
}
impl PluginRouter {
pub fn new(limits: WasmResourceLimits) -> Self {
PluginRouterBuilder::new(limits).build()
}
pub fn builder(limits: WasmResourceLimits) -> PluginRouterBuilder {
PluginRouterBuilder::new(limits)
}
pub fn load(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
match path
.extension()
.and_then(|extension| extension.to_str())
.map(str::to_ascii_lowercase)
.as_deref()
{
Some("wasm") => self.load_wasm(path, caps),
Some("clap") => self.load_clap(path, caps),
Some("lv2") => self.load_lv2(path, caps),
other => Err(Error::Eval(format!(
"unrecognised plugin extension {other:?}; expected .wasm, .clap, or .lv2"
))),
}
}
#[cfg(feature = "wasm-plugin")]
fn load_wasm(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
let bytes = std::fs::read(path)
.map_err(|err| Error::Eval(format!("cannot read {}: {err}", path.display())))?;
let plugin = crate::load_wasm_plugin(caps, &bytes, self.limits)?;
Ok(Box::new(plugin))
}
#[cfg(not(feature = "wasm-plugin"))]
fn load_wasm(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
let _ = (path, caps, self.limits);
Err(Error::Eval("wasm-plugin feature not enabled".to_owned()))
}
#[cfg(feature = "clap-host")]
fn load_clap(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
let Some(route) = &self.clap_route else {
return Err(Error::Eval("clap-host provider not configured".to_owned()));
};
route(path, caps)
}
#[cfg(not(feature = "clap-host"))]
fn load_clap(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
let _ = (path, caps);
Err(Error::Eval("clap-host feature not enabled".to_owned()))
}
#[cfg(all(feature = "lv2-host", target_os = "linux"))]
fn load_lv2(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
let Some(route) = &self.lv2_route else {
return Err(Error::Eval("lv2-host provider not configured".to_owned()));
};
route(path, caps)
}
#[cfg(not(all(feature = "lv2-host", target_os = "linux")))]
fn load_lv2(&self, path: &Path, caps: &CapabilitySet) -> Result<RoutedPlugin> {
let _ = (path, caps);
Err(Error::Eval(
"lv2-host feature not enabled or not Linux".to_owned(),
))
}
}
pub struct PluginRouterBuilder {
limits: WasmResourceLimits,
#[cfg(feature = "clap-host")]
clap_route: Option<Arc<ClapRoute>>,
#[cfg(all(feature = "lv2-host", target_os = "linux"))]
lv2_route: Option<Arc<Lv2Route>>,
}
impl PluginRouterBuilder {
pub fn new(limits: WasmResourceLimits) -> Self {
Self {
limits,
#[cfg(feature = "clap-host")]
clap_route: None,
#[cfg(all(feature = "lv2-host", target_os = "linux"))]
lv2_route: None,
}
}
#[cfg(feature = "clap-host")]
pub fn with_clap_provider<P>(mut self, provider: P) -> Self
where
P: sim_lib_plugin_clap::native::ClapHostProvider + Send + Sync + 'static,
P::Plugin: 'static,
{
self.clap_route = Some(Arc::new(move |path, caps| {
let spec = sim_lib_plugin_core::PluginLoadSpec::new(
sim_lib_plugin_core::PluginFormat::Clap,
path.to_string_lossy().into_owned(),
)?;
let plugin = crate::native_fallback::load_clap_plugin(caps, &spec, &provider)?;
Ok(Box::new(plugin))
}));
self
}
#[cfg(all(feature = "lv2-host", target_os = "linux"))]
pub fn with_lv2_provider<P>(mut self, provider: P) -> Self
where
P: sim_lib_plugin_lv2::native::Lv2HostProvider + Send + Sync + 'static,
P::Plugin: 'static,
{
self.lv2_route = Some(Arc::new(move |path, caps| {
let plugin = crate::native_fallback::load_lv2_plugin(
caps,
path.to_string_lossy().as_ref(),
&provider,
)?;
Ok(Box::new(plugin))
}));
self
}
pub fn build(self) -> PluginRouter {
PluginRouter {
limits: self.limits,
#[cfg(feature = "clap-host")]
clap_route: self.clap_route,
#[cfg(all(feature = "lv2-host", target_os = "linux"))]
lv2_route: self.lv2_route,
}
}
}