Add a schedule_background() EventLoop method
This commit is contained in:
parent
520eba71ca
commit
028aeed18e
4 changed files with 51 additions and 7 deletions
|
@ -24,10 +24,14 @@ pub(crate) use self::windows::WindowsEventLoop as OsEventLoop;
|
|||
|
||||
pub(crate) const TASK_QUEUE_CAPACITY: usize = 512;
|
||||
|
||||
/// A trait describing the functionality of the platform-specific event loop that can execute tasks
|
||||
/// of type `T` in executor `E`. Posting a task to the internal task queue should be realtime-safe.
|
||||
/// This event loop should be created during the wrapper's initial initialization on the main
|
||||
/// thread.
|
||||
/// A trait describing the functionality of a platform-specific event loop that can execute tasks of
|
||||
/// type `T` in executor `E` on the operating system's main thread (if applicable). Posting a task
|
||||
/// to the internal task queue should be realtime-safe. This event loop should be created during the
|
||||
/// wrapper's initial initialization on the main thread.
|
||||
///
|
||||
/// Additionally, this trait also allows posting tasks to a background thread that's completely
|
||||
/// detached from the GUI. This makes it possible for a plugin to execute long running jobs without
|
||||
/// blocking GUI rendering.
|
||||
///
|
||||
/// This is never used generically, but having this as a trait will cause any missing functions on
|
||||
/// an implementation to show up as compiler errors even when using a different platform. And since
|
||||
|
@ -52,6 +56,14 @@ where
|
|||
#[must_use]
|
||||
fn schedule_gui(&self, task: T) -> bool;
|
||||
|
||||
/// Post a task to the background task queue so it can be run in a dedicated background thread
|
||||
/// without blocking the plugin's GUI. This function needs to be callable at any time without
|
||||
/// blocking.
|
||||
///
|
||||
/// If the task queue is full, then this will return false.
|
||||
#[must_use]
|
||||
fn schedule_background(&self, task: T) -> bool;
|
||||
|
||||
/// Whether the calling thread is the event loop's main thread. This is usually the thread the
|
||||
/// event loop instance was initialized on.
|
||||
fn is_main_thread(&self) -> bool;
|
||||
|
|
|
@ -73,6 +73,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn schedule_background(&self, task: T) -> bool {
|
||||
// This event loop implementation already uses a thread that's completely decoupled from the
|
||||
// operating system's or the host's main thread, so we don't need _another_ thread here
|
||||
self.tasks_sender.try_send(Message::Task(task)).is_ok()
|
||||
}
|
||||
|
||||
fn is_main_thread(&self) -> bool {
|
||||
// FIXME: `thread::current()` may allocate the first time it's called, is there a safe
|
||||
// non-allocating version of this without using huge OS-specific libraries?
|
||||
|
|
|
@ -17,7 +17,7 @@ use windows::Win32::UI::WindowsAndMessaging::{
|
|||
WINDOW_EX_STYLE, WINDOW_STYLE, WM_CREATE, WM_DESTROY, WM_USER, WNDCLASSEXA,
|
||||
};
|
||||
|
||||
use super::{EventLoop, MainThreadExecutor};
|
||||
use super::{BackgroundThread, EventLoop, MainThreadExecutor};
|
||||
use crate::util::permit_alloc;
|
||||
|
||||
/// The custom message ID for our notify event. If the hidden event loop window receives this, then
|
||||
|
@ -52,6 +52,10 @@ pub(crate) struct WindowsEventLoop<T, E> {
|
|||
/// we'll wake up the window, which then continues to pop tasks off this queue until it is
|
||||
/// empty.
|
||||
tasks_sender: channel::Sender<T>,
|
||||
|
||||
/// A background thread for running tasks independently from the host's GUI thread. Useful for
|
||||
/// longer, blocking tasks.
|
||||
background_thread: BackgroundThread<T>,
|
||||
}
|
||||
|
||||
impl<T, E> EventLoop<T, E> for WindowsEventLoop<T, E>
|
||||
|
@ -122,11 +126,12 @@ where
|
|||
assert!(!window.is_invalid());
|
||||
|
||||
Self {
|
||||
executor,
|
||||
executor: executor.clone(),
|
||||
main_thread_id: thread::current().id(),
|
||||
message_window: window,
|
||||
message_window_class_name: class_name,
|
||||
tasks_sender,
|
||||
background_thread: BackgroundThread::new_and_spawn(executor),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,6 +153,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn schedule_background(&self, task: T) -> bool {
|
||||
self.background_thread.schedule(task)
|
||||
}
|
||||
|
||||
fn is_main_thread(&self) -> bool {
|
||||
// FIXME: `thread::current()` may allocate the first time it's called, is there a safe
|
||||
// non-allocating version of this without using huge OS-specific libraries?
|
||||
|
|
|
@ -80,7 +80,7 @@ use crate::buffer::Buffer;
|
|||
use crate::context::gui::AsyncExecutor;
|
||||
use crate::context::process::Transport;
|
||||
use crate::editor::{Editor, ParentWindowHandle};
|
||||
use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY};
|
||||
use crate::event_loop::{BackgroundThread, EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY};
|
||||
use crate::midi::{MidiConfig, NoteEvent};
|
||||
use crate::params::internals::ParamPtr;
|
||||
use crate::params::{ParamFlags, Params};
|
||||
|
@ -265,6 +265,9 @@ pub struct Wrapper<P: ClapPlugin> {
|
|||
/// [`host_thread_check`][Self::host_thread_check] thus contains a value), then that extension
|
||||
/// is used instead.
|
||||
main_thread_id: ThreadId,
|
||||
/// A background thread for running tasks independently from the host'main GUI thread. Useful
|
||||
/// for longer, blocking tasks. Initialized later as it needs a reference to the wrapper.
|
||||
background_thread: AtomicRefCell<Option<BackgroundThread<Task<P>>>>,
|
||||
}
|
||||
|
||||
/// Tasks that can be sent from the plugin to be executed on the main thread in a non-blocking
|
||||
|
@ -336,6 +339,14 @@ impl<P: ClapPlugin> EventLoop<Task<P>, Wrapper<P>> for Wrapper<P> {
|
|||
}
|
||||
}
|
||||
|
||||
fn schedule_background(&self, task: Task<P>) -> bool {
|
||||
self.background_thread
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.schedule(task)
|
||||
}
|
||||
|
||||
fn is_main_thread(&self) -> bool {
|
||||
// If the host supports the thread check interface then we'll use that, otherwise we'll
|
||||
// check if this is the same thread as the one that created the plugin instance.
|
||||
|
@ -676,6 +687,8 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
|
||||
tasks: ArrayQueue::new(TASK_QUEUE_CAPACITY),
|
||||
main_thread_id: thread::current().id(),
|
||||
// Initialized later as it needs a reference to the wrapper for the executor
|
||||
background_thread: AtomicRefCell::new(None),
|
||||
};
|
||||
|
||||
// Finally, the wrapper needs to contain a reference to itself so we can create GuiContexts
|
||||
|
@ -699,6 +712,10 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
})
|
||||
.map(Mutex::new);
|
||||
|
||||
// Same with the background thread
|
||||
*wrapper.background_thread.borrow_mut() =
|
||||
Some(BackgroundThread::new_and_spawn(wrapper.clone()));
|
||||
|
||||
wrapper
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue