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:
parent
028aeed18e
commit
0dd3bfe4e7
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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...");
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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...");
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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...");
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in a new issue