From 0dd3bfe4e7e4ee556b692ccec0a34b0152aae85e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 23 Oct 2022 16:23:20 +0200 Subject: [PATCH] Allow running tasks on a dedicated thread Independent of the GUI thread, which is also still an option. This is useful for long running IO jobs that might otherwise block the GUI for long enough to be noticeable. --- src/context/gui.rs | 21 ++++++++++++++++++--- src/context/process.rs | 11 +++++++++++ src/wrapper/clap/context.rs | 5 +++++ src/wrapper/clap/wrapper.rs | 10 +++++++++- src/wrapper/standalone/context.rs | 5 +++++ src/wrapper/standalone/wrapper.rs | 10 +++++++++- src/wrapper/vst3/context.rs | 5 +++++ src/wrapper/vst3/inner.rs | 27 +++++++++++++++++++++++---- 8 files changed, 85 insertions(+), 9 deletions(-) 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]