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.
This commit is contained in:
Robbert van der Helm
2021-01-27 18:46:36 +01:00
parent 04d0ff0949
commit 72e29d044a
5 changed files with 89 additions and 9 deletions
+17
View File
@@ -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;
+17 -3
View File
@@ -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<intptr_t>([&]() {
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 {
+8
View File
@@ -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.
*/
+31 -6
View File
@@ -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<tresult>([&]() {
// This static cast is required to upcast to `FUnknown*`
return object_instances[request.instance_id]
.plugin_base->initialize(
static_cast<YaHostApplication*>(
object_instances[request.instance_id]
.host_context_proxy));
const tresult result =
object_instances[request.instance_id]
.plugin_base->initialize(
static_cast<YaHostApplication*>(
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();
},
+16
View File
@@ -179,6 +179,15 @@ struct InstanceInterfaces {
Steinberg::FUnknownPtr<Steinberg::Vst::IUnitInfo> unit_info;
Steinberg::FUnknownPtr<Steinberg::Vst::IXmlRepresentationController>
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.