diff --git a/src/event_loop.rs b/src/event_loop.rs index 60126765..0d786242 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -55,11 +55,7 @@ where /// Something that can execute tasks of type `T`. pub(crate) trait MainThreadExecutor: 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); } diff --git a/src/event_loop/linux.rs b/src/event_loop/linux.rs index bd4c8a7b..5de51f63 100644 --- a/src/event_loop/linux.rs +++ b/src/event_loop/linux.rs @@ -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 \ diff --git a/src/event_loop/windows.rs b/src/event_loop/windows.rs index d61894e4..19973234 100644 --- a/src/event_loop/windows.rs +++ b/src/event_loop/windows.rs @@ -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(); diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index 9e6de961..297d3587 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -322,7 +322,7 @@ impl EventLoop, Wrapper

> for Wrapper

{ fn do_maybe_async(&self, task: Task

) -> 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 EventLoop, Wrapper

> for Wrapper

{ } impl MainThreadExecutor> for Wrapper

{ - unsafe fn execute(&self, task: Task

) { + fn execute(&self, task: Task

, 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 Wrapper

{ // [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); } } diff --git a/src/wrapper/standalone/wrapper.rs b/src/wrapper/standalone/wrapper.rs index 5d76324d..f17cafd0 100644 --- a/src/wrapper/standalone/wrapper.rs +++ b/src/wrapper/standalone/wrapper.rs @@ -146,7 +146,7 @@ pub struct TaskExecutorWrapper { } impl MainThreadExecutor for TaskExecutorWrapper

{ - unsafe fn execute(&self, task: P::BackgroundTask) { + fn execute(&self, task: P::BackgroundTask, _is_gui_thread: bool) { (self.task_executor.lock())(task) } } diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index 3ad38f1c..efc52fc6 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -377,7 +377,7 @@ impl WrapperInner

{ 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 WrapperInner

{ } impl MainThreadExecutor> for WrapperInner

{ - unsafe fn execute(&self, task: Task

) { + fn execute(&self, task: Task

, 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"), }, } diff --git a/src/wrapper/vst3/view.rs b/src/wrapper/vst3/view.rs index 27bc8356..989b62bd 100644 --- a/src/wrapper/vst3/view.rs +++ b/src/wrapper/vst3/view.rs @@ -486,7 +486,7 @@ impl IEventHandler for RunLoopEventHandler

{ // 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::();