mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-06-15 16:03:55 +02:00
Replace all threads with Win32Thread in Wine host
This commit is contained in:
@@ -81,100 +81,6 @@ intptr_t DefaultDataConverter::return_value(const int /*opcode*/,
|
|||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
EventHandler::EventHandler(
|
|
||||||
boost::asio::io_context& io_context,
|
|
||||||
boost::asio::local::stream_protocol::endpoint endpoint,
|
|
||||||
bool listen)
|
|
||||||
: io_context(io_context), endpoint(endpoint), socket(io_context) {
|
|
||||||
if (listen) {
|
|
||||||
fs::create_directories(fs::path(endpoint.path()).parent_path());
|
|
||||||
acceptor.emplace(io_context, endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventHandler::connect() {
|
|
||||||
if (acceptor) {
|
|
||||||
acceptor->accept(socket);
|
|
||||||
|
|
||||||
// As mentioned in `acceptor's` docstring, this acceptor will be
|
|
||||||
// recreated in `receive()` on another context, and potentially on the
|
|
||||||
// other side of the connection in the case of `vst_host_callback`
|
|
||||||
acceptor.reset();
|
|
||||||
fs::remove(endpoint.path());
|
|
||||||
} else {
|
|
||||||
socket.connect(endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventHandler::close() {
|
|
||||||
socket.shutdown(boost::asio::local::stream_protocol::socket::shutdown_both);
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Sockets::Sockets(boost::asio::io_context& io_context,
|
|
||||||
const boost::filesystem::path& endpoint_base_dir,
|
|
||||||
bool listen)
|
|
||||||
: base_dir(endpoint_base_dir),
|
|
||||||
host_vst_dispatch(io_context,
|
|
||||||
(base_dir / "host_vst_dispatch.sock").string(),
|
|
||||||
listen),
|
|
||||||
host_vst_dispatch_midi_events(
|
|
||||||
io_context,
|
|
||||||
(base_dir / "host_vst_dispatch_midi_events.sock").string(),
|
|
||||||
listen),
|
|
||||||
vst_host_callback(io_context,
|
|
||||||
(base_dir / "vst_host_callback.sock").string(),
|
|
||||||
listen),
|
|
||||||
host_vst_parameters(io_context),
|
|
||||||
host_vst_process_replacing(io_context),
|
|
||||||
host_vst_control(io_context),
|
|
||||||
host_vst_parameters_endpoint(
|
|
||||||
(base_dir / "host_vst_parameters.sock").string()),
|
|
||||||
host_vst_process_replacing_endpoint(
|
|
||||||
(base_dir / "host_vst_process_replacing.sock").string()),
|
|
||||||
host_vst_control_endpoint((base_dir / "host_vst_control.sock").string()) {
|
|
||||||
if (listen) {
|
|
||||||
fs::create_directories(base_dir);
|
|
||||||
|
|
||||||
acceptors = Acceptors{
|
|
||||||
.host_vst_parameters{io_context, host_vst_parameters_endpoint},
|
|
||||||
.host_vst_process_replacing{io_context,
|
|
||||||
host_vst_process_replacing_endpoint},
|
|
||||||
.host_vst_control{io_context, host_vst_control_endpoint},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Sockets::~Sockets() {
|
|
||||||
// Only clean if we're the ones who have created these files, although it
|
|
||||||
// should not cause any harm to also do this on the Wine side
|
|
||||||
if (acceptors) {
|
|
||||||
try {
|
|
||||||
fs::remove_all(base_dir);
|
|
||||||
} catch (const fs::filesystem_error&) {
|
|
||||||
// There should not be any filesystem errors since only one side
|
|
||||||
// removes the files, but if we somehow can't delete the file then
|
|
||||||
// we can just silently ignore this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Sockets::connect() {
|
|
||||||
host_vst_dispatch.connect();
|
|
||||||
host_vst_dispatch_midi_events.connect();
|
|
||||||
vst_host_callback.connect();
|
|
||||||
if (acceptors) {
|
|
||||||
acceptors->host_vst_parameters.accept(host_vst_parameters);
|
|
||||||
acceptors->host_vst_process_replacing.accept(
|
|
||||||
host_vst_process_replacing);
|
|
||||||
acceptors->host_vst_control.accept(host_vst_control);
|
|
||||||
} else {
|
|
||||||
host_vst_parameters.connect(host_vst_parameters_endpoint);
|
|
||||||
host_vst_process_replacing.connect(host_vst_process_replacing_endpoint);
|
|
||||||
host_vst_control.connect(host_vst_control_endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::filesystem::path generate_endpoint_base(const std::string& plugin_name) {
|
boost::filesystem::path generate_endpoint_base(const std::string& plugin_name) {
|
||||||
fs::path temp_directory = get_temporary_directory();
|
fs::path temp_directory = get_temporary_directory();
|
||||||
|
|
||||||
|
|||||||
+103
-15
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include <bitsery/adapter/buffer.h>
|
#include <bitsery/adapter/buffer.h>
|
||||||
#include <bitsery/bitsery.h>
|
#include <bitsery/bitsery.h>
|
||||||
@@ -204,7 +203,11 @@ class DefaultDataConverter {
|
|||||||
* above. Similarly, the `EventHandler::receive()` method first sets up
|
* above. Similarly, the `EventHandler::receive()` method first sets up
|
||||||
* asynchronous listeners for the socket endpoint, and then block and handle
|
* asynchronous listeners for the socket endpoint, and then block and handle
|
||||||
* events until the main socket is closed.
|
* events until the main socket is closed.
|
||||||
|
*
|
||||||
|
* @tparam Thread The thread implementation to use. On the Linux side this
|
||||||
|
* should be `std::jthread` and on the Wine side this should be `Win32Thread`.
|
||||||
*/
|
*/
|
||||||
|
template <typename Thread>
|
||||||
class EventHandler {
|
class EventHandler {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -223,20 +226,44 @@ class EventHandler {
|
|||||||
*/
|
*/
|
||||||
EventHandler(boost::asio::io_context& io_context,
|
EventHandler(boost::asio::io_context& io_context,
|
||||||
boost::asio::local::stream_protocol::endpoint endpoint,
|
boost::asio::local::stream_protocol::endpoint endpoint,
|
||||||
bool listen);
|
bool listen)
|
||||||
|
: io_context(io_context), endpoint(endpoint), socket(io_context) {
|
||||||
|
if (listen) {
|
||||||
|
boost::filesystem::create_directories(
|
||||||
|
boost::filesystem::path(endpoint.path()).parent_path());
|
||||||
|
acceptor.emplace(io_context, endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Depending on the value of the `listen` argument passed to the
|
* Depending on the value of the `listen` argument passed to the
|
||||||
* constructor, either accept connections made to the sockets on the Linux
|
* constructor, either accept connections made to the sockets on the Linux
|
||||||
* side or connect to the sockets on the Wine side
|
* side or connect to the sockets on the Wine side
|
||||||
*/
|
*/
|
||||||
void connect();
|
void connect() {
|
||||||
|
if (acceptor) {
|
||||||
|
acceptor->accept(socket);
|
||||||
|
|
||||||
|
// As mentioned in `acceptor's` docstring, this acceptor will be
|
||||||
|
// recreated in `receive()` on another context, and potentially on
|
||||||
|
// the other side of the connection in the case of
|
||||||
|
// `vst_host_callback`
|
||||||
|
acceptor.reset();
|
||||||
|
boost::filesystem::remove(endpoint.path());
|
||||||
|
} else {
|
||||||
|
socket.connect(endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the socket. Both sides that are actively listening will be thrown a
|
* Close the socket. Both sides that are actively listening will be thrown a
|
||||||
* `boost::system_error` when this happens.
|
* `boost::system_error` when this happens.
|
||||||
*/
|
*/
|
||||||
void close();
|
void close() {
|
||||||
|
socket.shutdown(
|
||||||
|
boost::asio::local::stream_protocol::socket::shutdown_both);
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize and send an event over a socket. This is used for both the host
|
* Serialize and send an event over a socket. This is used for both the host
|
||||||
@@ -384,7 +411,7 @@ class EventHandler {
|
|||||||
|
|
||||||
// This works the exact same was as `active_plugins` and
|
// This works the exact same was as `active_plugins` and
|
||||||
// `next_plugin_id` in `GroupBridge`
|
// `next_plugin_id` in `GroupBridge`
|
||||||
std::map<size_t, std::jthread> active_secondary_requests{};
|
std::map<size_t, Thread> active_secondary_requests{};
|
||||||
std::atomic_size_t next_request_id{};
|
std::atomic_size_t next_request_id{};
|
||||||
std::mutex active_secondary_requests_mutex{};
|
std::mutex active_secondary_requests_mutex{};
|
||||||
accept_requests(
|
accept_requests(
|
||||||
@@ -395,7 +422,7 @@ class EventHandler {
|
|||||||
// We have to make sure to keep moving these sockets into the
|
// We have to make sure to keep moving these sockets into the
|
||||||
// threads that will handle them
|
// threads that will handle them
|
||||||
std::lock_guard lock(active_secondary_requests_mutex);
|
std::lock_guard lock(active_secondary_requests_mutex);
|
||||||
active_secondary_requests[request_id] = std::jthread(
|
active_secondary_requests[request_id] = Thread(
|
||||||
[&, request_id](boost::asio::local::stream_protocol::socket
|
[&, request_id](boost::asio::local::stream_protocol::socket
|
||||||
secondary_socket) {
|
secondary_socket) {
|
||||||
// TODO: Factor this out
|
// TODO: Factor this out
|
||||||
@@ -427,15 +454,14 @@ class EventHandler {
|
|||||||
active_secondary_requests_mutex);
|
active_secondary_requests_mutex);
|
||||||
|
|
||||||
// The join is implicit because we're using
|
// The join is implicit because we're using
|
||||||
// std::jthread
|
// std::jthread/Win32Thread
|
||||||
active_secondary_requests.erase(request_id);
|
active_secondary_requests.erase(request_id);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
std::move(secondary_socket));
|
std::move(secondary_socket));
|
||||||
});
|
});
|
||||||
|
|
||||||
std::jthread secondary_requests_handler(
|
Thread secondary_requests_handler([&]() { secondary_context.run(); });
|
||||||
[&]() { secondary_context.run(); });
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
@@ -555,7 +581,11 @@ class EventHandler {
|
|||||||
* `true` before launching the Wine VST host. This will start listening on the
|
* `true` before launching the Wine VST host. This will start listening on the
|
||||||
* sockets, and the call to `connect()` will then accept any incoming
|
* sockets, and the call to `connect()` will then accept any incoming
|
||||||
* connections.
|
* connections.
|
||||||
|
*
|
||||||
|
* @tparam Thread The thread implementation to use. On the Linux side this
|
||||||
|
* should be `std::jthread` and on the Wine side this should be `Win32Thread`.
|
||||||
*/
|
*/
|
||||||
|
template <typename Thread>
|
||||||
class Sockets {
|
class Sockets {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -574,20 +604,78 @@ class Sockets {
|
|||||||
*/
|
*/
|
||||||
Sockets(boost::asio::io_context& io_context,
|
Sockets(boost::asio::io_context& io_context,
|
||||||
const boost::filesystem::path& endpoint_base_dir,
|
const boost::filesystem::path& endpoint_base_dir,
|
||||||
bool listen);
|
bool listen)
|
||||||
|
: base_dir(endpoint_base_dir),
|
||||||
|
host_vst_dispatch(io_context,
|
||||||
|
(base_dir / "host_vst_dispatch.sock").string(),
|
||||||
|
listen),
|
||||||
|
host_vst_dispatch_midi_events(
|
||||||
|
io_context,
|
||||||
|
(base_dir / "host_vst_dispatch_midi_events.sock").string(),
|
||||||
|
listen),
|
||||||
|
vst_host_callback(io_context,
|
||||||
|
(base_dir / "vst_host_callback.sock").string(),
|
||||||
|
listen),
|
||||||
|
host_vst_parameters(io_context),
|
||||||
|
host_vst_process_replacing(io_context),
|
||||||
|
host_vst_control(io_context),
|
||||||
|
host_vst_parameters_endpoint(
|
||||||
|
(base_dir / "host_vst_parameters.sock").string()),
|
||||||
|
host_vst_process_replacing_endpoint(
|
||||||
|
(base_dir / "host_vst_process_replacing.sock").string()),
|
||||||
|
host_vst_control_endpoint(
|
||||||
|
(base_dir / "host_vst_control.sock").string()) {
|
||||||
|
if (listen) {
|
||||||
|
boost::filesystem::create_directories(base_dir);
|
||||||
|
|
||||||
|
acceptors = Acceptors{
|
||||||
|
.host_vst_parameters{io_context, host_vst_parameters_endpoint},
|
||||||
|
.host_vst_process_replacing{
|
||||||
|
io_context, host_vst_process_replacing_endpoint},
|
||||||
|
.host_vst_control{io_context, host_vst_control_endpoint},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans up the directory containing the socket endpoints when yabridge
|
* Cleans up the directory containing the socket endpoints when yabridge
|
||||||
* shuts down if it still exists.
|
* shuts down if it still exists.
|
||||||
*/
|
*/
|
||||||
~Sockets();
|
~Sockets() {
|
||||||
|
// Only clean if we're the ones who have created these files, although
|
||||||
|
// it should not cause any harm to also do this on the Wine side
|
||||||
|
if (acceptors) {
|
||||||
|
try {
|
||||||
|
boost::filesystem::remove_all(base_dir);
|
||||||
|
} catch (const boost::filesystem::filesystem_error&) {
|
||||||
|
// There should not be any filesystem errors since only one side
|
||||||
|
// removes the files, but if we somehow can't delete the file
|
||||||
|
// then we can just silently ignore this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Depending on the value of the `listen` argument passed to the
|
* Depending on the value of the `listen` argument passed to the
|
||||||
* constructor, either accept connections made to the sockets on the Linux
|
* constructor, either accept connections made to the sockets on the Linux
|
||||||
* side or connect to the sockets on the Wine side
|
* side or connect to the sockets on the Wine side
|
||||||
*/
|
*/
|
||||||
void connect();
|
void connect() {
|
||||||
|
host_vst_dispatch.connect();
|
||||||
|
host_vst_dispatch_midi_events.connect();
|
||||||
|
vst_host_callback.connect();
|
||||||
|
if (acceptors) {
|
||||||
|
acceptors->host_vst_parameters.accept(host_vst_parameters);
|
||||||
|
acceptors->host_vst_process_replacing.accept(
|
||||||
|
host_vst_process_replacing);
|
||||||
|
acceptors->host_vst_control.accept(host_vst_control);
|
||||||
|
} else {
|
||||||
|
host_vst_parameters.connect(host_vst_parameters_endpoint);
|
||||||
|
host_vst_process_replacing.connect(
|
||||||
|
host_vst_process_replacing_endpoint);
|
||||||
|
host_vst_control.connect(host_vst_control_endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base directory for our socket endpoints. All `*_endpoint` variables
|
* The base directory for our socket endpoints. All `*_endpoint` variables
|
||||||
@@ -604,19 +692,19 @@ class Sockets {
|
|||||||
* The socket that forwards all `dispatcher()` calls from the VST host to
|
* The socket that forwards all `dispatcher()` calls from the VST host to
|
||||||
* the plugin.
|
* the plugin.
|
||||||
*/
|
*/
|
||||||
EventHandler host_vst_dispatch;
|
EventHandler<Thread> host_vst_dispatch;
|
||||||
/**
|
/**
|
||||||
* Used specifically for the `effProcessEvents` opcode. This is needed
|
* Used specifically for the `effProcessEvents` opcode. This is needed
|
||||||
* because the Win32 API is designed to block during certain GUI
|
* because the Win32 API is designed to block during certain GUI
|
||||||
* interactions such as resizing a window or opening a dropdown. Without
|
* interactions such as resizing a window or opening a dropdown. Without
|
||||||
* this MIDI input would just stop working at times.
|
* this MIDI input would just stop working at times.
|
||||||
*/
|
*/
|
||||||
EventHandler host_vst_dispatch_midi_events;
|
EventHandler<Thread> host_vst_dispatch_midi_events;
|
||||||
/**
|
/**
|
||||||
* The socket that forwards all `audioMaster()` calls from the Windows VST
|
* The socket that forwards all `audioMaster()` calls from the Windows VST
|
||||||
* plugin to the host.
|
* plugin to the host.
|
||||||
*/
|
*/
|
||||||
EventHandler vst_host_callback;
|
EventHandler<Thread> vst_host_callback;
|
||||||
/**
|
/**
|
||||||
* Used for both `getParameter` and `setParameter` since they mostly
|
* Used for both `getParameter` and `setParameter` since they mostly
|
||||||
* overlap.
|
* overlap.
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ void HostProcess::async_log_pipe_lines(patched_async_pipe& pipe,
|
|||||||
IndividualHost::IndividualHost(boost::asio::io_context& io_context,
|
IndividualHost::IndividualHost(boost::asio::io_context& io_context,
|
||||||
Logger& logger,
|
Logger& logger,
|
||||||
fs::path plugin_path,
|
fs::path plugin_path,
|
||||||
const Sockets& sockets)
|
const Sockets<std::jthread>& sockets)
|
||||||
: HostProcess(io_context, logger),
|
: HostProcess(io_context, logger),
|
||||||
plugin_arch(find_vst_architecture(plugin_path)),
|
plugin_arch(find_vst_architecture(plugin_path)),
|
||||||
host_path(find_vst_host(plugin_arch, false)),
|
host_path(find_vst_host(plugin_arch, false)),
|
||||||
@@ -130,7 +130,7 @@ void IndividualHost::terminate() {
|
|||||||
GroupHost::GroupHost(boost::asio::io_context& io_context,
|
GroupHost::GroupHost(boost::asio::io_context& io_context,
|
||||||
Logger& logger,
|
Logger& logger,
|
||||||
fs::path plugin_path,
|
fs::path plugin_path,
|
||||||
Sockets& sockets,
|
Sockets<std::jthread>& sockets,
|
||||||
std::string group_name)
|
std::string group_name)
|
||||||
: HostProcess(io_context, logger),
|
: HostProcess(io_context, logger),
|
||||||
plugin_arch(find_vst_architecture(plugin_path)),
|
plugin_arch(find_vst_architecture(plugin_path)),
|
||||||
|
|||||||
@@ -25,8 +25,8 @@
|
|||||||
#include <boost/process/child.hpp>
|
#include <boost/process/child.hpp>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "../common/logging.h"
|
|
||||||
#include "../common/communication.h"
|
#include "../common/communication.h"
|
||||||
|
#include "../common/logging.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +127,7 @@ class IndividualHost : public HostProcess {
|
|||||||
IndividualHost(boost::asio::io_context& io_context,
|
IndividualHost(boost::asio::io_context& io_context,
|
||||||
Logger& logger,
|
Logger& logger,
|
||||||
boost::filesystem::path plugin_path,
|
boost::filesystem::path plugin_path,
|
||||||
const Sockets& sockets);
|
const Sockets<std::jthread>& sockets);
|
||||||
|
|
||||||
PluginArchitecture architecture() override;
|
PluginArchitecture architecture() override;
|
||||||
boost::filesystem::path path() override;
|
boost::filesystem::path path() override;
|
||||||
@@ -169,7 +169,7 @@ class GroupHost : public HostProcess {
|
|||||||
GroupHost(boost::asio::io_context& io_context,
|
GroupHost(boost::asio::io_context& io_context,
|
||||||
Logger& logger,
|
Logger& logger,
|
||||||
boost::filesystem::path plugin_path,
|
boost::filesystem::path plugin_path,
|
||||||
Sockets& socket_endpoint,
|
Sockets<std::jthread>& socket_endpoint,
|
||||||
std::string group_name);
|
std::string group_name);
|
||||||
|
|
||||||
PluginArchitecture architecture() override;
|
PluginArchitecture architecture() override;
|
||||||
@@ -192,7 +192,7 @@ class GroupHost : public HostProcess {
|
|||||||
* The associated sockets for the plugin we're hosting. This is used to
|
* The associated sockets for the plugin we're hosting. This is used to
|
||||||
* terminate the plugin.
|
* terminate the plugin.
|
||||||
*/
|
*/
|
||||||
Sockets& sockets;
|
Sockets<std::jthread>& sockets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A thread that waits for the group host to have started and then ask it to
|
* A thread that waits for the group host to have started and then ask it to
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class PluginBridge {
|
|||||||
void log_init_message();
|
void log_init_message();
|
||||||
|
|
||||||
boost::asio::io_context io_context;
|
boost::asio::io_context io_context;
|
||||||
Sockets sockets;
|
Sockets<std::jthread> sockets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The thread that handles host callbacks.
|
* The thread that handles host callbacks.
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ class Vst2Bridge {
|
|||||||
/**
|
/**
|
||||||
* All sockets used for communicating with this specific plugin.
|
* All sockets used for communicating with this specific plugin.
|
||||||
*/
|
*/
|
||||||
Sockets sockets;
|
Sockets<Win32Thread> sockets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The thread that specifically handles `effProcessEvents` opcodes so the
|
* The thread that specifically handles `effProcessEvents` opcodes so the
|
||||||
|
|||||||
Reference in New Issue
Block a user