1
0
Fork 0

Add an is_gui_thread flag to MainThreadExecutor

We'll also use the EventLoop for running background tasks completely
decoupled from the GUI.
This commit is contained in:
Robbert van der Helm 2022-10-23 15:09:21 +02:00
parent 967426453a
commit 4524719128
7 changed files with 28 additions and 31 deletions

View file

@ -55,11 +55,7 @@ where
/// Something that can execute tasks of type `T`. /// Something that can execute tasks of type `T`.
pub(crate) trait MainThreadExecutor<T>: Send + Sync { pub(crate) trait MainThreadExecutor<T>: Send + Sync {
/// Execute a task on the current thread. This should only be called from the main thread. /// Execute a task on the current thread. This is either called from the GUI thread or from
/// /// another background thread, depending on how the task was scheduled in the [`EventContext`].
/// # Safety fn execute(&self, task: T, is_gui_thread: bool);
///
/// This is not actually unsafe in the typical Rust sense. But the implemnting function will
/// assume (and can only assume) that this is called from the main thread.
unsafe fn execute(&self, task: T);
} }

View file

@ -66,7 +66,7 @@ where
fn do_maybe_async(&self, task: T) -> bool { fn do_maybe_async(&self, task: T) -> bool {
if self.is_main_thread() { if self.is_main_thread() {
unsafe { self.executor.execute(task) }; self.executor.execute(task, true);
true true
} else { } else {
self.worker_thread_channel self.worker_thread_channel
@ -103,7 +103,7 @@ where
loop { loop {
match receiver.recv() { match receiver.recv() {
Ok(Message::Task(task)) => match executor.upgrade() { Ok(Message::Task(task)) => match executor.upgrade() {
Some(e) => unsafe { e.execute(task) }, Some(e) => e.execute(task, true),
None => { None => {
nih_trace!( nih_trace!(
"Received a new task but the executor is no longer alive, shutting down \ "Received a new task but the executor is no longer alive, shutting down \

View file

@ -97,7 +97,7 @@ where
}; };
while let Some(task) = tasks.pop() { while let Some(task) = tasks.pop() {
unsafe { executor.execute(task) }; executor.execute(task, true);
} }
}) })
}; };
@ -134,7 +134,7 @@ where
fn do_maybe_async(&self, task: T) -> bool { fn do_maybe_async(&self, task: T) -> bool {
if self.is_main_thread() { if self.is_main_thread() {
unsafe { e.execute(task) }; self.executor.execute(task, true);
true true
} else { } else {
let success = self.tasks.push(task).is_ok(); let success = self.tasks.push(task).is_ok();

View file

@ -322,7 +322,7 @@ impl<P: ClapPlugin> EventLoop<Task<P>, Wrapper<P>> for Wrapper<P> {
fn do_maybe_async(&self, task: Task<P>) -> bool { fn do_maybe_async(&self, task: Task<P>) -> bool {
if self.is_main_thread() { if self.is_main_thread() {
unsafe { self.execute(task) }; self.execute(task, true);
true true
} else { } else {
let success = self.tasks.push(task).is_ok(); let success = self.tasks.push(task).is_ok();
@ -351,33 +351,37 @@ impl<P: ClapPlugin> EventLoop<Task<P>, Wrapper<P>> for Wrapper<P> {
} }
impl<P: ClapPlugin> MainThreadExecutor<Task<P>> for Wrapper<P> { impl<P: ClapPlugin> MainThreadExecutor<Task<P>> for Wrapper<P> {
unsafe fn execute(&self, task: Task<P>) { fn execute(&self, task: Task<P>, is_gui_thread: bool) {
// This function is always called from the main thread, from [Self::on_main_thread]. // This function is always called from the main thread, from [Self::on_main_thread].
match task { match task {
Task::PluginTask(task) => (self.task_executor.lock())(task), Task::PluginTask(task) => (self.task_executor.lock())(task),
Task::LatencyChanged => match &*self.host_latency.borrow() { Task::LatencyChanged => match &*self.host_latency.borrow() {
Some(host_latency) => { Some(host_latency) => {
nih_debug_assert!(is_gui_thread);
// XXX: The CLAP docs mention that you should request a restart if this happens // XXX: The CLAP docs mention that you should request a restart if this happens
// while the plugin is activated (which is not entirely the same thing as // while the plugin is activated (which is not entirely the same thing as
// is processing, but we'll treat it as the same thing). In practice just // is processing, but we'll treat it as the same thing). In practice just
// calling the latency changed function also seems to work just fine. // calling the latency changed function also seems to work just fine.
if self.is_processing.load(Ordering::SeqCst) { if self.is_processing.load(Ordering::SeqCst) {
clap_call! { &*self.host_callback=>request_restart(&*self.host_callback) }; unsafe_clap_call! { &*self.host_callback=>request_restart(&*self.host_callback) };
} else { } else {
clap_call! { host_latency=>changed(&*self.host_callback) }; unsafe_clap_call! { host_latency=>changed(&*self.host_callback) };
} }
} }
None => nih_debug_assert_failure!("Host does not support the latency extension"), None => nih_debug_assert_failure!("Host does not support the latency extension"),
}, },
Task::VoiceInfoChanged => match &*self.host_voice_info.borrow() { Task::VoiceInfoChanged => match &*self.host_voice_info.borrow() {
Some(host_voice_info) => { Some(host_voice_info) => {
clap_call! { host_voice_info=>changed(&*self.host_callback) }; nih_debug_assert!(is_gui_thread);
unsafe_clap_call! { host_voice_info=>changed(&*self.host_callback) };
} }
None => nih_debug_assert_failure!("Host does not support the voice-info extension"), None => nih_debug_assert_failure!("Host does not support the voice-info extension"),
}, },
Task::RescanParamValues => match &*self.host_params.borrow() { Task::RescanParamValues => match &*self.host_params.borrow() {
Some(host_params) => { Some(host_params) => {
clap_call! { host_params=>rescan(&*self.host_callback, CLAP_PARAM_RESCAN_VALUES) }; nih_debug_assert!(is_gui_thread);
unsafe_clap_call! { host_params=>rescan(&*self.host_callback, CLAP_PARAM_RESCAN_VALUES) };
} }
None => nih_debug_assert_failure!("The host does not support parameters? What?"), None => nih_debug_assert_failure!("The host does not support parameters? What?"),
}, },
@ -2352,7 +2356,7 @@ impl<P: ClapPlugin> Wrapper<P> {
// [Self::do_maybe_async] posts a task to the queue and asks the host to call this function // [Self::do_maybe_async] posts a task to the queue and asks the host to call this function
// on the main thread, so once that's done we can just handle all requests here // on the main thread, so once that's done we can just handle all requests here
while let Some(task) = wrapper.tasks.pop() { while let Some(task) = wrapper.tasks.pop() {
wrapper.execute(task); wrapper.execute(task, true);
} }
} }

View file

@ -146,7 +146,7 @@ pub struct TaskExecutorWrapper<P: Plugin> {
} }
impl<P: Plugin> MainThreadExecutor<P::BackgroundTask> for TaskExecutorWrapper<P> { impl<P: Plugin> MainThreadExecutor<P::BackgroundTask> for TaskExecutorWrapper<P> {
unsafe fn execute(&self, task: P::BackgroundTask) { fn execute(&self, task: P::BackgroundTask, _is_gui_thread: bool) {
(self.task_executor.lock())(task) (self.task_executor.lock())(task)
} }
} }

View file

@ -377,7 +377,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
let event_loop = self.event_loop.borrow(); let event_loop = self.event_loop.borrow();
let event_loop = event_loop.as_ref().unwrap(); let event_loop = event_loop.as_ref().unwrap();
if event_loop.is_main_thread() { if event_loop.is_main_thread() {
unsafe { self.execute(task) }; self.execute(task, true);
true true
} else { } else {
// If the editor is open, and the host exposes the `IRunLoop` interface, then we'll run // If the editor is open, and the host exposes the `IRunLoop` interface, then we'll run
@ -535,25 +535,22 @@ impl<P: Vst3Plugin> WrapperInner<P> {
} }
impl<P: Vst3Plugin> MainThreadExecutor<Task<P>> for WrapperInner<P> { impl<P: Vst3Plugin> MainThreadExecutor<Task<P>> for WrapperInner<P> {
unsafe fn execute(&self, task: Task<P>) { fn execute(&self, task: Task<P>, is_gui_thread: bool) {
// This function is always called from the main thread // This function is always called from the main thread
// TODO: When we add GUI resizing and context menus, this should propagate those events to
// `IRunLoop` on Linux to keep REAPER happy. That does mean a double spool, but we can
// come up with a nicer solution to handle that later (can always add a separate
// function for checking if a to be scheduled task can be handled right there and
// then).
match task { match task {
Task::PluginTask(task) => (self.task_executor.lock())(task), Task::PluginTask(task) => (self.task_executor.lock())(task),
Task::TriggerRestart(flags) => match &*self.component_handler.borrow() { Task::TriggerRestart(flags) => match &*self.component_handler.borrow() {
Some(handler) => { Some(handler) => unsafe {
nih_debug_assert!(is_gui_thread);
handler.restart_component(flags); handler.restart_component(flags);
} },
None => nih_debug_assert_failure!("Component handler not yet set"), None => nih_debug_assert_failure!("Component handler not yet set"),
}, },
Task::RequestResize => match &*self.plug_view.read() { Task::RequestResize => match &*self.plug_view.read() {
Some(plug_view) => { Some(plug_view) => unsafe {
nih_debug_assert!(is_gui_thread);
plug_view.request_resize(); plug_view.request_resize();
} },
None => nih_debug_assert_failure!("Can't resize a closed editor"), None => nih_debug_assert_failure!("Can't resize a closed editor"),
}, },
} }

View file

@ -486,7 +486,7 @@ impl<P: Vst3Plugin> IEventHandler for RunLoopEventHandler<P> {
// This gets called from the host's UI thread because we wrote some bytes to the Unix domain // This gets called from the host's UI thread because we wrote some bytes to the Unix domain
// socket. We'll read that data from the socket again just to make REAPER happy. // socket. We'll read that data from the socket again just to make REAPER happy.
while let Some(task) = self.tasks.pop() { while let Some(task) = self.tasks.pop() {
self.inner.execute(task); self.inner.execute(task, true);
let mut notify_value = 1i8; let mut notify_value = 1i8;
const NOTIFY_VALUE_SIZE: usize = std::mem::size_of::<i8>(); const NOTIFY_VALUE_SIZE: usize = std::mem::size_of::<i8>();