From 6ffa23971eb556eb1a065bcbf489ac7389c8981f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 22 Oct 2022 00:21:08 +0200 Subject: [PATCH] Add AsyncExecutor support to InitContext --- BREAKING_CHANGES.md | 4 +++- plugins/crisp/src/lib.rs | 2 +- plugins/crossover/src/lib.rs | 2 +- plugins/diopser/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/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/async_executor.rs | 2 +- src/context.rs | 12 +++++++++++- src/plugin.rs | 2 +- src/wrapper/clap/context.rs | 7 ++++++- src/wrapper/clap/wrapper.rs | 4 ++++ src/wrapper/standalone/context.rs | 8 ++++++-- src/wrapper/standalone/wrapper.rs | 4 ++++ src/wrapper/vst3/context.rs | 7 ++++++- src/wrapper/vst3/inner.rs | 4 ++++ 22 files changed, 58 insertions(+), 20 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index acb7cafd..dcefbe0e 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -6,8 +6,10 @@ new and what's changed, this document lists all breaking changes in reverse chronological order. If a new feature did not require any changes to existing code then it will not be listed here. -## [2022-10-21] +## [2022-10-22] +- 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 simple, type-safe, and realtime-safe way. This sadly does mean that every `Plugin` instance now needs to define an `AsyncExecutor` type definition and diff --git a/plugins/crisp/src/lib.rs b/plugins/crisp/src/lib.rs index d9e4fbf8..a12addac 100644 --- a/plugins/crisp/src/lib.rs +++ b/plugins/crisp/src/lib.rs @@ -328,7 +328,7 @@ impl Plugin for Crisp { &mut self, bus_config: &BusConfig, buffer_config: &BufferConfig, - _context: &mut impl InitContext, + _context: &mut impl InitContext, ) -> bool { nih_debug_assert_eq!(bus_config.num_input_channels, NUM_CHANNELS); nih_debug_assert_eq!(bus_config.num_output_channels, NUM_CHANNELS); diff --git a/plugins/crossover/src/lib.rs b/plugins/crossover/src/lib.rs index fec11adf..4f3974c8 100644 --- a/plugins/crossover/src/lib.rs +++ b/plugins/crossover/src/lib.rs @@ -201,7 +201,7 @@ impl Plugin for Crossover { &mut self, _bus_config: &BusConfig, buffer_config: &BufferConfig, - context: &mut impl InitContext, + context: &mut impl InitContext, ) -> bool { self.buffer_config = *buffer_config; diff --git a/plugins/diopser/src/lib.rs b/plugins/diopser/src/lib.rs index 54f8e66a..7bb4566d 100644 --- a/plugins/diopser/src/lib.rs +++ b/plugins/diopser/src/lib.rs @@ -272,7 +272,7 @@ impl Plugin for Diopser { &mut self, _bus_config: &BusConfig, buffer_config: &BufferConfig, - _context: &mut impl InitContext, + _context: &mut impl InitContext, ) -> bool { self.sample_rate = buffer_config.sample_rate; diff --git a/plugins/examples/gain_gui_egui/src/lib.rs b/plugins/examples/gain_gui_egui/src/lib.rs index 952941b1..562994b6 100644 --- a/plugins/examples/gain_gui_egui/src/lib.rs +++ b/plugins/examples/gain_gui_egui/src/lib.rs @@ -159,7 +159,7 @@ impl Plugin for Gain { &mut self, _bus_config: &BusConfig, buffer_config: &BufferConfig, - _context: &mut impl InitContext, + _context: &mut impl InitContext, ) -> bool { // After `PEAK_METER_DECAY_MS` milliseconds of pure silence, the peak meter's value should // have dropped by 12 dB diff --git a/plugins/examples/gain_gui_iced/src/lib.rs b/plugins/examples/gain_gui_iced/src/lib.rs index 4698b61f..e5d53221 100644 --- a/plugins/examples/gain_gui_iced/src/lib.rs +++ b/plugins/examples/gain_gui_iced/src/lib.rs @@ -104,7 +104,7 @@ impl Plugin for Gain { &mut self, _bus_config: &BusConfig, buffer_config: &BufferConfig, - _context: &mut impl InitContext, + _context: &mut impl InitContext, ) -> bool { // After `PEAK_METER_DECAY_MS` milliseconds of pure silence, the peak meter's value should // have dropped by 12 dB diff --git a/plugins/examples/gain_gui_vizia/src/lib.rs b/plugins/examples/gain_gui_vizia/src/lib.rs index c15d443b..76cfd9be 100644 --- a/plugins/examples/gain_gui_vizia/src/lib.rs +++ b/plugins/examples/gain_gui_vizia/src/lib.rs @@ -103,7 +103,7 @@ impl Plugin for Gain { &mut self, _bus_config: &BusConfig, buffer_config: &BufferConfig, - _context: &mut impl InitContext, + _context: &mut impl InitContext, ) -> bool { // After `PEAK_METER_DECAY_MS` milliseconds of pure silence, the peak meter's value should // have dropped by 12 dB diff --git a/plugins/examples/sine/src/lib.rs b/plugins/examples/sine/src/lib.rs index 6ff2e573..ed4dc5ef 100644 --- a/plugins/examples/sine/src/lib.rs +++ b/plugins/examples/sine/src/lib.rs @@ -127,7 +127,7 @@ impl Plugin for Sine { &mut self, _bus_config: &BusConfig, buffer_config: &BufferConfig, - _context: &mut impl InitContext, + _context: &mut impl InitContext, ) -> bool { self.sample_rate = buffer_config.sample_rate; diff --git a/plugins/examples/stft/src/lib.rs b/plugins/examples/stft/src/lib.rs index 9e5cf7e9..cb1c627e 100644 --- a/plugins/examples/stft/src/lib.rs +++ b/plugins/examples/stft/src/lib.rs @@ -111,7 +111,7 @@ impl Plugin for Stft { &mut self, _bus_config: &BusConfig, _buffer_config: &BufferConfig, - context: &mut impl InitContext, + context: &mut impl InitContext, ) -> bool { // The plugin's latency consists of the block size from the overlap-add procedure and half // of the filter kernel's size (since we're using a linear phase/symmetrical convolution diff --git a/plugins/loudness_war_winner/src/lib.rs b/plugins/loudness_war_winner/src/lib.rs index 46da3a52..a2941777 100644 --- a/plugins/loudness_war_winner/src/lib.rs +++ b/plugins/loudness_war_winner/src/lib.rs @@ -138,7 +138,7 @@ impl Plugin for LoudnessWarWinner { &mut self, bus_config: &BusConfig, buffer_config: &BufferConfig, - _context: &mut impl InitContext, + _context: &mut impl InitContext, ) -> bool { self.sample_rate = buffer_config.sample_rate; diff --git a/plugins/puberty_simulator/src/lib.rs b/plugins/puberty_simulator/src/lib.rs index 94f0c828..fec36ce6 100644 --- a/plugins/puberty_simulator/src/lib.rs +++ b/plugins/puberty_simulator/src/lib.rs @@ -184,7 +184,7 @@ impl Plugin for PubertySimulator { &mut self, _bus_config: &BusConfig, _buffer_config: &BufferConfig, - context: &mut impl InitContext, + context: &mut impl InitContext, ) -> bool { // Planning with RustFFT is very fast, but it will still allocate we we'll plan all of the // FFTs we might need in advance diff --git a/plugins/safety_limiter/src/lib.rs b/plugins/safety_limiter/src/lib.rs index 5339ed20..428f4ccf 100644 --- a/plugins/safety_limiter/src/lib.rs +++ b/plugins/safety_limiter/src/lib.rs @@ -172,7 +172,7 @@ impl Plugin for SafetyLimiter { &mut self, _bus_config: &BusConfig, buffer_config: &BufferConfig, - _context: &mut impl InitContext, + _context: &mut impl InitContext, ) -> bool { self.buffer_config = *buffer_config; self.morse_fadeout_samples_start = diff --git a/plugins/spectral_compressor/src/lib.rs b/plugins/spectral_compressor/src/lib.rs index e60490d8..61599036 100644 --- a/plugins/spectral_compressor/src/lib.rs +++ b/plugins/spectral_compressor/src/lib.rs @@ -295,7 +295,7 @@ impl Plugin for SpectralCompressor { &mut self, bus_config: &BusConfig, buffer_config: &BufferConfig, - context: &mut impl InitContext, + context: &mut impl InitContext, ) -> bool { // Needed to update the compressors later self.buffer_config = *buffer_config; diff --git a/src/async_executor.rs b/src/async_executor.rs index 47535d9b..0cfcca28 100644 --- a/src/async_executor.rs +++ b/src/async_executor.rs @@ -5,7 +5,7 @@ /// Something that can run tasks of type [`Task`][Self::Task]. This can be used to defer expensive /// computations to a background thread. Tasks can be spawned through the methods on the various /// [`*Context`][crate::context] types. -pub trait AsyncExecutor { +pub trait AsyncExecutor: Send + Sync { /// The type of task this executor can execute. This is usually an enum type. The task type /// should not contain any heap allocated data like [`Vec`]s and [`Box`]es. type Task; diff --git a/src/context.rs b/src/context.rs index 8c6a2a9b..a35d3833 100644 --- a/src/context.rs +++ b/src/context.rs @@ -5,6 +5,7 @@ use std::fmt::Display; use crate::midi::NoteEvent; use crate::params::internals::ParamPtr; use crate::params::Param; +use crate::prelude::{AsyncExecutor, Plugin}; use crate::wrapper::state::PluginState; /// Callbacks the plugin can make while it is being initialized. This is passed to the plugin during @@ -14,7 +15,16 @@ use crate::wrapper::state::PluginState; // // 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 InitContext { +pub trait InitContext { + /// Run a task directly on this thread. This ensures that the task has finished executing before + /// the plugin finishes initializing. + /// + /// # Note + /// + /// There is no asynchronous alternative for this function as that may result in incorrect + /// behavior when doing offline rendering. + fn execute(&self, task: ::Task); + /// Get the current plugin API. fn plugin_api(&self) -> PluginApi; diff --git a/src/plugin.rs b/src/plugin.rs index 39a53da8..99967bd6 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -160,7 +160,7 @@ pub trait Plugin: Default + Send + 'static { &mut self, bus_config: &BusConfig, buffer_config: &BufferConfig, - context: &mut impl InitContext, + context: &mut impl InitContext, ) -> bool { true } diff --git a/src/wrapper/clap/context.rs b/src/wrapper/clap/context.rs index 11f14f72..32b0af16 100644 --- a/src/wrapper/clap/context.rs +++ b/src/wrapper/clap/context.rs @@ -3,6 +3,7 @@ use std::collections::VecDeque; use std::sync::Arc; use super::wrapper::{OutputParamEvent, Wrapper}; +use crate::async_executor::AsyncExecutor; use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport}; use crate::midi::NoteEvent; use crate::params::internals::ParamPtr; @@ -112,7 +113,11 @@ impl GuiContext for WrapperGuiContext

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

for WrapperInitContext<'_, P> { + fn execute(&self, task: ::Task) { + self.wrapper.async_executor.execute(task); + } + fn plugin_api(&self) -> PluginApi { PluginApi::Clap } diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index 970f37f5..ce61777c 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -105,6 +105,8 @@ pub struct Wrapper { /// The wrapped plugin instance. plugin: Mutex

, + /// The plugin's background task executor. + pub async_executor: P::AsyncExecutor, /// 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`. @@ -381,6 +383,7 @@ impl MainThreadExecutor for Wrapper

{ impl Wrapper

{ pub fn new(host_callback: *const clap_host) -> Arc { let plugin = P::default(); + let async_executor = plugin.async_executor(); let editor = plugin.editor().map(Mutex::new); // This is used to allow the plugin to restore preset data from its editor, see the comment @@ -542,6 +545,7 @@ impl Wrapper

{ this: AtomicRefCell::new(Weak::new()), plugin: Mutex::new(plugin), + async_executor, params, editor, editor_handle: Mutex::new(None), diff --git a/src/wrapper/standalone/context.rs b/src/wrapper/standalone/context.rs index 390585a6..6e07db37 100644 --- a/src/wrapper/standalone/context.rs +++ b/src/wrapper/standalone/context.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use super::backend::Backend; use super::wrapper::{GuiTask, Wrapper}; +use crate::async_executor::AsyncExecutor; use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport}; use crate::midi::NoteEvent; use crate::params::internals::ParamPtr; @@ -23,7 +24,6 @@ pub(crate) struct WrapperGuiContext { /// can hold on to lock guards for event queues. Otherwise reading these events would require /// constant unnecessary atomic operations to lock the uncontested RwLocks. pub(crate) struct WrapperInitContext<'a, P: Plugin, B: Backend> { - #[allow(dead_code)] pub(super) wrapper: &'a Wrapper, } @@ -78,7 +78,11 @@ impl GuiContext for WrapperGuiContext { } } -impl InitContext for WrapperInitContext<'_, P, B> { +impl InitContext

for WrapperInitContext<'_, P, B> { + fn execute(&self, task: <

::AsyncExecutor as crate::prelude::AsyncExecutor>::Task) { + self.wrapper.async_executor.execute(task); + } + fn plugin_api(&self) -> PluginApi { PluginApi::Standalone } diff --git a/src/wrapper/standalone/wrapper.rs b/src/wrapper/standalone/wrapper.rs index 6a7d1d82..eb0c08d9 100644 --- a/src/wrapper/standalone/wrapper.rs +++ b/src/wrapper/standalone/wrapper.rs @@ -35,6 +35,8 @@ pub struct Wrapper { /// The wrapped plugin instance. plugin: Mutex

, + /// The plugin's background task executor. + pub async_executor: P::AsyncExecutor, /// 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`. @@ -132,6 +134,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 params = plugin.params(); let editor = plugin.editor().map(|editor| Arc::new(Mutex::new(editor))); @@ -178,6 +181,7 @@ impl Wrapper { backend: AtomicRefCell::new(backend), plugin: Mutex::new(plugin), + async_executor, params, known_parameters: param_map.iter().map(|(_, ptr, _)| *ptr).collect(), param_map: param_map diff --git a/src/wrapper/vst3/context.rs b/src/wrapper/vst3/context.rs index 7ea3406c..0e7b8a2e 100644 --- a/src/wrapper/vst3/context.rs +++ b/src/wrapper/vst3/context.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use vst3_sys::vst::IComponentHandler; use super::inner::{Task, WrapperInner}; +use crate::async_executor::AsyncExecutor; use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport}; use crate::midi::NoteEvent; use crate::params::internals::ParamPtr; @@ -115,7 +116,11 @@ impl GuiContext for WrapperGuiContext

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

for WrapperInitContext<'_, P> { + fn execute(&self, task: ::Task) { + self.inner.async_executor.execute(task); + } + fn plugin_api(&self) -> PluginApi { PluginApi::Vst3 } diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index 016aa322..1afa2e8c 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -31,6 +31,8 @@ use crate::wrapper::util::{hash_param_id, process_wrapper}; pub(crate) struct WrapperInner { /// The wrapped plugin instance. pub plugin: Mutex

, + /// The plugin's background task executor. + pub async_executor: P::AsyncExecutor, /// 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`. @@ -191,6 +193,7 @@ impl WrapperInner

{ #[allow(unused_unsafe)] pub fn new() -> Arc { let plugin = P::default(); + let async_executor = plugin.async_executor(); let editor = plugin.editor().map(|editor| Arc::new(Mutex::new(editor))); // This is used to allow the plugin to restore preset data from its editor, see the comment @@ -274,6 +277,7 @@ impl WrapperInner

{ let wrapper = Self { plugin: Mutex::new(plugin), + async_executor, params, editor,