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`.
pub(crate) trait MainThreadExecutor<T>: Send + Sync {
/// Execute a task on the current thread. This should only be called from the main thread.
///
/// # Safety
///
/// 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);
/// 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`].
fn execute(&self, task: T, is_gui_thread: bool);
}

View file

@ -66,7 +66,7 @@ where
fn do_maybe_async(&self, task: T) -> bool {
if self.is_main_thread() {
unsafe { self.executor.execute(task) };
self.executor.execute(task, true);
true
} else {
self.worker_thread_channel
@ -103,7 +103,7 @@ where
loop {
match receiver.recv() {
Ok(Message::Task(task)) => match executor.upgrade() {
Some(e) => unsafe { e.execute(task) },
Some(e) => e.execute(task, true),
None => {
nih_trace!(
"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() {
unsafe { executor.execute(task) };
executor.execute(task, true);
}
})
};
@ -134,7 +134,7 @@ where
fn do_maybe_async(&self, task: T) -> bool {
if self.is_main_thread() {
unsafe { e.execute(task) };
self.executor.execute(task, true);
true
} else {
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 {
if self.is_main_thread() {
unsafe { self.execute(task) };
self.execute(task, true);
true
} else {
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> {
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].
match task {
Task::PluginTask(task) => (self.task_executor.lock())(task),
Task::LatencyChanged => match &*self.host_latency.borrow() {
Some(host_latency) => {
nih_debug_assert!(is_gui_thread);
// 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
// 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.
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 {
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"),
},
Task::VoiceInfoChanged => match &*self.host_voice_info.borrow() {
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"),
},
Task::RescanParamValues => match &*self.host_params.borrow() {
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?"),
},
@ -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
// on the main thread, so once that's done we can just handle all requests here
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> {
unsafe fn execute(&self, task: P::BackgroundTask) {
fn execute(&self, task: P::BackgroundTask, _is_gui_thread: bool) {
(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 = event_loop.as_ref().unwrap();
if event_loop.is_main_thread() {
unsafe { self.execute(task) };
self.execute(task, true);
true
} else {
// 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> {
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
// 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 {
Task::PluginTask(task) => (self.task_executor.lock())(task),
Task::TriggerRestart(flags) => match &*self.component_handler.borrow() {
Some(handler) => {
Some(handler) => unsafe {
nih_debug_assert!(is_gui_thread);
handler.restart_component(flags);
}
},
None => nih_debug_assert_failure!("Component handler not yet set"),
},
Task::RequestResize => match &*self.plug_view.read() {
Some(plug_view) => {
Some(plug_view) => unsafe {
nih_debug_assert!(is_gui_thread);
plug_view.request_resize();
}
},
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
// socket. We'll read that data from the socket again just to make REAPER happy.
while let Some(task) = self.tasks.pop() {
self.inner.execute(task);
self.inner.execute(task, true);
let mut notify_value = 1i8;
const NOTIFY_VALUE_SIZE: usize = std::mem::size_of::<i8>();