mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Run events async and centralized for group hosts
At a 30 fps rate with a limit on the number of window messages per frame. This is somehow needed for Melda plugins, as they otherwise dispatch tiemr messages indefinitely after opening a second editor with seemingly no way around it. With this and some refactoring #15 should be almost done.
This commit is contained in:
@@ -27,6 +27,13 @@
|
||||
// path operation will thrown an encoding related error
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
/**
|
||||
* The delay between calls to the event loop at a more than cinematic 30 fps.
|
||||
*/
|
||||
constexpr std::chrono::duration event_loop_interval = 1000ms / 30;
|
||||
|
||||
/**
|
||||
* Listen on the specified endpoint if no process is already listening there,
|
||||
* otherwise throw. This is needed to handle these three situations:
|
||||
@@ -98,6 +105,7 @@ GroupBridge::GroupBridge(boost::filesystem::path group_socket_path)
|
||||
group_socket_endpoint(group_socket_path.string()),
|
||||
group_socket_acceptor(
|
||||
create_acceptor_if_inactive(plugin_context, group_socket_endpoint)),
|
||||
events_timer(plugin_context),
|
||||
shutdown_timer(plugin_context) {
|
||||
// Write this process's original STDOUT and STDERR streams to the logger
|
||||
// TODO: This works for output generated by plugins, but not for debug
|
||||
@@ -114,19 +122,13 @@ GroupBridge::~GroupBridge() {
|
||||
}
|
||||
|
||||
void GroupBridge::handle_plugin_dispatch(const GroupRequest request) {
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
// At this point the `active_plugins` map will already contain the
|
||||
// intialized plugin's `Vst2Bridge` instance and this thread's handle
|
||||
auto& [thread, bridge] = active_plugins.at(request);
|
||||
|
||||
// Blocks the main thread until the plugin shuts down, handling all events
|
||||
// on the main IO context
|
||||
// TODO: Maybe add a function to each vstbridge that returns whether the
|
||||
// message loop should be postponed, and pass a function here that
|
||||
// checks if this is the case for any of the plugins?
|
||||
bridge->handle_dispatch_multi(plugin_context,
|
||||
[&]() { return should_skip_message_loop(); });
|
||||
// Blocks this thread until the plugin shuts down, handling all events on
|
||||
// the main IO context
|
||||
bridge->handle_dispatch_multi(plugin_context);
|
||||
logger.log("'" + request.plugin_path + "' has exited");
|
||||
|
||||
// After the plugin has exited, we'll remove this thread's plugin from the
|
||||
@@ -156,6 +158,7 @@ void GroupBridge::handle_plugin_dispatch(const GroupRequest request) {
|
||||
|
||||
void GroupBridge::handle_incoming_connections() {
|
||||
accept_requests();
|
||||
async_handle_events();
|
||||
|
||||
logger.log(
|
||||
"Group host is up and running, now accepting incoming connections");
|
||||
@@ -233,6 +236,49 @@ void GroupBridge::accept_requests() {
|
||||
});
|
||||
}
|
||||
|
||||
void GroupBridge::async_handle_events() {
|
||||
// Try to keep a steady framerate, but add in delays to let other events get
|
||||
// handled if the GUI message handling somehow takes very long.
|
||||
events_timer.expires_at(
|
||||
std::max(events_timer.expiry() + event_loop_interval,
|
||||
std::chrono::steady_clock::now() + 5ms));
|
||||
events_timer.async_wait([&](const boost::system::error_code& error) {
|
||||
if (error.failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
// Always handle X11 events
|
||||
std::lock_guard lock(active_plugins_mutex);
|
||||
for (auto& [parameters, value] : active_plugins) {
|
||||
auto& [thread, bridge] = value;
|
||||
bridge->handle_x11_events();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Win32 messages unless plugins are in the middle of opening
|
||||
// their editor
|
||||
// TODO: Check if those same weird crashes with Serum are happening
|
||||
// again with these normal threads
|
||||
if (!should_skip_message_loop()) {
|
||||
MSG msg;
|
||||
|
||||
// Keep the loop responsive by not handling too many events at once
|
||||
// TODO: For some reason the Melda plugins run into a seemingly
|
||||
// infinite timer loop for a little while after opening a
|
||||
// second editor. Without this limit everything will get
|
||||
// blocked indefinitely. How could this be fixed?
|
||||
for (int i = 0;
|
||||
i < 20 && PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); i++) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
async_handle_events();
|
||||
});
|
||||
}
|
||||
|
||||
void GroupBridge::async_log_pipe_lines(
|
||||
boost::asio::posix::stream_descriptor& pipe,
|
||||
boost::asio::streambuf& buffer,
|
||||
|
||||
@@ -183,6 +183,13 @@ class GroupBridge {
|
||||
*/
|
||||
void accept_requests();
|
||||
|
||||
/**
|
||||
* Handle both Win32 messages and X11 events on a timer within the IO
|
||||
* context. This is a centralized replacement for the event handling in
|
||||
* `Vst2Bridge::handle_dispatch_single` for plugin groups.
|
||||
*/
|
||||
void async_handle_events();
|
||||
|
||||
/**
|
||||
* Continuously read from a pipe and write the output to the log file. Used
|
||||
* with the IO streams captured by `stdout_redirect` and `stderr_redirect`.
|
||||
@@ -268,6 +275,14 @@ class GroupBridge {
|
||||
*/
|
||||
std::mutex active_plugins_mutex;
|
||||
|
||||
/**
|
||||
* A timer used to repeatedly handle the Win32 message loop and the X11
|
||||
* events.
|
||||
*
|
||||
8 @see async_handle_events
|
||||
*/
|
||||
boost::asio::steady_timer events_timer;
|
||||
|
||||
/**
|
||||
* A timer to defer shutting down the process, allowing for fast plugin
|
||||
* scanning without having to start a new group host process for each
|
||||
|
||||
@@ -16,9 +16,13 @@
|
||||
|
||||
#include "vst2.h"
|
||||
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
|
||||
#include "../../common/communication.h"
|
||||
#include "../../common/events.h"
|
||||
|
||||
/**
|
||||
* A function pointer to what should be the entry point of a VST plugin.
|
||||
*/
|
||||
@@ -156,12 +160,7 @@ void Vst2Bridge::handle_dispatch_single() {
|
||||
plugin, std::bind(&Vst2Bridge::dispatch_wrapper,
|
||||
this, _1, _2, _3, _4, _5, _6)));
|
||||
|
||||
// Don't run them message loop during the two step process of
|
||||
// opening the plugin editor since some plugins don't expect this
|
||||
if (!should_skip_message_loop()) {
|
||||
handle_win32_events();
|
||||
}
|
||||
|
||||
handle_win32_events();
|
||||
handle_x11_events();
|
||||
}
|
||||
} catch (const boost::system::system_error&) {
|
||||
@@ -170,6 +169,38 @@ void Vst2Bridge::handle_dispatch_single() {
|
||||
}
|
||||
}
|
||||
|
||||
void Vst2Bridge::handle_dispatch_multi(boost::asio::io_context& main_context) {
|
||||
// This works exactly the same as the function above, but execute the
|
||||
// actual event and run the message loop from the main thread that's
|
||||
// also instantiating these plugins. This is required for a few plugins
|
||||
// to run multiple instances in the same process
|
||||
try {
|
||||
while (true) {
|
||||
receive_event(
|
||||
host_vst_dispatch, std::nullopt,
|
||||
passthrough_event(
|
||||
plugin,
|
||||
[&](AEffect* plugin, int opcode, int index, intptr_t value,
|
||||
void* data, float option) -> intptr_t {
|
||||
std::promise<intptr_t> dispatch_result;
|
||||
boost::asio::dispatch(main_context, [&]() {
|
||||
const intptr_t result = dispatch_wrapper(
|
||||
plugin, opcode, index, value, data, option);
|
||||
|
||||
dispatch_result.set_value(result);
|
||||
});
|
||||
|
||||
// The message loop and X11 event handling will be run
|
||||
// separately on a timer
|
||||
return dispatch_result.get_future().get();
|
||||
}));
|
||||
}
|
||||
} catch (const boost::system::system_error&) {
|
||||
// The plugin has cut off communications, so we can shut down this
|
||||
// host application
|
||||
}
|
||||
}
|
||||
|
||||
void Vst2Bridge::handle_dispatch_midi_events() {
|
||||
try {
|
||||
while (true) {
|
||||
@@ -381,6 +412,12 @@ intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin,
|
||||
}
|
||||
|
||||
void Vst2Bridge::handle_win32_events() {
|
||||
// Don't run them message loop during the two step process of opening the
|
||||
// plugin editor since some plugins don't expect this
|
||||
if (should_skip_message_loop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (editor.has_value()) {
|
||||
editor->handle_win32_events();
|
||||
} else {
|
||||
|
||||
@@ -26,13 +26,9 @@
|
||||
#include <vestige/aeffectx.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/local/stream_protocol.hpp>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
|
||||
#include "../../common/communication.h"
|
||||
#include "../../common/events.h"
|
||||
#include "../../common/logging.h"
|
||||
#include "../editor.h"
|
||||
#include "../utils.h"
|
||||
@@ -101,50 +97,20 @@ class Vst2Bridge {
|
||||
*
|
||||
* @param main_context The main IO context that's handling the event
|
||||
* handling for all plugins.
|
||||
* @param message_loop_blocked A function that returns true if the message
|
||||
* loop is blocked. This is used to temporarily postpone running the
|
||||
* message loop while a plugin is opening its GUI.
|
||||
*
|
||||
* @note With this approach you'll have to make sure that the object was
|
||||
* instantiated from the same thread as the one that runs the IO context.
|
||||
* @note This appraoch does _not_ handle any events. This has to be done on
|
||||
* a timer within the IO context since otherwise things would become very
|
||||
* messy very quick.
|
||||
*/
|
||||
template <typename F = bool()>
|
||||
void handle_dispatch_multi(boost::asio::io_context& main_context,
|
||||
const F& message_loop_blocked) {
|
||||
// This works exactly the same as the function above, but execute the
|
||||
// actual event and run the message loop from the main thread that's
|
||||
// also instantiating these plugins. This is required for a few plugins
|
||||
// to run multiple instances in the same process
|
||||
try {
|
||||
while (true) {
|
||||
receive_event(
|
||||
host_vst_dispatch, std::nullopt,
|
||||
passthrough_event(
|
||||
plugin,
|
||||
[&](AEffect* plugin, int opcode, int index,
|
||||
intptr_t value, void* data,
|
||||
float option) -> intptr_t {
|
||||
std::promise<intptr_t> dispatch_result;
|
||||
boost::asio::dispatch(main_context, [&]() {
|
||||
const intptr_t result = dispatch_wrapper(
|
||||
plugin, opcode, index, value, data, option);
|
||||
void handle_dispatch_multi(boost::asio::io_context& main_context);
|
||||
|
||||
dispatch_result.set_value(result);
|
||||
if (!message_loop_blocked()) {
|
||||
handle_win32_events();
|
||||
}
|
||||
|
||||
handle_x11_events();
|
||||
});
|
||||
|
||||
return dispatch_result.get_future().get();
|
||||
}));
|
||||
}
|
||||
} catch (const boost::system::system_error&) {
|
||||
// The plugin has cut off communications, so we can shut down this
|
||||
// host application
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle X11 events for the editor window if it is open. This can be run
|
||||
* safely from any thread.
|
||||
*/
|
||||
void handle_x11_events();
|
||||
|
||||
// These functions are the entry points for the `*_handler` threads
|
||||
// defined below. They're defined here because we can't use lambdas with
|
||||
@@ -197,12 +163,6 @@ class Vst2Bridge {
|
||||
*/
|
||||
void handle_win32_events();
|
||||
|
||||
/**
|
||||
* Handle X11 events for the editor window if it is open. This can be run
|
||||
* safely from any thread.
|
||||
*/
|
||||
void handle_x11_events();
|
||||
|
||||
/**
|
||||
* The shared library handle of the VST plugin. I sadly could not get
|
||||
* Boost.DLL to work here, so we'll just load the VST plugisn by hand.
|
||||
|
||||
Reference in New Issue
Block a user