diff --git a/src/wrapper/vst3/context.rs b/src/wrapper/vst3/context.rs index 560c8937..5ad1bacf 100644 --- a/src/wrapper/vst3/context.rs +++ b/src/wrapper/vst3/context.rs @@ -6,7 +6,6 @@ use vst3_sys::vst::{IComponentHandler, RestartFlags}; use super::inner::{Task, WrapperInner}; use crate::context::{GuiContext, ProcessContext, Transport}; -use crate::event_loop::EventLoop; use crate::midi::NoteEvent; use crate::param::internals::ParamPtr; use crate::plugin::Vst3Plugin; @@ -117,7 +116,8 @@ impl ProcessContext for WrapperProcessContext<'_, P> { // Only trigger a restart if it's actually needed let old_latency = self.inner.current_latency.swap(samples, Ordering::SeqCst); if old_latency != samples { - let task_posted = unsafe { self.inner.event_loop.borrow().assume_init_ref() } + let task_posted = self + .inner .do_maybe_async(Task::TriggerRestart(RestartFlags::kLatencyChanged as i32)); nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); } diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index c2e112a5..17048e51 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -49,7 +49,9 @@ pub(crate) struct WrapperInner { pub plug_view: RwLock>>>, /// A realtime-safe task queue so the plugin can schedule tasks that need to be run later on the - /// GUI thread. + /// GUI thread. This field should not be used directly for posting tasks. This should be done + /// through [`Self::do_maybe_async()`] instead. That method posts the task to the host's + /// `IRunLoop` instead of it's available. /// /// This RwLock is only needed because it has to be initialized late. There is no reason to /// mutably borrow the event loop, so reads will never be contested. @@ -312,6 +314,34 @@ impl WrapperInner

{ } } + /// Either posts the function to the task queue using [`EventLoop::do_maybe_async()`] 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 + /// + /// If the task queue is full, then this will return false. + #[must_use] + pub fn do_maybe_async(&self, task: Task) -> bool { + let event_loop = self.event_loop.borrow(); + let event_loop = unsafe { event_loop.assume_init_ref() }; + if event_loop.is_main_thread() { + unsafe { self.execute(task) }; + true + } else { + // If the editor is open, and the host exposes the `IRunLoop` interface, then we'll run + // the task on the host's GUI thread using that interface. Otherwise we'll use the + // regular eent loop. If the editor gets dropped while there's still outstanding work + // left in the run loop task queue, then those tasks will be posted to the regular event + // loop so no work is lost. + match &*self.plug_view.read() { + Some(plug_view) => match plug_view.do_maybe_in_run_loop(task) { + Ok(()) => true, + Err(task) => event_loop.do_maybe_async(task), + }, + None => event_loop.do_maybe_async(task), + } + } + } + /// If there's an editor open, let it know that parameter values have changed. This should be /// called whenever there's been a call or multiple calls to /// [`set_normalized_value_by_hash()[Self::set_normalized_value_by_hash()`].