Skip to main content

clack_plugin/plugin/
instance.rs

1use crate::extensions::PluginExtensions;
2use crate::extensions::wrapper::{PluginWrapper, PluginWrapperError, handle_panic};
3use crate::host::{HostInfo, HostMainThreadHandle, HostSharedHandle};
4use crate::plugin::instance::WrapperData::*;
5use crate::plugin::{Plugin, PluginAudioProcessor, PluginError, PluginMainThread};
6use crate::prelude::PluginDescriptor;
7use crate::process::{Audio, Events, PluginAudioConfiguration, Process};
8use clap_sys::plugin::{clap_plugin, clap_plugin_descriptor};
9use clap_sys::process::{CLAP_PROCESS_ERROR, clap_process, clap_process_status};
10use core::ffi::c_void;
11use std::cell::UnsafeCell;
12use std::ffi::CStr;
13use std::marker::PhantomData;
14use std::mem::ManuallyDrop;
15use std::sync::atomic::{AtomicU8, Ordering};
16
17pub(crate) trait PluginInitializer<'a, P: Plugin>: 'a {
18    fn init(
19        self: Box<Self>,
20        host: HostMainThreadHandle<'a>,
21    ) -> Result<PluginWrapper<'a, P>, PluginError>;
22}
23
24impl<'a, P: Plugin, FS: 'a, FM: 'a> PluginInitializer<'a, P> for (FS, FM)
25where
26    FS: FnOnce(HostSharedHandle<'a>) -> Result<P::Shared<'a>, PluginError>,
27    FM: FnOnce(
28        HostMainThreadHandle<'a>,
29        &'a P::Shared<'a>,
30    ) -> Result<P::MainThread<'a>, PluginError>,
31{
32    fn init(
33        self: Box<Self>,
34        host: HostMainThreadHandle<'a>,
35    ) -> Result<PluginWrapper<'a, P>, PluginError> {
36        let (shared_initializer, main_thread_initializer) = *self;
37        let shared_handle = host.shared();
38        let shared = Box::pin(shared_initializer(shared_handle)?);
39
40        // SAFETY: this lives long enough
41        let shared_ref = unsafe { &*(shared.as_ref().get_ref() as *const _) };
42        let main_thread = main_thread_initializer(host, shared_ref)?;
43
44        // SAFETY: we just created the shared and main_thread together
45        Ok(unsafe { PluginWrapper::new(shared_handle, shared, main_thread) })
46    }
47}
48
49impl<'a, P: Plugin, F: 'a, FM: 'a> PluginInitializer<'a, P> for F
50where
51    F: FnOnce(HostMainThreadHandle<'a>) -> Result<(P::Shared<'a>, FM), PluginError>,
52    FM: FnOnce(&'a P::Shared<'a>) -> Result<P::MainThread<'a>, PluginError>,
53{
54    fn init(
55        self: Box<Self>,
56        host: HostMainThreadHandle<'a>,
57    ) -> Result<PluginWrapper<'a, P>, PluginError> {
58        let shared_handle = host.shared();
59        let (shared, main_thread_initializer) = self(host)?;
60        let shared = Box::pin(shared);
61
62        // SAFETY: this lives long enough
63        let shared_ref = unsafe { &*(shared.as_ref().get_ref() as *const _) };
64        let main_thread = main_thread_initializer(shared_ref)?;
65
66        // SAFETY: we just created the shared and main_thread together
67        Ok(unsafe { PluginWrapper::new(shared_handle, shared, main_thread) })
68    }
69}
70
71pub(crate) enum WrapperData<'a, P: Plugin> {
72    Initialized(PluginWrapper<'a, P>),
73    Initializing,
74    Uninitialized(Box<dyn PluginInitializer<'a, P>>),
75}
76
77pub(crate) struct PluginBoxInner<'a, P: Plugin> {
78    host: HostSharedHandle<'a>,
79    state: AtomicU8,
80    plugin_data: UnsafeCell<WrapperData<'a, P>>,
81}
82
83const UNINITIALIZED: u8 = 0;
84const INITIALIZED: u8 = 1;
85const INITIALIZING: u8 = 2;
86const INITIALIZATION_FAILED: u8 = 3;
87const DESTROYING: u8 = 4;
88
89impl<'a, P: Plugin> PluginBoxInner<'a, P> {
90    #[inline]
91    pub(crate) fn wrapper_uninit(
92        &self,
93    ) -> Result<Option<&PluginWrapper<'a, P>>, PluginWrapperError> {
94        match self.state.load(Ordering::Acquire) {
95            INITIALIZING => Ok(None),
96            UNINITIALIZED => Err(PluginWrapperError::UninitializedPlugin),
97            INITIALIZATION_FAILED => Err(PluginWrapperError::InitializationAlreadyFailed),
98            DESTROYING => Err(PluginWrapperError::Destroying),
99
100            // SAFETY: when in the initialized state, it is guarantee that plugin_data is never written to again.
101            INITIALIZED => match unsafe { &*self.plugin_data.get() } {
102                Initialized(w) => Ok(Some(w)),
103                // If the state is Initialized, then the enum should have been set properly
104                _ => unreachable!(),
105            },
106            _ => unreachable!(),
107        }
108    }
109
110    #[inline]
111    pub(crate) fn wrapper(&self) -> Result<&PluginWrapper<'a, P>, PluginWrapperError> {
112        match self.state.load(Ordering::Acquire) {
113            INITIALIZING => Err(PluginWrapperError::PluginCalledDuringInitialization),
114            UNINITIALIZED => Err(PluginWrapperError::UninitializedPlugin),
115            INITIALIZATION_FAILED => Err(PluginWrapperError::InitializationAlreadyFailed),
116            DESTROYING => Err(PluginWrapperError::Destroying),
117
118            // SAFETY: when in the initialized state, it is guarantee that plugin_data is never written to again.
119            INITIALIZED => match unsafe { &*self.plugin_data.get() } {
120                Initialized(w) => Ok(w),
121                // If the state is Initialized, then the enum should have been set properly
122                _ => unreachable!(),
123            },
124            _ => unreachable!(),
125        }
126    }
127}
128
129impl<'a, P: Plugin> PluginBoxInner<'a, P> {
130    fn get_plugin_desc(
131        host: HostSharedHandle<'a>,
132        desc: &'a clap_plugin_descriptor,
133        initializer: Box<dyn PluginInitializer<'a, P>>,
134    ) -> clap_plugin {
135        clap_plugin {
136            desc,
137            plugin_data: Box::into_raw(Box::new(Self {
138                host,
139                plugin_data: UnsafeCell::new(Uninitialized(initializer)),
140                state: AtomicU8::new(UNINITIALIZED),
141            }))
142            .cast(),
143            init: Some(Self::init),
144            destroy: Some(Self::destroy),
145            activate: Some(Self::activate),
146            deactivate: Some(Self::deactivate),
147            reset: Some(Self::reset),
148            start_processing: Some(Self::start_processing),
149            stop_processing: Some(Self::stop_processing),
150            process: Some(Self::process),
151            get_extension: Some(Self::get_extension),
152            on_main_thread: Some(Self::on_main_thread),
153        }
154    }
155
156    #[inline]
157    pub fn host(&self) -> &HostSharedHandle<'a> {
158        &self.host
159    }
160
161    #[allow(clippy::missing_safety_doc)]
162    unsafe extern "C" fn init(plugin: *const clap_plugin) -> bool {
163        PluginWrapper::<P>::handle_plugin_data(plugin, |data| {
164            // We can only keep a shared reference in here, now that init() is called.
165            let data = data.as_ref();
166
167            let current_state = data.state.compare_exchange(
168                UNINITIALIZED,
169                INITIALIZING,
170                Ordering::SeqCst,
171                Ordering::Acquire,
172            );
173
174            let uninit_data = match current_state {
175                Ok(UNINITIALIZED) => data.plugin_data.get().replace(Initializing),
176                Ok(s) | Err(s) => match s {
177                    INITIALIZED => return Err(PluginWrapperError::AlreadyInitialized),
178                    DESTROYING => return Err(PluginWrapperError::Destroying),
179                    INITIALIZATION_FAILED | INITIALIZING => {
180                        return Err(PluginWrapperError::InitializationAlreadyFailed);
181                    }
182                    _ => unreachable!(),
183                },
184            };
185
186            let Uninitialized(initializer) = uninit_data else {
187                unreachable!()
188            };
189
190            let init_result = initializer.init(data.host.as_main_thread_unchecked());
191
192            match init_result {
193                Ok(wrapper) => {
194                    // We now guaranteed that the current state is INITIALIZING, so there is nothing to drop.
195                    data.plugin_data.get().write(Initialized(wrapper));
196                    // The write operation completed, we can now inform other threads that initialization is complete.
197                    data.state.store(INITIALIZED, Ordering::Release);
198                    Ok(())
199                }
200                Err(e) => {
201                    data.state.store(INITIALIZATION_FAILED, Ordering::Release);
202                    Err(e.into())
203                }
204            }
205        })
206        .is_some()
207    }
208
209    #[allow(clippy::missing_safety_doc)]
210    unsafe extern "C" fn destroy(plugin: *const clap_plugin) {
211        // Deactivate the plugin, in case the host didn't call deactivate() first.
212        // This also handles all kinds of logging in case things are already wrong (double free, etc.)
213        PluginWrapper::<P>::handle_plugin_data(plugin, |data| {
214            use PluginWrapperError::*;
215
216            let wrapper = match data.as_ref().wrapper() {
217                Ok(w) => w,
218                // Silence those errors: it is normal to call destroy in these states, no need to log warnings.
219                Err(
220                    PluginCalledDuringInitialization
221                    | UninitializedPlugin
222                    | InitializationAlreadyFailed,
223                ) => return Ok(()),
224                Err(e) => return Err(e),
225            };
226
227            if wrapper.is_active() {
228                wrapper.deactivate()
229            } else {
230                Ok(())
231            }
232        });
233
234        if let Some(plugin) = plugin.cast_mut().as_mut() {
235            // Try our best to guard against double-free
236            let Some(plugin_data) = plugin.plugin_data.cast::<PluginBoxInner<P>>().as_mut() else {
237                return;
238            };
239
240            // We could use direct &mut access for the swap, but let's use atomic operations just in case...
241            if plugin_data.state.swap(DESTROYING, Ordering::SeqCst) != DESTROYING {
242                let _ = handle_panic(|| {
243                    let _ = Box::<PluginBoxInner<P>>::from_raw(
244                        plugin.plugin_data as *const c_void as *mut _,
245                    );
246                });
247            }
248
249            // This tries to prevent double-free (but not use-after-free) in case destroy() is called twice:
250            // all PluginWrapper methods immediately fail if plugin_data is null,
251            // and we check it is non-null above before deallocating.
252            plugin.plugin_data = core::ptr::null_mut();
253
254            let _ = Box::from_raw(plugin as *mut clap_plugin);
255        }
256    }
257
258    #[allow(clippy::missing_safety_doc)]
259    unsafe extern "C" fn activate(
260        plugin: *const clap_plugin,
261        sample_rate: f64,
262        min_sample_count: u32,
263        max_sample_count: u32,
264    ) -> bool {
265        PluginWrapper::<P>::handle(plugin, |p| {
266            let config = PluginAudioConfiguration {
267                sample_rate,
268                min_frames_count: min_sample_count,
269                max_frames_count: max_sample_count,
270            };
271
272            p.activate(config)
273        })
274        .is_some()
275    }
276
277    #[allow(clippy::missing_safety_doc)]
278    unsafe extern "C" fn deactivate(plugin: *const clap_plugin) {
279        PluginWrapper::<P>::handle(plugin, |p| p.deactivate());
280    }
281
282    #[allow(clippy::missing_safety_doc)]
283    unsafe extern "C" fn reset(plugin: *const clap_plugin) {
284        PluginWrapper::<P>::handle(plugin, |p| {
285            p.audio_processor()?.as_mut().reset();
286            Ok(())
287        });
288    }
289
290    #[allow(clippy::missing_safety_doc)]
291    unsafe extern "C" fn start_processing(plugin: *const clap_plugin) -> bool {
292        PluginWrapper::<P>::handle(plugin, |p| {
293            Ok(p.audio_processor()?.as_mut().start_processing()?)
294        })
295        .is_some()
296    }
297
298    #[allow(clippy::missing_safety_doc)]
299    unsafe extern "C" fn stop_processing(plugin: *const clap_plugin) {
300        PluginWrapper::<P>::handle(plugin, |p| {
301            p.audio_processor()?.as_mut().stop_processing();
302            Ok(())
303        });
304    }
305
306    #[allow(clippy::missing_safety_doc)]
307    unsafe extern "C" fn process(
308        plugin: *const clap_plugin,
309        process: *const clap_process,
310    ) -> clap_process_status {
311        // SAFETY: process ptr is never accessed later, and is guaranteed to be valid and unique by the host
312        PluginWrapper::<P>::handle(plugin, |p| {
313            Ok(p.audio_processor()?.as_mut().process(
314                Process::from_raw(&*process),
315                Audio::from_raw(&*process),
316                Events::from_raw(&*process),
317            )?)
318        })
319        .map(|s| s as clap_process_status)
320        .unwrap_or(CLAP_PROCESS_ERROR)
321    }
322
323    #[allow(clippy::missing_safety_doc)]
324    unsafe extern "C" fn get_extension(
325        plugin: *const clap_plugin,
326        identifier: *const std::os::raw::c_char,
327    ) -> *const c_void {
328        let identifier = CStr::from_ptr(identifier);
329        let mut builder = PluginExtensions::new(identifier);
330
331        PluginWrapper::<P>::handle_plugin_data(plugin, |data| {
332            let p = data.as_ref().wrapper_uninit()?;
333            P::declare_extensions(&mut builder, p.map(|p| p.shared()));
334            Ok(())
335        });
336        builder.found()
337    }
338
339    #[allow(clippy::missing_safety_doc)]
340    unsafe extern "C" fn on_main_thread(plugin: *const clap_plugin) {
341        PluginWrapper::<P>::handle(plugin, |p| {
342            p.main_thread().on_main_thread();
343            Ok(())
344        });
345    }
346}
347
348/// A wrapper around a [`Plugin`] instance.
349///
350/// This type is created with its [`new`](PluginInstance::new) method when the host wants to
351/// instantiate a given plugin type, and is what needs to be returned by the
352/// [`PluginFactory::instantiate_plugin`](crate::factory::plugin::PluginFactoryImpl::create_plugin) method.
353pub struct PluginInstance<'a> {
354    inner: Box<clap_plugin>,
355    lifetime: PhantomData<&'a clap_plugin_descriptor>,
356}
357
358impl<'a> PluginInstance<'a> {
359    #[inline]
360    pub(crate) fn into_owned_ptr(self) -> *mut clap_plugin {
361        ManuallyDrop::new(self).inner.as_mut()
362    }
363
364    /// Instantiates a plugin of a given implementation `P`.
365    ///
366    /// Instantiated plugins also require an [`HostInfo`] instance given by the host, and a
367    /// reference to the associated [`PluginDescriptor`].
368    ///
369    /// See the [`PluginFactory`](crate::factory::plugin::PluginFactoryImpl)'s trait documentation for
370    /// a usage example.
371    pub fn new<P: Plugin>(
372        host_info: HostInfo<'a>,
373        descriptor: &'a PluginDescriptor,
374        shared_initializer: impl FnOnce(HostSharedHandle<'a>) -> Result<P::Shared<'a>, PluginError> + 'a,
375        main_thread_initializer: impl FnOnce(
376            HostMainThreadHandle<'a>,
377            &'a P::Shared<'a>,
378        ) -> Result<P::MainThread<'a>, PluginError>
379        + 'a,
380    ) -> PluginInstance<'a> {
381        // SAFETY: we guarantee that no host_handle methods are called until init() is called
382        let host = unsafe { host_info.to_handle() };
383        Self {
384            inner: Box::new(PluginBoxInner::<P>::get_plugin_desc(
385                host,
386                descriptor.as_raw(),
387                Box::new((shared_initializer, main_thread_initializer))
388                    as Box<dyn PluginInitializer<'a, P>>,
389            )),
390            lifetime: PhantomData,
391        }
392    }
393
394    /// Instantiates a plugin of a given implementation `P` with a custom initializer.
395    ///
396    /// This initializer allows to perform custom work on the main thread at initialization time
397    /// before the plugin is split into its shared and main thread components.
398    ///
399    /// Instantiated plugins also require an [`HostInfo`] instance given by the host, and a
400    /// reference to the associated [`PluginDescriptor`].
401    ///
402    /// # Example
403    ///
404    /// This example shows how to use this API to create a [`PluginShared`] struct and a
405    /// [`PluginMainThread`] struct that each use one end of a [`channel`].
406    ///
407    /// ```
408    /// use std::sync::mpsc::{channel, Receiver, Sender};
409    ///
410    /// use clack_plugin::prelude::*;
411    /// use clack_plugin::entry::prelude::*;
412    ///
413    /// struct MyPlugin;
414    ///
415    /// impl Plugin for MyPlugin {
416    ///     type AudioProcessor<'a> = ();
417    ///     type Shared<'a> = MyPluginShared<'a>;
418    ///     type MainThread<'a> = MyPluginMainThread<'a>;
419    /// }
420    ///
421    /// struct MyPluginShared<'a>(HostSharedHandle<'a>, Sender<u32>);
422    /// struct MyPluginMainThread<'a>(HostMainThreadHandle<'a>, &'a MyPluginShared<'a>, Receiver<u32>);
423    ///
424    /// impl<'a> PluginShared<'a> for MyPluginShared<'a> {}
425    /// impl<'a> PluginMainThread<'a, MyPluginShared<'a>> for MyPluginMainThread<'a> {}
426    ///
427    /// fn create_plugin<'a>(host_info: HostInfo<'a>) -> Result<PluginInstance<'a>, PluginError> {
428    ///     let plugin_descriptor: &'a PluginDescriptor = /* ... */
429    /// #    unreachable!();
430    ///     Ok(PluginInstance::new_with_initializer::<MyPlugin, _>(host_info, plugin_descriptor, |host| {
431    ///         let (tx, rx) = channel();
432    ///
433    ///         Ok((
434    ///             MyPluginShared(host.shared(), tx),
435    ///             move |shared| Ok(MyPluginMainThread(host, shared, rx))
436    ///         ))
437    ///     }))
438    /// }
439    ///
440    /// ```
441    /// [`PluginShared`]: crate::plugin::PluginShared
442    /// [`channel`]: std::sync::mpsc::channel
443    pub fn new_with_initializer<P: Plugin, FM>(
444        host_info: HostInfo<'a>,
445        descriptor: &'a PluginDescriptor,
446        initializer: impl FnOnce(HostMainThreadHandle<'a>) -> Result<(P::Shared<'a>, FM), PluginError>
447        + 'a,
448    ) -> PluginInstance<'a>
449    where
450        FM: 'a + FnOnce(&'a P::Shared<'a>) -> Result<P::MainThread<'a>, PluginError>,
451    {
452        // SAFETY: we guarantee that no host_handle methods are called until init() is called
453        let host = unsafe { host_info.to_handle() };
454        Self {
455            inner: Box::new(PluginBoxInner::<P>::get_plugin_desc(
456                host,
457                descriptor.as_raw(),
458                Box::new(initializer) as Box<dyn PluginInitializer<'a, P>>,
459            )),
460            lifetime: PhantomData,
461        }
462    }
463
464    /// Returns a raw, C FFI-compatible reference to this plugin instance.
465    #[inline]
466    pub fn as_raw(&self) -> &clap_plugin {
467        &self.inner
468    }
469}
470
471// In case the instance is dropped by a faulty plugin factory implementation.
472impl Drop for PluginInstance<'_> {
473    #[inline]
474    fn drop(&mut self) {
475        if let Some(destroy) = self.inner.destroy {
476            // SAFETY: the destroy fn is valid as it's provided by us directly.
477            unsafe { destroy(self.inner.as_ref()) }
478        }
479    }
480}