1
0
Fork 0

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.
This commit is contained in:
Robbert van der Helm 2022-10-23 16:23:20 +02:00
parent 028aeed18e
commit 0dd3bfe4e7
8 changed files with 85 additions and 9 deletions

View file

@ -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<P: Plugin> {
pub(crate) inner: Arc<dyn Fn(P::BackgroundTask) + Send + Sync>,
pub(crate) execute_background: Arc<dyn Fn(P::BackgroundTask) + Send + Sync>,
pub(crate) execute_gui: Arc<dyn Fn(P::BackgroundTask) + Send + Sync>,
}
// Can't derive this since Rust then requires `P` to also be `Clone`able
impl<P: Plugin> Clone for AsyncExecutor<P> {
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<P: Plugin> AsyncExecutor<P> {
/// 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<P: Plugin> AsyncExecutor<P> {
/// 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);
}
}

View file

@ -17,6 +17,17 @@ pub trait ProcessContext<P: Plugin> {
/// 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.
///

View file

@ -59,6 +59,11 @@ impl<P: ClapPlugin> ProcessContext<P> 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...");

View file

@ -701,7 +701,15 @@ impl<P: ClapPlugin> Wrapper<P> {
.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| {

View file

@ -67,6 +67,11 @@ impl<P: Plugin, B: Backend> ProcessContext<P> 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...");

View file

@ -242,7 +242,15 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
.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| {

View file

@ -61,6 +61,11 @@ impl<P: Vst3Plugin> ProcessContext<P> 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...");

View file

@ -336,7 +336,15 @@ impl<P: Vst3Plugin> WrapperInner<P> {
.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<P: Vst3Plugin> WrapperInner<P> {
}
}
/// 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<P>) -> 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]