1
0
Fork 0

Support exporting multiple CLAP plugins

This required the factory to be rewritten as a macro. since variadic
generics are not yet a thing. Not 100% satisfied with this design yet,
but it's much less ugly than my earlier attempts at this.
This commit is contained in:
Robbert van der Helm 2023-08-05 16:38:57 +02:00
parent ff89e25d2f
commit 3ccaced613
6 changed files with 118 additions and 104 deletions

View file

@ -10,6 +10,18 @@ Since there is no stable release yet, the changes are organized per day in
reverse chronological order. The main purpose of this document in its current reverse chronological order. The main purpose of this document in its current
state is to list breaking changes. state is to list breaking changes.
## [2023-08-05]
### Breaking changes
- The minimum supported Rust version has been bumped to 1.70 so we can start
using `OnceCell` and `OnceLock` to phase out uses of `lazy_static`.
### Added
- `nih_export_clap!()` can now take more than one plugin type argument to allow
exporting more than one plugin from a single plugin library.
## [2023-05-13] ## [2023-05-13]
### Fixed ### Fixed

View file

@ -2,7 +2,7 @@
name = "nih_plug" name = "nih_plug"
version = "0.0.0" version = "0.0.0"
edition = "2021" edition = "2021"
rust-version = "1.63" rust-version = "1.70"
authors = ["Robbert van der Helm <mail@robbertvanderhelm.nl>"] authors = ["Robbert van der Helm <mail@robbertvanderhelm.nl>"]
license = "ISC" license = "ISC"

View file

@ -3,31 +3,114 @@ mod util;
mod context; mod context;
mod descriptor; mod descriptor;
mod factory;
pub mod features; pub mod features;
mod wrapper; mod wrapper;
/// Re-export for the wrapper. /// Re-export for the macro
pub use self::factory::Factory; pub use self::descriptor::PluginDescriptor;
pub use self::wrapper::Wrapper;
pub use clap_sys::entry::clap_plugin_entry; pub use clap_sys::entry::clap_plugin_entry;
pub use clap_sys::factory::plugin_factory::CLAP_PLUGIN_FACTORY_ID; pub use clap_sys::factory::plugin_factory::{clap_plugin_factory, CLAP_PLUGIN_FACTORY_ID};
pub use clap_sys::host::clap_host;
pub use clap_sys::plugin::{clap_plugin, clap_plugin_descriptor};
pub use clap_sys::version::CLAP_VERSION; pub use clap_sys::version::CLAP_VERSION;
pub use lazy_static::lazy_static; pub use lazy_static::lazy_static;
/// Export a CLAP plugin from this library using the provided plugin type. /// Export a CLAP plugin from this library using the provided plugin type.
#[macro_export] #[macro_export]
macro_rules! nih_export_clap { macro_rules! nih_export_clap {
($plugin_ty:ty) => { ($($plugin_ty:ty),+) => {
// We need a function pointer to a [wrapper::get_factory()] that creates a factory for `$plugin_ty`, so we need to generate the function inside of this macro // Earlier versions used a simple generic struct for this, but because we don't have
// variadic generics (yet) we can't generate the struct for multiple plugin types without
// macros. So instead we'll generate the implementation ad-hoc inside of this macro.
#[doc(hidden)] #[doc(hidden)]
mod clap { mod clap {
// Because `$plugin_ty` is likely defined in the enclosing scope // Because the `$plugin_ty`s are likely defined in the enclosing scope. This works even
// if the types are not public because this is a child module.
use super::*; use super::*;
// We don't use generics inside of statics, so this lazy_static is used as kind of an const CLAP_PLUGIN_FACTORY: ::nih_plug::wrapper::clap::clap_plugin_factory =
// escape hatch ::nih_plug::wrapper::clap::clap_plugin_factory {
::nih_plug::wrapper::clap::lazy_static! { get_plugin_count: Some(get_plugin_count),
static ref FACTORY: ::nih_plug::wrapper::clap::Factory<$plugin_ty> = ::nih_plug::wrapper::clap::Factory::default(); get_plugin_descriptor: Some(get_plugin_descriptor),
create_plugin: Some(create_plugin),
};
// Sneaky way to get the number of expanded elements
const PLUGIN_COUNT: usize = [$(stringify!($plugin_ty)),+].len();
// We'll put these plugin descriptors in a tuple since we can't easily associate them
// with indices without involving even more macros. We can't initialize this tuple
// completely statically
static PLUGIN_DESCRIPTORS: ::std::sync::OnceLock<
[::nih_plug::wrapper::clap::PluginDescriptor; PLUGIN_COUNT]
> = ::std::sync::OnceLock::new();
fn plugin_descriptors() -> &'static [::nih_plug::wrapper::clap::PluginDescriptor; PLUGIN_COUNT] {
PLUGIN_DESCRIPTORS.get_or_init(|| {
let descriptors = [$(::nih_plug::wrapper::clap::PluginDescriptor::for_plugin::<$plugin_ty>()),+];
if cfg!(debug_assertions) {
let unique_plugin_ids: std::collections::HashSet<_>
= descriptors.iter().map(|d| d.clap_id()).collect();
::nih_plug::debug::nih_debug_assert_eq!(
unique_plugin_ids.len(),
descriptors.len(),
"Duplicate plugin IDs found in `nih_export_clap!()` call"
);
}
descriptors
})
}
unsafe extern "C" fn get_plugin_count(
_factory: *const ::nih_plug::wrapper::clap::clap_plugin_factory,
) -> u32 {
plugin_descriptors().len() as u32
}
unsafe extern "C" fn get_plugin_descriptor (
_factory: *const ::nih_plug::wrapper::clap::clap_plugin_factory,
index: u32,
) -> *const ::nih_plug::wrapper::clap::clap_plugin_descriptor {
match plugin_descriptors().get(index as usize) {
Some(descriptor) => descriptor.clap_plugin_descriptor(),
None => std::ptr::null()
}
}
unsafe extern "C" fn create_plugin (
factory: *const ::nih_plug::wrapper::clap::clap_plugin_factory,
host: *const ::nih_plug::wrapper::clap::clap_host,
plugin_id: *const ::std::os::raw::c_char,
) -> *const ::nih_plug::wrapper::clap::clap_plugin {
if plugin_id.is_null() {
return ::std::ptr::null();
}
let plugin_id_cstr = ::std::ffi::CStr::from_ptr(plugin_id);
// This isn't great, but we'll just assume that `$plugin_ids` and the descriptors
// are in the same order. We also can't directly enumerate over them with an index,
// which is why we do things the way we do. Otherwise we could have used a tuple
// instead.
let descriptors = plugin_descriptors();
let mut descriptor_idx = 0;
$({
let descriptor = &descriptors[descriptor_idx];
if plugin_id_cstr == descriptor.clap_id() {
// Arc does not have a convenient leak function like Box, so this gets a bit awkward
// This pointer gets turned into an Arc and its reference count decremented in
// [Wrapper::destroy()]
return (*Arc::into_raw(::nih_plug::wrapper::clap::Wrapper::<$plugin_ty>::new(host)))
.clap_plugin
.as_ptr();
}
descriptor_idx += 1;
})+
std::ptr::null()
} }
pub extern "C" fn init(_plugin_path: *const ::std::os::raw::c_char) -> bool { pub extern "C" fn init(_plugin_path: *const ::std::os::raw::c_char) -> bool {
@ -44,7 +127,7 @@ macro_rules! nih_export_clap {
&& unsafe { ::std::ffi::CStr::from_ptr(factory_id) } && unsafe { ::std::ffi::CStr::from_ptr(factory_id) }
== ::nih_plug::wrapper::clap::CLAP_PLUGIN_FACTORY_ID == ::nih_plug::wrapper::clap::CLAP_PLUGIN_FACTORY_ID
{ {
&*FACTORY as *const _ as *const ::std::ffi::c_void &CLAP_PLUGIN_FACTORY as *const _ as *const ::std::ffi::c_void
} else { } else {
std::ptr::null() std::ptr::null()
} }

View file

@ -1,7 +1,6 @@
use clap_sys::plugin::clap_plugin_descriptor; use clap_sys::plugin::clap_plugin_descriptor;
use clap_sys::version::CLAP_VERSION; use clap_sys::version::CLAP_VERSION;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::os::raw::c_char; use std::os::raw::c_char;
use crate::prelude::ClapPlugin; use crate::prelude::ClapPlugin;
@ -11,7 +10,7 @@ use crate::prelude::ClapPlugin;
/// ///
/// This cannot be cloned as [`Self::clap_features_ptrs`] contains pointers to /// This cannot be cloned as [`Self::clap_features_ptrs`] contains pointers to
/// [Self::clap_features]. /// [Self::clap_features].
pub struct PluginDescriptor<P: ClapPlugin> { pub struct PluginDescriptor {
// We need [CString]s for all of `ClapPlugin`'s `&str` fields // We need [CString]s for all of `ClapPlugin`'s `&str` fields
clap_id: CString, clap_id: CString,
name: CString, name: CString,
@ -29,13 +28,14 @@ pub struct PluginDescriptor<P: ClapPlugin> {
/// descriptor upfront. We also need to initialize the `CString` fields above first before we /// descriptor upfront. We also need to initialize the `CString` fields above first before we
/// can initialize this plugin descriptor. /// can initialize this plugin descriptor.
plugin_descriptor: Option<clap_plugin_descriptor>, plugin_descriptor: Option<clap_plugin_descriptor>,
/// The plugin's type.
_phantom: PhantomData<P>,
} }
impl<P: ClapPlugin> Default for PluginDescriptor<P> { unsafe impl Send for PluginDescriptor {}
fn default() -> Self { unsafe impl Sync for PluginDescriptor {}
impl PluginDescriptor {
/// Construct the plugin descriptor for a specific CLAP plugin.
pub fn for_plugin<P: ClapPlugin>() -> Self {
let mut descriptor = Self { let mut descriptor = Self {
clap_id: CString::new(P::CLAP_ID).expect("`CLAP_ID` contained null bytes"), clap_id: CString::new(P::CLAP_ID).expect("`CLAP_ID` contained null bytes"),
name: CString::new(P::NAME).expect("`NAME` contained null bytes"), name: CString::new(P::NAME).expect("`NAME` contained null bytes"),
@ -59,8 +59,6 @@ impl<P: ClapPlugin> Default for PluginDescriptor<P> {
// descriptor // descriptor
clap_features_ptrs: Vec::new(), clap_features_ptrs: Vec::new(),
plugin_descriptor: None, plugin_descriptor: None,
_phantom: PhantomData,
}; };
// The keyword list is an environ-like list of char pointers terminated by a null pointer. // The keyword list is an environ-like list of char pointers terminated by a null pointer.
@ -101,12 +99,7 @@ impl<P: ClapPlugin> Default for PluginDescriptor<P> {
descriptor descriptor
} }
}
unsafe impl<P: ClapPlugin> Send for PluginDescriptor<P> {}
unsafe impl<P: ClapPlugin> Sync for PluginDescriptor<P> {}
impl<P: ClapPlugin> PluginDescriptor<P> {
pub fn clap_plugin_descriptor(&self) -> &clap_plugin_descriptor { pub fn clap_plugin_descriptor(&self) -> &clap_plugin_descriptor {
self.plugin_descriptor.as_ref().unwrap() self.plugin_descriptor.as_ref().unwrap()
} }

View file

@ -1,75 +0,0 @@
use clap_sys::factory::plugin_factory::clap_plugin_factory;
use clap_sys::host::clap_host;
use clap_sys::plugin::{clap_plugin, clap_plugin_descriptor};
use std::ffi::CStr;
use std::os::raw::c_char;
use std::ptr;
use std::sync::Arc;
use super::descriptor::PluginDescriptor;
use super::wrapper::Wrapper;
use crate::prelude::ClapPlugin;
/// The plugin's factory. Initialized using a lazy_static from the entry point's `get_factory()`
/// function. From this point onwards we don't need to generate code with macros anymore.
#[doc(hidden)]
#[repr(C)]
pub struct Factory<P: ClapPlugin> {
// Keep the vtable as the first field so we can do a simple pointer cast. There's no data
// pointer as the API expects this thing to be entirely static, which in our case it isn't.
pub clap_plugin_factory: clap_plugin_factory,
plugin_descriptor: PluginDescriptor<P>,
}
impl<P: ClapPlugin> Default for Factory<P> {
fn default() -> Self {
Self {
clap_plugin_factory: clap_plugin_factory {
get_plugin_count: Some(Self::get_plugin_count),
get_plugin_descriptor: Some(Self::get_plugin_descriptor),
create_plugin: Some(Self::create_plugin),
},
plugin_descriptor: PluginDescriptor::default(),
}
}
}
impl<P: ClapPlugin> Factory<P> {
unsafe extern "C" fn get_plugin_count(_factory: *const clap_plugin_factory) -> u32 {
1
}
unsafe extern "C" fn get_plugin_descriptor(
factory: *const clap_plugin_factory,
index: u32,
) -> *const clap_plugin_descriptor {
let factory = &*(factory as *const Self);
if index == 0 {
factory.plugin_descriptor.clap_plugin_descriptor()
} else {
ptr::null()
}
}
unsafe extern "C" fn create_plugin(
factory: *const clap_plugin_factory,
host: *const clap_host,
plugin_id: *const c_char,
) -> *const clap_plugin {
let factory = &*(factory as *const Self);
if !plugin_id.is_null() && CStr::from_ptr(plugin_id) == factory.plugin_descriptor.clap_id()
{
// Arc does not have a convenient leak function like Box, so this gets a bit awkward
// This pointer gets turned into an Arc and its reference count decremented in
// [Wrapper::destroy()]
(*Arc::into_raw(Wrapper::<P>::new(host)))
.clap_plugin
.as_ptr()
} else {
ptr::null()
}
}
}

View file

@ -174,7 +174,7 @@ pub struct Wrapper<P: ClapPlugin> {
pub clap_plugin: AtomicRefCell<clap_plugin>, pub clap_plugin: AtomicRefCell<clap_plugin>,
/// Needs to be boxed because the plugin object is supposed to contain a static reference to /// Needs to be boxed because the plugin object is supposed to contain a static reference to
/// this. /// this.
_plugin_descriptor: Box<PluginDescriptor<P>>, _plugin_descriptor: Box<PluginDescriptor>,
clap_plugin_audio_ports: clap_plugin_audio_ports, clap_plugin_audio_ports: clap_plugin_audio_ports,
@ -433,7 +433,8 @@ impl<P: ClapPlugin> Wrapper<P> {
// on `Self::updated_state_sender` // on `Self::updated_state_sender`
let (updated_state_sender, updated_state_receiver) = channel::bounded(0); let (updated_state_sender, updated_state_receiver) = channel::bounded(0);
let plugin_descriptor: Box<PluginDescriptor<P>> = Box::default(); let plugin_descriptor: Box<PluginDescriptor> =
Box::new(PluginDescriptor::for_plugin::<P>());
// We're not allowed to query any extensions until the init function has been called, so we // We're not allowed to query any extensions until the init function has been called, so we
// need a bunch of AtomicRefCells instead // need a bunch of AtomicRefCells instead