From 72e29d044a3e819acc2d76991924b1200b270803 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 27 Jan 2021 18:46:36 +0100 Subject: [PATCH] Add a function for temporarily blocking event loop This can be used to prevent the Win32 message loop from running while there are plugins in some partially initialized state. --- src/wine-host/bridges/common.h | 17 ++++++++++++++++ src/wine-host/bridges/vst2.cpp | 20 +++++++++++++++--- src/wine-host/bridges/vst2.h | 8 ++++++++ src/wine-host/bridges/vst3.cpp | 37 ++++++++++++++++++++++++++++------ src/wine-host/bridges/vst3.h | 16 +++++++++++++++ 5 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/wine-host/bridges/common.h b/src/wine-host/bridges/common.h index e3a97ef1..76520655 100644 --- a/src/wine-host/bridges/common.h +++ b/src/wine-host/bridges/common.h @@ -32,6 +32,20 @@ class HostBridge { public: virtual ~HostBridge(){}; + /** + * If a plugin instance returns `true` here, then the event loop should not + * be run. Some very specific plugins, like the T-RackS 5 plugins, will set + * up a Win32 timer in their constructor, but since the plugins are left in + * a partially initialized state until `effOpen()` has been called running + * the Win32 message loop before that time will trigger a race condition + * within those plugins. This is very much an issue with those plugins, but + * since this situation wouldn't occur on Windows we'll just have to work + * around it. + * + * @relates MainContext::async_handle_events + */ + virtual bool inhibits_event_loop() = 0; + /** * Handle events until the plugin exits. The actual events are posted to * `main_context` to ensure that all operations to could potentially @@ -66,6 +80,9 @@ class HostBridge { * specific situation that can cause a race condition in some plugins * because of incorrect assumptions made by the plugin. See the dostring for * `Vst2Bridge::editor` for more information. + * + * TODO: We can get rid of this now, since we no longer have any special + * handling here */ virtual void handle_win32_events() = 0; diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 5373a8c6..4138fa09 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -285,6 +285,10 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context, }); } +bool Vst2Bridge::inhibits_event_loop() { + return !is_initialized; +} + void Vst2Bridge::run() { sockets.host_vst_dispatch.receive_events( std::nullopt, [&](Event& event, bool /*on_main_thread*/) { @@ -352,9 +356,19 @@ void Vst2Bridge::run() { if (unsafe_opcodes.contains(opcode)) { return main_context .run_in_context([&]() { - return dispatch_wrapper(plugin, opcode, - index, value, data, - option); + const intptr_t result = + dispatch_wrapper(plugin, opcode, index, + value, data, option); + + // The Win32 message loop will not be run up + // to this point to prevent plugins with + // partially initialized states from + // misbehaving + if (opcode == effOpen) { + is_initialized = true; + } + + return result; }) .get(); } else { diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 9d4ad387..5c747b39 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -59,6 +59,8 @@ class Vst2Bridge : public HostBridge { std::string plugin_dll_path, std::string endpoint_base_dir); + bool inhibits_event_loop() override; + /** * Here we'll handle incoming `dispatch()` messages until the sockets get * closed during `effClose()`. @@ -127,6 +129,12 @@ class Vst2Bridge : public HostBridge { */ AEffect* plugin; + /** + * Whether `effOpen()` has already been called. Used in + * `HostBridge::inhibits_event_loop` to work around a bug in T-RackS 5. + */ + bool is_initialized = false; + /** * The thread that responds to `getParameter` and `setParameter` requests. */ diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 97c171ab..ba5ede37 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -60,7 +60,11 @@ InstanceInterfaces::InstanceInterfaces( process_context_requirements(object), program_list_data(object), unit_info(object), - xml_representation_controller(object) {} + xml_representation_controller(object), + // If the object doesn't support `IPlugBase` then the object cannot be + // uninitialized (this isn't possible right now, but, who knows what the + // future might bring) + is_initialized(!plugin_base) {} Vst3Bridge::Vst3Bridge(MainContext& main_context, std::string plugin_dll_path, @@ -88,6 +92,18 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, main_context.update_timer_interval(config.event_loop_interval()); } +bool Vst3Bridge::inhibits_event_loop() { + std::lock_guard lock(object_instances_mutex); + + for (const auto& [instance_id, object] : object_instances) { + if (!object.is_initialized) { + return true; + } + } + + return false; +} + void Vst3Bridge::run() { // XXX: In theory all of thise should be safe assuming the host doesn't do // anything weird. We're using mutexes when inserting and removing @@ -837,11 +853,20 @@ void Vst3Bridge::run() { return main_context .run_in_context([&]() { // This static cast is required to upcast to `FUnknown*` - return object_instances[request.instance_id] - .plugin_base->initialize( - static_cast( - object_instances[request.instance_id] - .host_context_proxy)); + const tresult result = + object_instances[request.instance_id] + .plugin_base->initialize( + static_cast( + object_instances[request.instance_id] + .host_context_proxy)); + + // The Win32 message loop will not be run up to this + // point to prevent plugins with partially initialized + // states from misbehaving + object_instances[request.instance_id].is_initialized = + true; + + return result; }) .get(); }, diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index c5ab1c25..51f72e38 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -179,6 +179,15 @@ struct InstanceInterfaces { Steinberg::FUnknownPtr unit_info; Steinberg::FUnknownPtr xml_representation_controller; + + /** + * Whether `IPluginBase:initialize()` has already been called for this + * object instance. If the object doesn't implement `IPluginBase` then this + * will always be true. I haven't run into any VST3 plugins that have issues + * with partially initialized states like the VST2 versions of T-RackS 5 + * have, but we'll just do this out of precaution. + */ + bool is_initialized = false; }; /** @@ -209,6 +218,13 @@ class Vst3Bridge : public HostBridge { std::string plugin_dll_path, std::string endpoint_base_dir); + /** + * For VST3 plugins we'll have to check for every object in + * `object_instances` that supports `IPluginBase` whether + * `IPluginBase::iniitalize()` has been called. + */ + bool inhibits_event_loop() override; + /** * Here we'll listen for and handle incoming control messages until the * sockets get closed.