Always run the event loop, fixing processing issue

Also remove any special `effEditIdle` handling.

Apparently plugins rely on the message loop for their internal tasks,
even for things that have nothing to do with GUIs, such as deferring
initialization.
This commit is contained in:
Robbert van der Helm
2020-04-27 18:45:20 +02:00
parent e69d08c503
commit 4b84f663ab
4 changed files with 29 additions and 21 deletions
-2
View File
@@ -12,8 +12,6 @@ There are a few things that should be done before releasing this, including:
- Fix implementation bugs: - Fix implementation bugs:
- Serum crashed and audio engine froze while browsing through Serum presets in - Serum crashed and audio engine froze while browsing through Serum presets in
the browser? the browser?
- Plugins from certain manufacturers, such as KiloHearts or Image-Line, don't
do any audio processing until the editor has been opened.
- KiloHearts plugins create a ridiculous amount of file descriptor leaks in - KiloHearts plugins create a ridiculous amount of file descriptor leaks in
wineserver when esync is enabled. I haven't come across any other plugins wineserver when esync is enabled. I haven't come across any other plugins
that do this. Not sure if this is fixable in yabridge. that do this. Not sure if this is fixable in yabridge.
+7 -6
View File
@@ -131,18 +131,18 @@ void Editor::send_idle_event() {
} }
void Editor::handle_events() { void Editor::handle_events() {
send_idle_event(); MSG msg;
// The null value for the second argument is needed to handle interaction // The null value for the second argument is needed to handle interaction
// with child GUI components // with child GUI components
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
// This timer would periodically send `effEditIdle` events so the editor // This timer would periodically send `effEditIdle` events so the editor
// remains responsive even during blocking GUI operations such as open // remains responsive even during blocking GUI operations such as open
// dropdowns or message boxes. We filter it out here because we will // dropdowns or message boxes. This is only needed when the GUI is
// send sent the event manually every time the host calls // actually blocked and it will be dispatched by the messaging loop of
// `effEditIdle()`. It will still be fired implicitely when the GUI // the blocking GUI component. Since we're not touching the
// thread gets blocked. // `effEditIdle` event sent by the host we can always filter this timer
// event out in this event loop.
if (msg.message == WM_TIMER && msg.wParam == idle_timer_id && if (msg.message == WM_TIMER && msg.wParam == idle_timer_id &&
msg.hwnd == win32_handle.get()) { msg.hwnd == win32_handle.get()) {
continue; continue;
@@ -201,6 +201,7 @@ void Editor::handle_events() {
xcb_flush(x11_connection.get()); xcb_flush(x11_connection.get());
} break; } break;
} }
free(generic_event); free(generic_event);
} }
} }
+2 -2
View File
@@ -88,14 +88,14 @@ class Editor {
/** /**
* Send a single `effEditIdle` event to the plugin to allow it to update its * Send a single `effEditIdle` event to the plugin to allow it to update its
* GUI state. This is called periodically from a timer while the GUI is * GUI state. This is called periodically from a timer while the GUI is
* being blocked. * being blocked, and also called explicitly by the host on a timer.
*/ */
void send_idle_event(); void send_idle_event();
/** /**
* Pump messages from the editor GUI's event loop until all events are * Pump messages from the editor GUI's event loop until all events are
* process. Must be run from the same thread the GUI was created in because * process. Must be run from the same thread the GUI was created in because
* of Win32 limitations. I guess that's what `effEditIdle` is for. * of Win32 limitations.
*/ */
void handle_events(); void handle_events();
+20 -11
View File
@@ -150,6 +150,26 @@ void PluginBridge::handle_dispatch() {
passthrough_event(host_vst_dispatch, std::nullopt, plugin, passthrough_event(host_vst_dispatch, std::nullopt, plugin,
std::bind(&PluginBridge::dispatch_wrapper, this, std::bind(&PluginBridge::dispatch_wrapper, this,
_1, _2, _3, _4, _5, _6)); _1, _2, _3, _4, _5, _6));
// Because of the way the Win32 API works we have to process events
// on the same thread as the one the window was created on, and that
// thread is the thread that's handling dispatcher calls.
if (editor.has_value()) {
// This will handle Win32 events similar to the loop below, and
// it will also handle any X11 events.
editor->handle_events();
} else {
MSG msg;
// Since some plugins rely on the Win32 message API even for
// non-editor related tasks (such as deferring the loading of
// presets using a timer), we have to run a message loop even
// when the editor is closed.
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
} }
} catch (const boost::system::system_error&) { } catch (const boost::system::system_error&) {
// The plugin has cut off communications, so we can shut down this host // The plugin has cut off communications, so we can shut down this host
@@ -286,17 +306,6 @@ intptr_t PluginBridge::dispatch_wrapper(AEffect* plugin,
// We have to intercept GUI open calls since we can't use // We have to intercept GUI open calls since we can't use
// the X11 window handle passed by the host // the X11 window handle passed by the host
switch (opcode) { switch (opcode) {
case effEditIdle:
// Because of the way the Win32 API works we have to process events
// on the same thread the window was created, and that thread is the
// thread that's handling dispatcher calls
// To allow the GUI to update even when this thread gets blocked
// (e.g. when a dropdown is open), the actual `effEditIdle` event
// gets sent to the plugin on a timer.
editor->handle_events();
return 1;
break;
case effEditOpen: { case effEditOpen: {
// Create a Win32 window through Wine, embed it into the window // Create a Win32 window through Wine, embed it into the window
// provided by the host, and let the plugin embed itself into the // provided by the host, and let the plugin embed itself into the