mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
Handle dispatch() directly during event handling
When the message loop is active and we get an incoming dispatch() event, we'll just handle it directly. In practice this would only be needed when the event is a response to an `audioMaster()` call made during the event loop, but we can't know that. This allows the `getProgram()` during `audioMasterUpdateDisplay()` in REAPER and Renoise to work correctly. Hopefully this doesn't cause random rare breakage.
This commit is contained in:
@@ -364,7 +364,7 @@ class EventHandler {
|
||||
// `next_plugin_id` in `GroupBridge`
|
||||
std::map<size_t, std::jthread> active_secondary_requests{};
|
||||
std::atomic_size_t next_request_id{};
|
||||
std::mutex active_secondary_requests_mutex;
|
||||
std::mutex active_secondary_requests_mutex{};
|
||||
accept_requests(
|
||||
*acceptor, logging,
|
||||
[&](boost::asio::local::stream_protocol::socket secondary_socket) {
|
||||
|
||||
@@ -29,11 +29,6 @@ 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:
|
||||
@@ -94,10 +89,9 @@ GroupBridge::GroupBridge(boost::filesystem::path group_socket_path)
|
||||
stdout_redirect(stdio_context, STDOUT_FILENO),
|
||||
stderr_redirect(stdio_context, STDERR_FILENO),
|
||||
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) {
|
||||
group_socket_acceptor(create_acceptor_if_inactive(plugin_context.context,
|
||||
group_socket_endpoint)),
|
||||
shutdown_timer(plugin_context.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
|
||||
// messages generated by wineserver. Is it possible to catch those?
|
||||
@@ -131,7 +125,7 @@ void GroupBridge::handle_plugin_dispatch(size_t plugin_id) {
|
||||
// potentially corrupt our heap. This way we can also properly join the
|
||||
// thread again. If no active plugins remain, then we'll terminate the
|
||||
// process.
|
||||
boost::asio::post(plugin_context, [this, plugin_id]() {
|
||||
boost::asio::post(plugin_context.context, [this, plugin_id]() {
|
||||
std::lock_guard lock(active_plugins_mutex);
|
||||
|
||||
// The join is implicit because we're using std::jthread
|
||||
@@ -240,16 +234,7 @@ 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;
|
||||
}
|
||||
|
||||
plugin_context.async_handle_events([&]() {
|
||||
{
|
||||
// Always handle X11 events
|
||||
std::lock_guard lock(active_plugins_mutex);
|
||||
@@ -279,8 +264,6 @@ void GroupBridge::async_handle_events() {
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
async_handle_events();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ class GroupBridge {
|
||||
* operations that may involve the Win32 mesasge loop (e.g. initialization
|
||||
* and most `AEffect::dispatcher()` calls) should be run on.
|
||||
*/
|
||||
boost::asio::io_context plugin_context;
|
||||
PluginContext plugin_context;
|
||||
/**
|
||||
* A seperate IO context that handles the STDIO redirect through
|
||||
* `StdIoCapture`. This is seperated the `plugin_context` above so that
|
||||
@@ -279,14 +279,6 @@ 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
|
||||
|
||||
@@ -64,13 +64,13 @@ Vst2Bridge& get_bridge_instance(const AEffect* plugin) {
|
||||
return *static_cast<Vst2Bridge*>(plugin->ptr1);
|
||||
}
|
||||
|
||||
Vst2Bridge::Vst2Bridge(boost::asio::io_context& main_context,
|
||||
Vst2Bridge::Vst2Bridge(PluginContext& main_context,
|
||||
std::string plugin_dll_path,
|
||||
std::string endpoint_base_dir)
|
||||
: vst_plugin_path(plugin_dll_path),
|
||||
io_context(main_context),
|
||||
plugin_context(main_context),
|
||||
plugin_handle(LoadLibrary(plugin_dll_path.c_str()), FreeLibrary),
|
||||
sockets(io_context, endpoint_base_dir, false) {
|
||||
sockets(plugin_context.context, endpoint_base_dir, false) {
|
||||
// Got to love these C APIs
|
||||
if (!plugin_handle) {
|
||||
throw std::runtime_error("Could not load the Windows .dll file at '" +
|
||||
@@ -158,13 +158,14 @@ void Vst2Bridge::handle_dispatch() {
|
||||
// `plugin->dispatcher()` (or `dispatch_wrapper()`)
|
||||
// directly, we'll run the function within the IO context so
|
||||
// all events will be executed on the same thread as the one
|
||||
// that runs the Win32 message loop. We'll assume every
|
||||
// event that comes in while the main thread is already
|
||||
// handling an event in the IO context can safely be handled
|
||||
// on an off thread.
|
||||
if (on_main_thread) {
|
||||
// that runs the Win32 message loop. In some scenarios we'll
|
||||
// receive incoming calls from multiple threads or we'll
|
||||
// receive calls while we're currently stuck in the Win32
|
||||
// message loop. In those cases we'll assume that these
|
||||
// events can be safely handled directlyfrom another thread.
|
||||
if (on_main_thread && !plugin_context.event_loop_active) {
|
||||
std::promise<intptr_t> dispatch_result;
|
||||
boost::asio::dispatch(io_context, [&]() {
|
||||
boost::asio::dispatch(plugin_context.context, [&]() {
|
||||
const intptr_t result = dispatch_wrapper(
|
||||
plugin, opcode, index, value, data, option);
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ class Vst2Bridge {
|
||||
* @throw std::runtime_error Thrown when the VST plugin could not be loaded,
|
||||
* or if communication could not be set up.
|
||||
*/
|
||||
Vst2Bridge(boost::asio::io_context& main_context,
|
||||
Vst2Bridge(PluginContext& main_context,
|
||||
std::string plugin_dll_path,
|
||||
std::string endpoint_base_dir);
|
||||
|
||||
@@ -172,7 +172,7 @@ class Vst2Bridge {
|
||||
* message handling can be performed from a single thread, even when hosting
|
||||
* multiple plugins.
|
||||
*/
|
||||
boost::asio::io_context& io_context;
|
||||
PluginContext& plugin_context;
|
||||
|
||||
/**
|
||||
* The configuration for this instance of yabridge based on the `.so` file
|
||||
|
||||
@@ -24,19 +24,6 @@
|
||||
#include "../common/utils.h"
|
||||
#include "bridges/vst2.h"
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Handle both Win32 and X11 events on a timer. This is more or less a
|
||||
* simplified version of `GroupBridge::async_handle_events`.
|
||||
*/
|
||||
void async_handle_events(boost::asio::steady_timer& timer, Vst2Bridge& bridge);
|
||||
|
||||
/**
|
||||
* This is the default VST host application. It will load the specified VST2
|
||||
* plugin, and then connect back to the `libyabridge.so` instance that spawned
|
||||
@@ -80,10 +67,10 @@ int __cdecl main(int argc, char* argv[]) {
|
||||
// setup is slightly more convoluted than it has to be, but doing it this
|
||||
// way we don't need to differentiate between individually hosted plugins
|
||||
// and plugin groups when it comes to event handling.
|
||||
boost::asio::io_context io_context{};
|
||||
PluginContext plugin_context{};
|
||||
std::unique_ptr<Vst2Bridge> bridge;
|
||||
try {
|
||||
bridge = std::make_unique<Vst2Bridge>(io_context, plugin_dll_path,
|
||||
bridge = std::make_unique<Vst2Bridge>(plugin_context, plugin_dll_path,
|
||||
socket_endpoint_path);
|
||||
} catch (const std::runtime_error& error) {
|
||||
std::cerr << "Error while initializing Wine VST host:" << std::endl;
|
||||
@@ -101,25 +88,9 @@ int __cdecl main(int argc, char* argv[]) {
|
||||
|
||||
// Handle Win32 messages and X11 events on a timer, just like in
|
||||
// `GroupBridge::async_handle_events()``
|
||||
boost::asio::steady_timer events_timer(io_context);
|
||||
async_handle_events(events_timer, *bridge);
|
||||
|
||||
io_context.run();
|
||||
}
|
||||
|
||||
void async_handle_events(boost::asio::steady_timer& timer, Vst2Bridge& bridge) {
|
||||
// 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.
|
||||
timer.expires_at(std::max(timer.expiry() + event_loop_interval,
|
||||
std::chrono::steady_clock::now() + 5ms));
|
||||
timer.async_wait([&](const boost::system::error_code& error) {
|
||||
if (error.failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bridge.handle_x11_events();
|
||||
bridge.handle_win32_events();
|
||||
|
||||
async_handle_events(timer, bridge);
|
||||
plugin_context.async_handle_events([&]() {
|
||||
bridge->handle_x11_events();
|
||||
bridge->handle_win32_events();
|
||||
});
|
||||
plugin_context.run();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,16 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
PluginContext::PluginContext() : context(), events_timer(context) {}
|
||||
|
||||
void PluginContext::run() {
|
||||
context.run();
|
||||
}
|
||||
|
||||
void PluginContext::stop() {
|
||||
context.stop();
|
||||
}
|
||||
|
||||
Win32Thread::Win32Thread() : handle(nullptr, nullptr) {}
|
||||
|
||||
Win32Timer::Win32Timer(HWND window_handle,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "boost-fix.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
@@ -26,6 +28,91 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
/**
|
||||
* The delay between calls to the event loop at an even more than cinematic 30
|
||||
* fps.
|
||||
*/
|
||||
constexpr std::chrono::duration event_loop_interval =
|
||||
std::chrono::milliseconds(1000) / 30;
|
||||
|
||||
/**
|
||||
* A wrapper around `boost::asio::io_context()`. A single instance is shared for
|
||||
* all plugins in a plugin group so that most events can be handled on the main
|
||||
* thread, which can be required because all GUI related operations have to be
|
||||
* handled from the same thread. If during the Win32 message loop the plugin
|
||||
* performs a host callback and the host then calls a function on the plugin in
|
||||
* response, then this IO context will still be busy with the message loop
|
||||
* which. To prevent a deadlock in this situation, we'll allow different threads
|
||||
* to handle `dispatch()` calls while the message loop is running.
|
||||
*/
|
||||
class PluginContext {
|
||||
public:
|
||||
PluginContext();
|
||||
|
||||
/**
|
||||
* Run the IO context. This rest of this class assumes that this is only
|
||||
* done from a single thread.
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* Drop all future work from the IO context. This does not necessarily mean
|
||||
* that the thread that called `plugin_context.run()` immediatly returns.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Start a timer to handle events every `event_loop_interval` milliseconds.
|
||||
* `message_loop_active()` will return `true` while `handler` is being
|
||||
* executed.
|
||||
*
|
||||
* @param handler The function that should be executed in the IO context
|
||||
* when the timer ticks. This should be a function that handles both the
|
||||
* X11 events and the Win32 message loop.
|
||||
*/
|
||||
template <typename F>
|
||||
void async_handle_events(F handler) {
|
||||
// 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() + std::chrono::milliseconds(5)));
|
||||
events_timer.async_wait(
|
||||
[&, handler](const boost::system::error_code& error) {
|
||||
if (error.failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
event_loop_active = true;
|
||||
handler();
|
||||
event_loop_active = false;
|
||||
|
||||
async_handle_events(handler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Is `true` if the context is currently handling the Win32 message loop and
|
||||
* incoming `dispatch()` events should be handled on their own thread (as
|
||||
* posting them to the IO context will thus block).
|
||||
*/
|
||||
std::atomic_bool event_loop_active;
|
||||
|
||||
/**
|
||||
* The raw IO context. Can and should be used directly for everything that's
|
||||
* not the event handling loop.
|
||||
*/
|
||||
boost::asio::io_context context;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The timer used to periodically handle X11 events and Win32 messages.
|
||||
*/
|
||||
boost::asio::steady_timer events_timer;
|
||||
};
|
||||
|
||||
/**
|
||||
* A simple RAII wrapper around the Win32 thread API.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user