diff --git a/src/wrapper/clap/plugin.rs b/src/wrapper/clap/plugin.rs index 26d7ebe8..f0042e37 100644 --- a/src/wrapper/clap/plugin.rs +++ b/src/wrapper/clap/plugin.rs @@ -2,16 +2,18 @@ use clap_sys::host::clap_host; use clap_sys::plugin::clap_plugin; use clap_sys::process::{clap_process, clap_process_status}; use crossbeam::atomic::AtomicCell; +use crossbeam::queue::ArrayQueue; use parking_lot::RwLock; use std::collections::VecDeque; use std::ffi::c_void; use std::os::raw::c_char; use std::ptr; use std::sync::atomic::AtomicU32; +use std::thread::{self, ThreadId}; use super::context::WrapperProcessContext; use super::descriptor::PluginDescriptor; -use crate::event_loop::{EventLoop, MainThreadExecutor}; +use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY}; use crate::plugin::{BufferConfig, BusConfig, ClapPlugin}; use crate::NoteEvent; @@ -45,6 +47,18 @@ pub struct Plugin { /// Needs to be boxed because the plugin object is supposed to contain a static reference to /// this. plugin_descriptor: Box>, + + /// A queue of tasks that still need to be performed. Because CLAP lets the plugin request a + /// host callback directly, we don't need to use the OsEventLoop we use in our other plugin + /// implementations. Instead, we'll post tasks to this queue, ask the host to call + /// [Self::on_main_thread] on the main thread, and then continue to pop tasks off this queue + /// there until it is empty. + tasks: ArrayQueue, + /// The ID of the main thread. In practice this is the ID of the thread that created this + /// object. + /// + /// TODO: If the host supports the ThreadCheck extension, we should use that instead. + main_thread_id: ThreadId, } /// Send+Sync wrapper around clap_host. @@ -62,22 +76,35 @@ pub enum Task { /// Because CLAP has this [clap_host::request_host_callback()] function, we don't need to use /// `OsEventLoop` and can instead just request a main thread callback directly. impl EventLoop> for Plugin

{ - fn new_and_spawn(executor: std::sync::Weak) -> Self { + fn new_and_spawn(_executor: std::sync::Weak) -> Self { panic!("What are you doing"); } fn do_maybe_async(&self, task: Task) -> bool { - todo!() + if self.is_main_thread() { + unsafe { self.execute(task) }; + true + } else { + let success = self.tasks.push(task).is_ok(); + if success { + // CLAP lets us use the host's event loop instead of having to implement our own + let host = self.host_callback.0; + unsafe { ((*host).request_callback)(host) }; + } + + success + } } fn is_main_thread(&self) -> bool { - todo!() + // TODO: Use the `thread_check::is_main_thread` extension method if that's available + thread::current().id() == self.main_thread_id } } impl MainThreadExecutor for Plugin

{ unsafe fn execute(&self, task: Task) { - todo!() + todo!("Implement latency changes for CLAP") } } @@ -120,6 +147,9 @@ impl Plugin

{ host_callback: HostCallback(host_callback), plugin_descriptor, + + tasks: ArrayQueue::new(TASK_QUEUE_CAPACITY), + main_thread_id: thread::current().id(), } } @@ -142,7 +172,7 @@ impl Plugin

{ unsafe extern "C" fn activate( plugin: *const clap_plugin, sample_rate: f64, - min_frames_count: u32, + _min_frames_count: u32, max_frames_count: u32, ) -> bool { let plugin = &*(plugin as *const Self); @@ -199,6 +229,12 @@ impl Plugin

{ } unsafe extern "C" fn on_main_thread(plugin: *const clap_plugin) { - todo!(); + let plugin = &*(plugin as *const Self); + + // [Self::do_maybe_async] posts a task to the queue and asks the host to call this function + // on the main thread, so once that's done we can just handle all requests here + while let Some(task) = plugin.tasks.pop() { + plugin.execute(task); + } } }