1
0
Fork 0

Add an AsyncExecutor for editor GUIs

This is decoupled form `GuiContext` as that would require invasive
changes all over the place.
This commit is contained in:
Robbert van der Helm 2022-10-22 15:05:39 +02:00
parent f3bb816cb5
commit c980576102
13 changed files with 91 additions and 30 deletions

View file

@ -29,6 +29,8 @@ code then it will not be listed here.
changed to `&mut impl InitContext<Self>`.
- The `&mut impl ProcessContext` argument to `Plugin::process()` needs to be
changed to `&mut impl ProcessContext<Self>`.
- The `Plugin::editor()` method now also takes a
`_async_executor: AsyncExecutor<Self>` parameter.
## [2022-10-20]

View file

@ -313,7 +313,7 @@ impl Plugin for Crisp {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {
fn editor(&self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
editor::create(self.params.clone(), self.params.editor_state.clone())
}

View file

@ -258,7 +258,7 @@ impl Plugin for Diopser {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {
fn editor(&self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
editor::create(self.params.clone(), self.params.editor_state.clone())
}

View file

@ -89,7 +89,7 @@ impl Plugin for Gain {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {
fn editor(&self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
let params = self.params.clone();
let peak_meter = self.peak_meter.clone();
create_egui_editor(

View file

@ -86,7 +86,7 @@ impl Plugin for Gain {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {
fn editor(&self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
editor::create(
self.params.clone(),
self.peak_meter.clone(),

View file

@ -85,7 +85,7 @@ impl Plugin for Gain {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {
fn editor(&self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
editor::create(
self.params.clone(),
self.peak_meter.clone(),

View file

@ -278,7 +278,7 @@ impl Plugin for SpectralCompressor {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {
fn editor(&self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
editor::create(self.params.clone(), self.editor_state.clone())
}

View file

@ -8,6 +8,7 @@ use crate::context::process::ProcessContext;
use crate::editor::Editor;
use crate::midi::MidiConfig;
use crate::params::Params;
use crate::prelude::AsyncExecutor;
use crate::wrapper::clap::features::ClapFeature;
use crate::wrapper::state::PluginState;
@ -122,7 +123,7 @@ pub trait Plugin: Default + Send + 'static {
/// access into the editor. You can later modify the parameters through the
/// [`GuiContext`][crate::prelude::GuiContext] and [`ParamSetter`][crate::prelude::ParamSetter] after the editor
/// GUI has been created.
fn editor(&self) -> Option<Box<dyn Editor>> {
fn editor(&self, async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
None
}

View file

@ -77,6 +77,7 @@ use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContex
use super::descriptor::PluginDescriptor;
use super::util::ClapPtr;
use crate::buffer::Buffer;
use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport;
use crate::editor::{Editor, ParentWindowHandle};
use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY};
@ -114,8 +115,8 @@ pub struct Wrapper<P: ClapPlugin> {
params: Arc<dyn Params>,
/// The plugin's editor, if it has one. This object does not do anything on its own, but we need
/// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when
/// creating an editor.
editor: Option<Mutex<Box<dyn Editor>>>,
/// creating an editor. Wrapped in an `AtomicRefCell` because it needs to be initialized late.
editor: AtomicRefCell<Option<Mutex<Box<dyn Editor>>>>,
/// A handle for the currently active editor instance. The plugin should implement `Drop` on
/// this handle for its closing behavior.
editor_handle: Mutex<Option<Box<dyn Any + Send>>>,
@ -388,7 +389,6 @@ impl<P: ClapPlugin> Wrapper<P> {
pub fn new(host_callback: *const clap_host) -> Arc<Self> {
let plugin = P::default();
let task_executor = Mutex::new(plugin.task_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
// on `Self::updated_state_sender`
@ -551,7 +551,8 @@ impl<P: ClapPlugin> Wrapper<P> {
plugin: Mutex::new(plugin),
task_executor,
params,
editor,
// Initialized later as it needs a reference to the wrapper for the async executor
editor: AtomicRefCell::new(None),
editor_handle: Mutex::new(None),
editor_scaling_factor: AtomicF32::new(1.0),
@ -678,6 +679,22 @@ impl<P: ClapPlugin> Wrapper<P> {
let wrapper = Arc::new(wrapper);
*wrapper.this.borrow_mut() = Arc::downgrade(&wrapper);
// The editor also needs to be initialized later so the Async executor can work.
*wrapper.editor.borrow_mut() = wrapper
.plugin
.lock()
.editor(AsyncExecutor {
inner: Arc::new({
let wrapper = wrapper.clone();
move |task| {
let task_posted = wrapper.do_maybe_async(Task::PluginTask(task));
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
}
}),
})
.map(Mutex::new);
wrapper
}
@ -725,7 +742,7 @@ impl<P: ClapPlugin> Wrapper<P> {
/// the editor instance is currently locked then nothing will happen, and the request can safely
/// be ignored.
pub fn notify_param_values_changed(&self) {
if let Some(editor) = &self.editor {
if let Some(editor) = self.editor.borrow().as_ref() {
match editor.try_lock() {
Some(editor) => editor.param_values_changed(),
None => nih_debug_assert_failure!(
@ -740,7 +757,10 @@ impl<P: ClapPlugin> Wrapper<P> {
/// safely be called from any thread. If this returns `false`, then the plugin should reset its
/// size back to the previous value.
pub fn request_resize(&self) -> bool {
match (&*self.host_gui.borrow(), &self.editor) {
match (
self.host_gui.borrow().as_ref(),
self.editor.borrow().as_ref(),
) {
(Some(host_gui), Some(editor)) => {
let (unscaled_width, unscaled_height) = editor.lock().size();
let scaling_factor = self.editor_scaling_factor.load(Ordering::Relaxed);
@ -2300,7 +2320,7 @@ impl<P: ClapPlugin> Wrapper<P> {
&wrapper.clap_plugin_audio_ports_config as *const _ as *const c_void
} else if id == CLAP_EXT_AUDIO_PORTS {
&wrapper.clap_plugin_audio_ports as *const _ as *const c_void
} else if id == CLAP_EXT_GUI && wrapper.editor.is_some() {
} else if id == CLAP_EXT_GUI && wrapper.editor.borrow().is_some() {
// Only report that we support this extension if the plugin has an editor
&wrapper.clap_plugin_gui as *const _ as *const c_void
} else if id == CLAP_EXT_LATENCY {
@ -2686,6 +2706,7 @@ impl<P: ClapPlugin> Wrapper<P> {
if wrapper
.editor
.borrow()
.as_ref()
.unwrap()
.lock()
@ -2709,7 +2730,8 @@ impl<P: ClapPlugin> Wrapper<P> {
let wrapper = &*(plugin as *const Self);
// For macOS the scaling factor is always 1
let (unscaled_width, unscaled_height) = wrapper.editor.as_ref().unwrap().lock().size();
let (unscaled_width, unscaled_height) =
wrapper.editor.borrow().as_ref().unwrap().lock().size();
let scaling_factor = wrapper.editor_scaling_factor.load(Ordering::Relaxed);
(*width, *height) = (
(unscaled_width as f32 * scaling_factor).round() as u32,
@ -2751,7 +2773,8 @@ impl<P: ClapPlugin> Wrapper<P> {
check_null_ptr!(false, plugin);
let wrapper = &*(plugin as *const Self);
let (unscaled_width, unscaled_height) = wrapper.editor.as_ref().unwrap().lock().size();
let (unscaled_width, unscaled_height) =
wrapper.editor.borrow().as_ref().unwrap().lock().size();
let scaling_factor = wrapper.editor_scaling_factor.load(Ordering::Relaxed);
let (editor_width, editor_height) = (
(unscaled_width as f32 * scaling_factor).round() as u32,
@ -2793,7 +2816,7 @@ impl<P: ClapPlugin> Wrapper<P> {
};
// This extension is only exposed when we have an editor
*editor_handle = Some(wrapper.editor.as_ref().unwrap().lock().spawn(
*editor_handle = Some(wrapper.editor.borrow().as_ref().unwrap().lock().spawn(
ParentWindowHandle { handle },
wrapper.clone().make_gui_context(),
));

View file

@ -108,7 +108,8 @@ impl<P: Plugin, B: Backend> GuiContext for WrapperGuiContext<P, B> {
}
fn request_resize(&self) -> bool {
let (unscaled_width, unscaled_height) = self.wrapper.editor.as_ref().unwrap().lock().size();
let (unscaled_width, unscaled_height) =
self.wrapper.editor.borrow().as_ref().unwrap().lock().size();
// This will cause the editor to be resized at the start of the next frame
let push_successful = self

View file

@ -13,6 +13,7 @@ use std::thread;
use super::backend::Backend;
use super::config::WrapperConfig;
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport;
use crate::editor::{Editor, ParentWindowHandle};
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
@ -51,8 +52,8 @@ pub struct Wrapper<P: Plugin, B: Backend> {
param_map: HashMap<String, ParamPtr>,
/// The plugin's editor, if it has one. This object does not do anything on its own, but we need
/// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when
/// creating an editor.
pub editor: Option<Arc<Mutex<Box<dyn Editor>>>>,
/// creating an editor. Wrapped in an `AtomicRefCell` because it needs to be initialized late.
pub editor: AtomicRefCell<Option<Arc<Mutex<Box<dyn Editor>>>>>,
/// 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
@ -159,7 +160,6 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
task_executor: Mutex::new(plugin.task_executor()),
});
let params = plugin.params();
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
// on `Self::updated_state_sender`
@ -211,7 +211,8 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
.into_iter()
.map(|(param_id, param_ptr, _)| (param_id, param_ptr))
.collect(),
editor,
// Initialized later as it needs a reference to the wrapper for the async executor
editor: AtomicRefCell::new(None),
event_loop: OsEventLoop::new_and_spawn(Arc::downgrade(&task_executor_wrapper)),
@ -236,6 +237,22 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
updated_state_receiver,
});
// The editor needs to be initialized later so the Async executor can work.
*wrapper.editor.borrow_mut() = wrapper
.plugin
.lock()
.editor(AsyncExecutor {
inner: Arc::new({
let wrapper = wrapper.clone();
move |task| {
let task_posted = wrapper.event_loop.do_maybe_async(task);
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
}
}),
})
.map(|editor| Arc::new(Mutex::new(editor)));
// Right now the IO configuration is fixed in the standalone target, so if the plugin cannot
// work with this then we cannot initialize the plugin at all.
{
@ -283,7 +300,7 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
thread::spawn(move || this.run_audio_thread(terminate_audio_thread, gui_task_sender))
};
match self.editor.clone() {
match self.editor.borrow().clone() {
Some(editor) => {
let context = self.clone().make_gui_context(gui_task_sender);
@ -511,7 +528,7 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
/// off-chance that the editor instance is currently locked then nothing will happen, and the
/// request can safely be ignored.
fn notify_param_values_changed(&self) {
if let Some(editor) = &self.editor {
if let Some(editor) = self.editor.borrow().as_ref() {
match editor.try_lock() {
Some(editor) => editor.param_values_changed(),
None => nih_debug_assert_failure!(

View file

@ -15,6 +15,7 @@ use super::param_units::ParamUnits;
use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START};
use super::view::WrapperView;
use crate::buffer::Buffer;
use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport;
use crate::editor::Editor;
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
@ -41,8 +42,8 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
pub params: Arc<dyn Params>,
/// The plugin's editor, if it has one. This object does not do anything on its own, but we need
/// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when
/// creating an editor.
pub editor: Option<Arc<Mutex<Box<dyn Editor>>>>,
/// creating an editor. Wrapped in an `AtomicRefCell` because it needs to be initialized late.
pub editor: AtomicRefCell<Option<Arc<Mutex<Box<dyn Editor>>>>>,
/// The host's [`IComponentHandler`] instance, if passed through
/// [`IEditController::set_component_handler`].
@ -198,7 +199,6 @@ impl<P: Vst3Plugin> WrapperInner<P> {
pub fn new() -> Arc<Self> {
let plugin = P::default();
let task_executor = Mutex::new(plugin.task_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
// on `Self::updated_state_sender`
@ -283,7 +283,8 @@ impl<P: Vst3Plugin> WrapperInner<P> {
plugin: Mutex::new(plugin),
task_executor,
params,
editor,
// Initialized later as it needs a reference to the wrapper for the async executor
editor: AtomicRefCell::new(None),
component_handler: AtomicRefCell::new(None),
@ -331,6 +332,22 @@ impl<P: Vst3Plugin> WrapperInner<P> {
*wrapper.event_loop.borrow_mut() =
Some(OsEventLoop::new_and_spawn(Arc::downgrade(&wrapper)));
// The editor also needs to be initialized later so the Async executor can work.
*wrapper.editor.borrow_mut() = wrapper
.plugin
.lock()
.editor(AsyncExecutor {
inner: Arc::new({
let wrapper = wrapper.clone();
move |task| {
let task_posted = wrapper.do_maybe_async(Task::PluginTask(task));
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
}
}),
})
.map(|editor| Arc::new(Mutex::new(editor)));
wrapper
}
@ -385,7 +402,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
/// that the editor instance is currently locked then nothing will happen, and the request can
/// safely be ignored.
pub fn notify_param_values_changed(&self) {
if let Some(editor) = &self.editor {
if let Some(editor) = self.editor.borrow().as_ref() {
match editor.try_lock() {
Some(editor) => editor.param_values_changed(),
None => nih_debug_assert_failure!(

View file

@ -771,7 +771,7 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
unsafe fn create_view(&self, _name: vst3_sys::base::FIDString) -> *mut c_void {
// Without specialization this is the least redundant way to check if the plugin has an
// editor. The default implementation returns a None here.
match &self.inner.editor {
match self.inner.editor.borrow().as_ref() {
Some(editor) => Box::into_raw(WrapperView::new(self.inner.clone(), editor.clone()))
as *mut vst3_sys::c_void,
None => ptr::null_mut(),