From 84f834abb6adc943f75c2ebb4cedb7bbd8a29ffc Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 22 Oct 2022 01:15:16 +0200 Subject: [PATCH] Add AsyncExecutor support to ProcessContext --- BREAKING_CHANGES.md | 2 ++ README.md | 4 +++- plugins/crisp/src/lib.rs | 2 +- plugins/crossover/src/lib.rs | 2 +- plugins/diopser/src/lib.rs | 2 +- plugins/examples/gain/src/lib.rs | 2 +- plugins/examples/gain_gui_egui/src/lib.rs | 2 +- plugins/examples/gain_gui_iced/src/lib.rs | 2 +- plugins/examples/gain_gui_vizia/src/lib.rs | 2 +- plugins/examples/midi_inverter/src/lib.rs | 2 +- plugins/examples/poly_mod_synth/src/lib.rs | 6 +++--- plugins/examples/sine/src/lib.rs | 2 +- plugins/examples/stft/src/lib.rs | 2 +- plugins/loudness_war_winner/src/lib.rs | 2 +- plugins/puberty_simulator/src/lib.rs | 2 +- plugins/safety_limiter/src/lib.rs | 2 +- plugins/spectral_compressor/src/lib.rs | 2 +- src/context.rs | 6 +++++- src/event_loop.rs | 13 +++++++++++++ src/plugin.rs | 2 +- src/wrapper/clap/context.rs | 13 +++++++++++-- src/wrapper/clap/wrapper.rs | 20 ++++++++++++-------- src/wrapper/standalone/context.rs | 8 +++++++- src/wrapper/standalone/wrapper.rs | 18 +++++++++++++++--- src/wrapper/vst3/context.rs | 7 ++++++- src/wrapper/vst3/inner.rs | 18 +++++++++++------- src/wrapper/vst3/view.rs | 8 ++++---- 27 files changed, 107 insertions(+), 46 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index dcefbe0e..02eff8fa 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -8,6 +8,8 @@ code then it will not be listed here. ## [2022-10-22] +- The `&mut impl ProcessContext` argument to `Plugin::process()` needs to be + changed to `&mut impl ProcessContext`. - The `&mut impl InitContext` argument to `Plugin::initialize()` needs to be changed to `&mut impl InitContext`. - NIH-plug has gained support for asynchronously running background tasks in a diff --git a/README.md b/README.md index 172e6e7b..427fdc70 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Scroll down for more information on the underlying plugin framework. - Standalone binaries can be made by calling `nih_export_standalone(Foo)` from your `main()` function. Standalones come with a CLI for configuration and full JACK audio, MIDI, and transport support. -- Declarative parameter handling without any boilerplate. +- Rich declarative parameter system without any boilerplate. - Define parameters for your plugin by adding `FloatParam`, `IntParam`, `BoolParam`, and `EnumParam` fields to your parameter struct, assign stable IDs to them with the `#[id = "foobar"]`, and a `#[derive(Params)]` @@ -107,6 +107,8 @@ Scroll down for more information on the underlying plugin framework. trait to enable compile time generated parameters and other bespoke functionality. - Stateful. Behaves mostly like JUCE, just without all of the boilerplate. +- Comes with a simple yet powerful way to asynchronously run background tasks + from a plugin that's both type-safe and realtime-safe. - Does not make any assumptions on how you want to process audio, but does come with utilities and adapters to help with common access patterns. - Efficiently iterate over an audio buffer either per-sample per-channel, diff --git a/plugins/crisp/src/lib.rs b/plugins/crisp/src/lib.rs index a12addac..2e7ef495 100644 --- a/plugins/crisp/src/lib.rs +++ b/plugins/crisp/src/lib.rs @@ -356,7 +356,7 @@ impl Plugin for Crisp { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, + _context: &mut impl ProcessContext, ) -> ProcessStatus { for (_, mut block) in buffer.iter_blocks(BLOCK_SIZE) { let mut rm_outputs = [[0.0; NUM_CHANNELS as usize]; BLOCK_SIZE]; diff --git a/plugins/crossover/src/lib.rs b/plugins/crossover/src/lib.rs index 4f3974c8..e96cfd9a 100644 --- a/plugins/crossover/src/lib.rs +++ b/plugins/crossover/src/lib.rs @@ -228,7 +228,7 @@ impl Plugin for Crossover { &mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers, - context: &mut impl ProcessContext, + context: &mut impl ProcessContext, ) -> ProcessStatus { // Right now both crossover types only do 24 dB/octave Linkwitz-Riley style crossovers match self.params.crossover_type.value() { diff --git a/plugins/diopser/src/lib.rs b/plugins/diopser/src/lib.rs index 7bb4566d..d24f3184 100644 --- a/plugins/diopser/src/lib.rs +++ b/plugins/diopser/src/lib.rs @@ -288,7 +288,7 @@ impl Plugin for Diopser { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, + _context: &mut impl ProcessContext, ) -> ProcessStatus { // Since this is an expensive operation, only update the filters when it's actually // necessary, and allow smoothing only every n samples using the automation precision diff --git a/plugins/examples/gain/src/lib.rs b/plugins/examples/gain/src/lib.rs index 80b9687b..8142e9ea 100644 --- a/plugins/examples/gain/src/lib.rs +++ b/plugins/examples/gain/src/lib.rs @@ -156,7 +156,7 @@ impl Plugin for Gain { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, + _context: &mut impl ProcessContext, ) -> ProcessStatus { for channel_samples in buffer.iter_samples() { // Smoothing is optionally built into the parameters themselves diff --git a/plugins/examples/gain_gui_egui/src/lib.rs b/plugins/examples/gain_gui_egui/src/lib.rs index 562994b6..d21cc7db 100644 --- a/plugins/examples/gain_gui_egui/src/lib.rs +++ b/plugins/examples/gain_gui_egui/src/lib.rs @@ -174,7 +174,7 @@ impl Plugin for Gain { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, + _context: &mut impl ProcessContext, ) -> ProcessStatus { for channel_samples in buffer.iter_samples() { let mut amplitude = 0.0; diff --git a/plugins/examples/gain_gui_iced/src/lib.rs b/plugins/examples/gain_gui_iced/src/lib.rs index e5d53221..1202a464 100644 --- a/plugins/examples/gain_gui_iced/src/lib.rs +++ b/plugins/examples/gain_gui_iced/src/lib.rs @@ -119,7 +119,7 @@ impl Plugin for Gain { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, + _context: &mut impl ProcessContext, ) -> ProcessStatus { for channel_samples in buffer.iter_samples() { let mut amplitude = 0.0; diff --git a/plugins/examples/gain_gui_vizia/src/lib.rs b/plugins/examples/gain_gui_vizia/src/lib.rs index 76cfd9be..4e016556 100644 --- a/plugins/examples/gain_gui_vizia/src/lib.rs +++ b/plugins/examples/gain_gui_vizia/src/lib.rs @@ -118,7 +118,7 @@ impl Plugin for Gain { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, + _context: &mut impl ProcessContext, ) -> ProcessStatus { for channel_samples in buffer.iter_samples() { let mut amplitude = 0.0; diff --git a/plugins/examples/midi_inverter/src/lib.rs b/plugins/examples/midi_inverter/src/lib.rs index e00ee8c9..3a6cd6ff 100644 --- a/plugins/examples/midi_inverter/src/lib.rs +++ b/plugins/examples/midi_inverter/src/lib.rs @@ -44,7 +44,7 @@ impl Plugin for MidiInverter { &mut self, _buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - context: &mut impl ProcessContext, + context: &mut impl ProcessContext, ) -> ProcessStatus { // We'll invert the channel, note index, velocity, pressure, CC value, pitch bend, and // anything else that is invertable for all events we receive diff --git a/plugins/examples/poly_mod_synth/src/lib.rs b/plugins/examples/poly_mod_synth/src/lib.rs index 429bbb6f..308849ee 100644 --- a/plugins/examples/poly_mod_synth/src/lib.rs +++ b/plugins/examples/poly_mod_synth/src/lib.rs @@ -177,7 +177,7 @@ impl Plugin for PolyModSynth { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - context: &mut impl ProcessContext, + context: &mut impl ProcessContext, ) -> ProcessStatus { // NIH-plug has a block-splitting adapter for `Buffer`. While this works great for effect // plugins, for polyphonic synths the block size should be `min(MAX_BLOCK_SIZE, @@ -443,7 +443,7 @@ impl PolyModSynth { /// voice will be stolen. Returns a reference to the new voice. fn start_voice( &mut self, - context: &mut impl ProcessContext, + context: &mut impl ProcessContext, sample_offset: u32, voice_id: Option, channel: u8, @@ -544,7 +544,7 @@ impl PolyModSynth { /// matching voices. fn choke_voices( &mut self, - context: &mut impl ProcessContext, + context: &mut impl ProcessContext, sample_offset: u32, voice_id: Option, channel: u8, diff --git a/plugins/examples/sine/src/lib.rs b/plugins/examples/sine/src/lib.rs index ed4dc5ef..39c51244 100644 --- a/plugins/examples/sine/src/lib.rs +++ b/plugins/examples/sine/src/lib.rs @@ -145,7 +145,7 @@ impl Plugin for Sine { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - context: &mut impl ProcessContext, + context: &mut impl ProcessContext, ) -> ProcessStatus { let mut next_event = context.next_event(); for (sample_id, channel_samples) in buffer.iter_samples().enumerate() { diff --git a/plugins/examples/stft/src/lib.rs b/plugins/examples/stft/src/lib.rs index cb1c627e..1f2c1f24 100644 --- a/plugins/examples/stft/src/lib.rs +++ b/plugins/examples/stft/src/lib.rs @@ -132,7 +132,7 @@ impl Plugin for Stft { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, + _context: &mut impl ProcessContext, ) -> ProcessStatus { self.stft .process_overlap_add(buffer, 1, |_channel_idx, real_fft_buffer| { diff --git a/plugins/loudness_war_winner/src/lib.rs b/plugins/loudness_war_winner/src/lib.rs index a2941777..7ddcacce 100644 --- a/plugins/loudness_war_winner/src/lib.rs +++ b/plugins/loudness_war_winner/src/lib.rs @@ -174,7 +174,7 @@ impl Plugin for LoudnessWarWinner { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, + _context: &mut impl ProcessContext, ) -> ProcessStatus { for mut channel_samples in buffer.iter_samples() { let output_gain = self.params.output_gain.smoothed.next(); diff --git a/plugins/puberty_simulator/src/lib.rs b/plugins/puberty_simulator/src/lib.rs index fec36ce6..aba22596 100644 --- a/plugins/puberty_simulator/src/lib.rs +++ b/plugins/puberty_simulator/src/lib.rs @@ -224,7 +224,7 @@ impl Plugin for PubertySimulator { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - context: &mut impl ProcessContext, + context: &mut impl ProcessContext, ) -> ProcessStatus { // Compensate for the window function, the overlap, and the extra gain introduced by the // IDFT operation diff --git a/plugins/safety_limiter/src/lib.rs b/plugins/safety_limiter/src/lib.rs index 428f4ccf..b5d98830 100644 --- a/plugins/safety_limiter/src/lib.rs +++ b/plugins/safety_limiter/src/lib.rs @@ -200,7 +200,7 @@ impl Plugin for SafetyLimiter { &mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, + _context: &mut impl ProcessContext, ) -> ProcessStatus { // Don't do anything when bouncing if self.buffer_config.process_mode == ProcessMode::Offline { diff --git a/plugins/spectral_compressor/src/lib.rs b/plugins/spectral_compressor/src/lib.rs index 61599036..e900ae78 100644 --- a/plugins/spectral_compressor/src/lib.rs +++ b/plugins/spectral_compressor/src/lib.rs @@ -346,7 +346,7 @@ impl Plugin for SpectralCompressor { &mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers, - context: &mut impl ProcessContext, + context: &mut impl ProcessContext, ) -> ProcessStatus { // If the window size has changed since the last process call, reset the buffers and chance // our latency. All of these buffers already have enough capacity so this won't allocate. diff --git a/src/context.rs b/src/context.rs index a35d3833..7aff3d73 100644 --- a/src/context.rs +++ b/src/context.rs @@ -50,7 +50,11 @@ pub trait InitContext { // // The implementing wrapper needs to be able to handle concurrent requests, and it should perform // the actual callback within [MainThreadQueue::do_maybe_async]. -pub trait ProcessContext { +pub trait ProcessContext { + /// Run a task on a background thread. This allows defering expensive background tasks for + /// alter. As long as creating the `task` is realtime-safe, this operation is too. + fn execute_async(&self, task: ::Task); + /// Get the current plugin API. fn plugin_api(&self) -> PluginApi; diff --git a/src/event_loop.rs b/src/event_loop.rs index 647acab8..9fabf682 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -10,6 +10,8 @@ mod linux; #[cfg(target_os = "windows")] mod windows; +use crate::prelude::AsyncExecutor; + #[cfg(all(target_family = "unix", not(target_os = "macos")))] pub(crate) use self::linux::LinuxEventLoop as OsEventLoop; // For now, also use the Linux event loop on macOS so it at least compiles @@ -38,6 +40,9 @@ where { /// Create and start a new event loop. The thread this is called on will be designated as the /// main thread, so this should be called when constructing the wrapper. + // + // TODO: This use of `Weak` is a bit weird outside the context of the VST3 wrapper, but the + // alternatives didn't feel right either. fn new_and_spawn(executor: Weak) -> Self; /// Either post the function to the task queue so it can be delegated to the main thread, or @@ -63,3 +68,11 @@ pub(crate) trait MainThreadExecutor: Send + Sync { /// assume (and can only assume) that this is called from the main thread. unsafe fn execute(&self, task: T); } + +/// An adapter implementation to allow any [`AsyncExecutor`] to function as a +/// [`MainThreadExecutor`]. Used only in the standalone wrapper. +impl> MainThreadExecutor for E { + unsafe fn execute(&self, task: T) { + AsyncExecutor::execute(self, task); + } +} diff --git a/src/plugin.rs b/src/plugin.rs index 99967bd6..93057d03 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -195,7 +195,7 @@ pub trait Plugin: Default + Send + 'static { &mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers, - context: &mut impl ProcessContext, + context: &mut impl ProcessContext, ) -> ProcessStatus; /// Called when the plugin is deactivated. The host will call diff --git a/src/wrapper/clap/context.rs b/src/wrapper/clap/context.rs index 32b0af16..09d9442f 100644 --- a/src/wrapper/clap/context.rs +++ b/src/wrapper/clap/context.rs @@ -2,9 +2,10 @@ use atomic_refcell::AtomicRefMut; use std::collections::VecDeque; use std::sync::Arc; -use super::wrapper::{OutputParamEvent, Wrapper}; +use super::wrapper::{OutputParamEvent, Task, Wrapper}; use crate::async_executor::AsyncExecutor; use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport}; +use crate::event_loop::EventLoop; use crate::midi::NoteEvent; use crate::params::internals::ParamPtr; use crate::plugin::ClapPlugin; @@ -131,7 +132,15 @@ impl InitContext

for WrapperInitContext<'_, P> { } } -impl ProcessContext for WrapperProcessContext<'_, P> { +impl ProcessContext

for WrapperProcessContext<'_, P> { + fn execute_async( + &self, + task: <

::AsyncExecutor as AsyncExecutor>::Task, + ) { + let task_posted = self.wrapper.do_maybe_async(Task::PluginTask(task)); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + fn plugin_api(&self) -> PluginApi { PluginApi::Clap } diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index ce61777c..1c3a16c3 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -76,6 +76,7 @@ use std::time::Duration; use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext}; use super::descriptor::PluginDescriptor; use super::util::ClapPtr; +use crate::async_executor::AsyncExecutor; use crate::buffer::Buffer; use crate::context::Transport; use crate::editor::{Editor, ParentWindowHandle}; @@ -84,7 +85,7 @@ use crate::midi::{MidiConfig, NoteEvent}; use crate::params::internals::ParamPtr; use crate::params::{ParamFlags, Params}; use crate::plugin::{ - AuxiliaryBuffers, BufferConfig, BusConfig, ClapPlugin, ProcessMode, ProcessStatus, + AuxiliaryBuffers, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessMode, ProcessStatus, }; use crate::util::permit_alloc; use crate::wrapper::clap::util::{read_stream, write_stream}; @@ -257,7 +258,7 @@ pub struct Wrapper { /// implementations. Instead, we'll post tasks to this queue, ask the host to call /// [`on_main_thread()`][Self::on_main_thread()] on the main thread, and then continue to pop /// tasks off this queue there until it is empty. - tasks: ArrayQueue, + tasks: ArrayQueue>, /// The ID of the main thread. In practice this is the ID of the thread that created this /// object. If the host supports the thread check extension (and /// [`host_thread_check`][Self::host_thread_check] thus contains a value), then that extension @@ -268,8 +269,10 @@ pub struct Wrapper { /// Tasks that can be sent from the plugin to be executed on the main thread in a non-blocking /// realtime-safe way. Instead of using a random thread or the OS' event loop like in the Linux /// implementation, this uses [`clap_host::request_callback()`] instead. -#[derive(Debug)] -pub enum Task { +#[allow(clippy::enum_variant_names)] +pub enum Task { + /// Execute one of the plugin's background tasks. + PluginTask(::Task), /// Inform the host that the latency has changed. LatencyChanged, /// Inform the host that the voice info has changed. @@ -311,12 +314,12 @@ pub enum OutputParamEvent { /// Because CLAP has this [`clap_host::request_host_callback()`] function, we don't need to use /// `OsEventLoop` and can instead just request a main thread callback directly. -impl EventLoop> for Wrapper

{ +impl EventLoop, Wrapper

> for Wrapper

{ fn new_and_spawn(_executor: std::sync::Weak) -> Self { panic!("What are you doing"); } - fn do_maybe_async(&self, task: Task) -> bool { + fn do_maybe_async(&self, task: Task

) -> bool { if self.is_main_thread() { unsafe { self.execute(task) }; true @@ -346,10 +349,11 @@ impl EventLoop> for Wrapper

{ } } -impl MainThreadExecutor for Wrapper

{ - unsafe fn execute(&self, task: Task) { +impl MainThreadExecutor> for Wrapper

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

) { // This function is always called from the main thread, from [Self::on_main_thread]. match task { + Task::PluginTask(task) => AsyncExecutor::execute(&self.async_executor, task), Task::LatencyChanged => match &*self.host_latency.borrow() { Some(host_latency) => { // XXX: The CLAP docs mention that you should request a restart if this happens diff --git a/src/wrapper/standalone/context.rs b/src/wrapper/standalone/context.rs index 6e07db37..5b7f4bab 100644 --- a/src/wrapper/standalone/context.rs +++ b/src/wrapper/standalone/context.rs @@ -5,6 +5,7 @@ use super::backend::Backend; use super::wrapper::{GuiTask, Wrapper}; use crate::async_executor::AsyncExecutor; use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport}; +use crate::event_loop::EventLoop; use crate::midi::NoteEvent; use crate::params::internals::ParamPtr; use crate::plugin::Plugin; @@ -96,7 +97,12 @@ impl InitContext

for WrapperInitContext<'_, P, B> { } } -impl ProcessContext for WrapperProcessContext<'_, P, B> { +impl ProcessContext

for WrapperProcessContext<'_, P, B> { + fn execute_async(&self, task: ::Task) { + let task_posted = self.wrapper.event_loop.do_maybe_async(task); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + fn plugin_api(&self) -> PluginApi { PluginApi::Standalone } diff --git a/src/wrapper/standalone/wrapper.rs b/src/wrapper/standalone/wrapper.rs index eb0c08d9..0221eda1 100644 --- a/src/wrapper/standalone/wrapper.rs +++ b/src/wrapper/standalone/wrapper.rs @@ -13,8 +13,10 @@ use std::thread; use super::backend::Backend; use super::config::WrapperConfig; use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext}; +use crate::async_executor::AsyncExecutor; use crate::context::Transport; use crate::editor::{Editor, ParentWindowHandle}; +use crate::event_loop::{EventLoop, OsEventLoop}; use crate::midi::NoteEvent; use crate::params::internals::ParamPtr; use crate::params::{ParamFlags, Params}; @@ -36,7 +38,7 @@ pub struct Wrapper { /// The wrapped plugin instance. plugin: Mutex

, /// The plugin's background task executor. - pub async_executor: P::AsyncExecutor, + pub async_executor: Arc, /// The plugin's parameters. These are fetched once during initialization. That way the /// `ParamPtr`s are guaranteed to live at least as long as this object and we can interact with /// the `Params` object without having to acquire a lock on `plugin`. @@ -52,6 +54,14 @@ pub struct Wrapper { /// creating an editor. pub editor: Option>>>, + /// A realtime-safe task queue so the plugin can schedule tasks that need to be run later on the + /// GUI thread. See the same field in the VST3 wrapper for more information on why this looks + /// the way it does. + /// + /// This is only used for executing [`AsyncExecutor`] tasks, so it's parameterized directly over + /// that using a special `MainThreadExecutor` wrapper around `AsyncExecutor`. + pub(crate) event_loop: OsEventLoop<::Task, P::AsyncExecutor>, + config: WrapperConfig, /// The bus and buffer configurations are static for the standalone target. @@ -134,7 +144,7 @@ impl Wrapper { /// not accept the IO configuration from the wrapper config. pub fn new(backend: B, config: WrapperConfig) -> Result, WrapperError> { let plugin = P::default(); - let async_executor = plugin.async_executor(); + let async_executor = Arc::new(plugin.async_executor()); let params = plugin.params(); let editor = plugin.editor().map(|editor| Arc::new(Mutex::new(editor))); @@ -181,7 +191,7 @@ impl Wrapper { backend: AtomicRefCell::new(backend), plugin: Mutex::new(plugin), - async_executor, + async_executor: async_executor.clone(), params, known_parameters: param_map.iter().map(|(_, ptr, _)| *ptr).collect(), param_map: param_map @@ -190,6 +200,8 @@ impl Wrapper { .collect(), editor, + event_loop: OsEventLoop::new_and_spawn(Arc::downgrade(&async_executor)), + bus_config: BusConfig { num_input_channels: config.input_channels.unwrap_or(P::DEFAULT_INPUT_CHANNELS), num_output_channels: config.output_channels.unwrap_or(P::DEFAULT_OUTPUT_CHANNELS), diff --git a/src/wrapper/vst3/context.rs b/src/wrapper/vst3/context.rs index 0e7b8a2e..c72f1f43 100644 --- a/src/wrapper/vst3/context.rs +++ b/src/wrapper/vst3/context.rs @@ -134,7 +134,12 @@ impl InitContext

for WrapperInitContext<'_, P> { } } -impl ProcessContext for WrapperProcessContext<'_, P> { +impl ProcessContext

for WrapperProcessContext<'_, P> { + fn execute_async(&self, task: ::Task) { + let task_posted = self.inner.do_maybe_async(Task::PluginTask(task)); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + fn plugin_api(&self) -> PluginApi { PluginApi::Vst3 } diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index 1afa2e8c..ecbe68a1 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -14,6 +14,7 @@ use super::note_expressions::NoteExpressionController; use super::param_units::ParamUnits; use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START}; use super::view::WrapperView; +use crate::async_executor::AsyncExecutor; use crate::buffer::Buffer; use crate::context::Transport; use crate::editor::Editor; @@ -21,7 +22,7 @@ use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop}; use crate::midi::{MidiConfig, NoteEvent}; use crate::params::internals::ParamPtr; use crate::params::{ParamFlags, Params}; -use crate::plugin::{BufferConfig, BusConfig, ProcessMode, ProcessStatus, Vst3Plugin}; +use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessMode, ProcessStatus, Vst3Plugin}; use crate::wrapper::state::{self, PluginState}; use crate::wrapper::util::{hash_param_id, process_wrapper}; @@ -59,7 +60,7 @@ pub(crate) struct WrapperInner { /// reason to mutably borrow the event loop, so reads will never be contested. /// /// TODO: Is there a better type for Send+Sync late initialization? - pub event_loop: AtomicRefCell>>, + pub event_loop: AtomicRefCell, Self>>>, /// Whether the plugin is currently processing audio. In other words, the last state /// `IAudioProcessor::setActive()` has been called with. @@ -150,8 +151,10 @@ pub(crate) struct WrapperInner { /// Tasks that can be sent from the plugin to be executed on the main thread in a non-blocking /// realtime-safe way (either a random thread or `IRunLoop` on Linux, the OS' message loop on /// Windows and macOS). -#[derive(Debug, Clone)] -pub enum Task { +#[allow(clippy::enum_variant_names)] +pub enum Task { + /// Execute one of the plugin's background tasks. + PluginTask(::Task), /// Trigger a restart with the given restart flags. This is a bit set of the flags from /// [`vst3_sys::vst::RestartFlags`]. TriggerRestart(i32), @@ -353,7 +356,7 @@ impl WrapperInner

{ /// /// If the task queue is full, then this will return false. #[must_use] - pub fn do_maybe_async(&self, task: Task) -> bool { + pub fn do_maybe_async(&self, task: Task

) -> bool { let event_loop = self.event_loop.borrow(); let event_loop = event_loop.as_ref().unwrap(); if event_loop.is_main_thread() { @@ -514,8 +517,8 @@ impl WrapperInner

{ } } -impl MainThreadExecutor for WrapperInner

{ - unsafe fn execute(&self, task: Task) { +impl MainThreadExecutor> for WrapperInner

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

) { // 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 @@ -523,6 +526,7 @@ impl MainThreadExecutor for WrapperInner

{ // function for checking if a to be scheduled task can be handled right there and // then). match task { + Task::PluginTask(task) => AsyncExecutor::execute(&self.async_executor, task), Task::TriggerRestart(flags) => match &*self.component_handler.borrow() { Some(handler) => { handler.restart_component(flags); diff --git a/src/wrapper/vst3/view.rs b/src/wrapper/vst3/view.rs index 14f8ff10..27bc8356 100644 --- a/src/wrapper/vst3/view.rs +++ b/src/wrapper/vst3/view.rs @@ -96,7 +96,7 @@ struct RunLoopEventHandler { /// implementations. Instead, we'll post tasks to this queue, ask the host to call /// [`on_main_thread()`][Self::on_main_thread()] on the main thread, and then continue to pop /// tasks off this queue there until it is empty. - tasks: ArrayQueue, + tasks: ArrayQueue>, } impl WrapperView

{ @@ -162,7 +162,7 @@ impl WrapperView

{ /// run on the host's UI thread. If not, then this will return an `Err` value containing the /// task so it can be run elsewhere. #[cfg(target_os = "linux")] - pub fn do_maybe_in_run_loop(&self, task: Task) -> Result<(), Task> { + pub fn do_maybe_in_run_loop(&self, task: Task

) -> Result<(), Task

> { match &*self.run_loop_event_handler.0.read() { Some(run_loop) => run_loop.post_task(task), None => Err(task), @@ -174,7 +174,7 @@ impl WrapperView

{ /// task so it can be run elsewhere. #[cfg(not(target_os = "linux"))] #[must_use] - pub fn do_maybe_in_run_loop(&self, task: Task) -> Result<(), Task> { + pub fn do_maybe_in_run_loop(&self, task: Task

) -> Result<(), Task

> { Err(task) } } @@ -222,7 +222,7 @@ impl RunLoopEventHandler

{ /// Post a task to the tasks queue so it will be run on the host's GUI thread later. Returns the /// task if the queue is full and the task could not be posted. - pub fn post_task(&self, task: Task) -> Result<(), Task> { + pub fn post_task(&self, task: Task

) -> Result<(), Task

> { self.tasks.push(task)?; // We need to use a Unix domain socket to let the host know to call our event handler. In