diff --git a/src/context/gui.rs b/src/context/gui.rs index 6febbd77..c1491646 100644 --- a/src/context/gui.rs +++ b/src/context/gui.rs @@ -84,14 +84,16 @@ pub trait GuiContext: Send + Sync + 'static { // NOTE: This is separate from `GuiContext` because adding a type parameter there would clutter up a // lot of structs, and may even be incompatible with the way certain GUI libraries work. pub struct AsyncExecutor { - pub(crate) inner: Arc, + pub(crate) execute_background: Arc, + pub(crate) execute_gui: Arc, } // Can't derive this since Rust then requires `P` to also be `Clone`able impl Clone for AsyncExecutor

{ fn clone(&self) -> Self { Self { - inner: self.inner.clone(), + execute_background: self.execute_background.clone(), + execute_gui: self.execute_gui.clone(), } } } @@ -104,6 +106,19 @@ pub struct ParamSetter<'a> { } impl AsyncExecutor

{ + /// Run a task from a background thread. This allows you to defer expensive tasks for later + /// without blocking either the process function or the GUI thread. As long as creating the + /// `task` is realtime-safe, this operation is too. + /// + /// # Note + /// + /// Scheduling the same task multiple times will cause those duplicate tasks to pile up. Try to + /// either prevent this from happening, or check whether the task still needs to be completed in + /// your task executor. + pub fn execute_background(&self, task: P::BackgroundTask) { + (self.execute_background)(task); + } + /// Run a task from the plugin's GUI thread. /// /// # Note @@ -112,7 +127,7 @@ impl AsyncExecutor

{ /// either prevent this from happening, or check whether the task still needs to be completed in /// your task executor. pub fn execute_gui(&self, task: P::BackgroundTask) { - (self.inner)(task); + (self.execute_gui)(task); } } diff --git a/src/context/process.rs b/src/context/process.rs index 1ca1c69f..7314f4ee 100644 --- a/src/context/process.rs +++ b/src/context/process.rs @@ -17,6 +17,17 @@ pub trait ProcessContext { /// Get the current plugin API. fn plugin_api(&self) -> PluginApi; + /// Run a task from a background thread. This allows you to defer expensive tasks for later + /// without blocking either the process function or the GUI thread. As long as creating the + /// `task` is realtime-safe, this operation is too. + /// + /// # Note + /// + /// Scheduling the same task multiple times will cause those duplicate tasks to pile up. Try to + /// either prevent this from happening, or check whether the task still needs to be completed in + /// your task executor. + fn execute_background(&self, task: P::BackgroundTask); + /// Run a task from the plugin's GUI thread. As long as creating the `task` is realtime-safe, /// this operation is too. /// diff --git a/src/wrapper/clap/context.rs b/src/wrapper/clap/context.rs index 7c176484..67e182f4 100644 --- a/src/wrapper/clap/context.rs +++ b/src/wrapper/clap/context.rs @@ -59,6 +59,11 @@ impl ProcessContext

for WrapperProcessContext<'_, P> { PluginApi::Clap } + fn execute_background(&self, task: P::BackgroundTask) { + let task_posted = self.wrapper.schedule_background(Task::PluginTask(task)); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + fn execute_gui(&self, task: P::BackgroundTask) { let task_posted = self.wrapper.schedule_gui(Task::PluginTask(task)); nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index 518c22af..c0038fb1 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -701,7 +701,15 @@ impl Wrapper

{ .plugin .lock() .editor(AsyncExecutor { - inner: Arc::new({ + execute_background: Arc::new({ + let wrapper = wrapper.clone(); + + move |task| { + let task_posted = wrapper.schedule_background(Task::PluginTask(task)); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + }), + execute_gui: Arc::new({ let wrapper = wrapper.clone(); move |task| { diff --git a/src/wrapper/standalone/context.rs b/src/wrapper/standalone/context.rs index 3d216006..72ed7a46 100644 --- a/src/wrapper/standalone/context.rs +++ b/src/wrapper/standalone/context.rs @@ -67,6 +67,11 @@ impl ProcessContext

for WrapperProcessContext<'_, P, B PluginApi::Standalone } + fn execute_background(&self, task: P::BackgroundTask) { + let task_posted = self.wrapper.event_loop.schedule_background(task); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + fn execute_gui(&self, task: P::BackgroundTask) { let task_posted = self.wrapper.event_loop.schedule_gui(task); nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); diff --git a/src/wrapper/standalone/wrapper.rs b/src/wrapper/standalone/wrapper.rs index ca7cc44c..1eed7a9d 100644 --- a/src/wrapper/standalone/wrapper.rs +++ b/src/wrapper/standalone/wrapper.rs @@ -242,7 +242,15 @@ impl Wrapper { .plugin .lock() .editor(AsyncExecutor { - inner: Arc::new({ + execute_background: Arc::new({ + let wrapper = wrapper.clone(); + + move |task| { + let task_posted = wrapper.event_loop.schedule_background(task); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + }), + execute_gui: Arc::new({ let wrapper = wrapper.clone(); move |task| { diff --git a/src/wrapper/vst3/context.rs b/src/wrapper/vst3/context.rs index 3f1e7859..e42cab99 100644 --- a/src/wrapper/vst3/context.rs +++ b/src/wrapper/vst3/context.rs @@ -61,6 +61,11 @@ impl ProcessContext

for WrapperProcessContext<'_, P> { PluginApi::Vst3 } + fn execute_background(&self, task: P::BackgroundTask) { + let task_posted = self.inner.schedule_background(Task::PluginTask(task)); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + fn execute_gui(&self, task: P::BackgroundTask) { let task_posted = self.inner.schedule_gui(Task::PluginTask(task)); nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index cb05c5a4..e0df47db 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -336,7 +336,15 @@ impl WrapperInner

{ .plugin .lock() .editor(AsyncExecutor { - inner: Arc::new({ + execute_background: Arc::new({ + let wrapper = wrapper.clone(); + + move |task| { + let task_posted = wrapper.schedule_background(Task::PluginTask(task)); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + }), + execute_gui: Arc::new({ let wrapper = wrapper.clone(); move |task| { @@ -367,9 +375,20 @@ impl WrapperInner

{ } } - /// Either posts the function to the task queue using [`EventLoop::schedule_gui()`] so it can - /// be delegated to the main thread, executes the task directly if this is the main thread, or - /// runs the task on the host's `IRunLoop` if the GUI is open and it exposes one. This function + /// Posts the task to the background task queue using [`EventLoop::schedule_background()`] so it + /// can be run in the background without blocking either the GUI or the audio thread. + /// + /// If the task queue is full, then this will return false. + #[must_use] + pub fn schedule_background(&self, task: Task

) -> bool { + let event_loop = self.event_loop.borrow(); + let event_loop = event_loop.as_ref().unwrap(); + event_loop.schedule_background(task) + } + + /// Either posts the task to the task queue using [`EventLoop::schedule_gui()`] so it can be + /// delegated to the main thread, executes the task directly if this is the main thread, or runs + /// the task on the host's `IRunLoop` if the GUI is open and it exposes one. /// /// If the task queue is full, then this will return false. #[must_use]