Replace all non-group sockets with SocketHandler

This greatly reduces the amount of boilerplate and potential for error.
This commit is contained in:
Robbert van der Helm
2020-10-30 13:30:08 +01:00
parent 42792a883d
commit fa01ac843b
4 changed files with 63 additions and 131 deletions
+34 -84
View File
@@ -211,6 +211,8 @@ class SocketHandler {
* packets arriving out of order. * packets arriving out of order.
* *
* @see write_object * @see write_object
* @see SocketHandler::receive_single
* @see SocketHandler::receive_multi
*/ */
template <typename T> template <typename T>
inline void send(const T& object, std::vector<uint8_t>& buffer) { inline void send(const T& object, std::vector<uint8_t>& buffer) {
@@ -244,7 +246,7 @@ class SocketHandler {
* @relates SocketHandler::send * @relates SocketHandler::send
* *
* @see read_object * @see read_object
* @see SocketHandler::receive * @see SocketHandler::receive_multi
*/ */
template <typename T> template <typename T>
inline T receive_single(std::vector<uint8_t>& buffer) { inline T receive_single(std::vector<uint8_t>& buffer) {
@@ -270,8 +272,10 @@ class SocketHandler {
* we'd probably want to do some more stuff after sending a reply, calling * we'd probably want to do some more stuff after sending a reply, calling
* `send()` is the responsibility of this function. * `send()` is the responsibility of this function.
* *
* @tparam F A function type in the form of `void(T)` that does something * @tparam F A function type in the form of `void(T, std::vector<uint8_t>&)`
* with the object, and then calls `send()`. * that does something with the object, and then calls `send()`. The
* reading/writing buffer is passed along so it can be reused for sending
* large amounts of data.
* *
* @relates SocketHandler::send * @relates SocketHandler::send
* *
@@ -279,13 +283,13 @@ class SocketHandler {
* @see SocketHandler::receive_single * @see SocketHandler::receive_single
*/ */
template <typename T, typename F> template <typename T, typename F>
void receive(F callback) { void receive_multi(F callback) {
std::vector<uint8_t> buffer{}; std::vector<uint8_t> buffer{};
while (true) { while (true) {
try { try {
auto object = receive_single<T>(buffer); auto object = receive_single<T>(buffer);
callback(std::move(object)); callback(std::move(object), buffer);
} catch (const boost::system::system_error&) { } catch (const boost::system::system_error&) {
// This happens when the sockets got closed because the plugin // This happens when the sockets got closed because the plugin
// is being shut down // is being shut down
@@ -804,35 +808,33 @@ class Sockets {
vst_host_callback(io_context, vst_host_callback(io_context,
(base_dir / "vst_host_callback.sock").string(), (base_dir / "vst_host_callback.sock").string(),
listen), listen),
host_vst_parameters(io_context), host_vst_parameters(io_context,
host_vst_process_replacing(io_context), (base_dir / "host_vst_parameters.sock").string(),
host_vst_control(io_context), listen),
host_vst_parameters_endpoint( host_vst_process_replacing(
(base_dir / "host_vst_parameters.sock").string()), io_context,
host_vst_process_replacing_endpoint( (base_dir / "host_vst_process_replacing.sock").string(),
(base_dir / "host_vst_process_replacing.sock").string()), listen),
host_vst_control_endpoint( host_vst_control(io_context,
(base_dir / "host_vst_control.sock").string()) { (base_dir / "host_vst_control.sock").string(),
if (listen) { 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() {
// Manually close all sockets so we break out of any blocking operations
// that may still be active
host_vst_dispatch.close();
host_vst_dispatch_midi_events.close();
vst_host_callback.close();
host_vst_parameters.close();
host_vst_process_replacing.close();
host_vst_control.close();
// Only clean if we're the ones who have created these files, although // 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 // it should not cause any harm to also do this on the Wine side
if (acceptors) {
try { try {
boost::filesystem::remove_all(base_dir); boost::filesystem::remove_all(base_dir);
} catch (const boost::filesystem::filesystem_error&) { } catch (const boost::filesystem::filesystem_error&) {
@@ -842,25 +844,6 @@ class Sockets {
} }
} }
// Manually close all sockets so we break out of any blocking operations
// that may still be active
host_vst_dispatch.close();
host_vst_dispatch_midi_events.close();
vst_host_callback.close();
// These shutdowns can fail when the socket has already been closed, but
// that's not an issue in our case
constexpr auto shutdown_type =
boost::asio::local::stream_protocol::socket::shutdown_both;
boost::system::error_code err;
host_vst_parameters.shutdown(shutdown_type, err);
host_vst_process_replacing.shutdown(shutdown_type, err);
host_vst_control.shutdown(shutdown_type, err);
host_vst_parameters.close();
host_vst_process_replacing.close();
host_vst_control.close();
}
/** /**
* 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
@@ -870,17 +853,9 @@ class Sockets {
host_vst_dispatch.connect(); host_vst_dispatch.connect();
host_vst_dispatch_midi_events.connect(); host_vst_dispatch_midi_events.connect();
vst_host_callback.connect(); vst_host_callback.connect();
if (acceptors) { host_vst_parameters.connect();
acceptors->host_vst_parameters.accept(host_vst_parameters); host_vst_process_replacing.connect();
acceptors->host_vst_process_replacing.accept( host_vst_control.connect();
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);
}
} }
/** /**
@@ -915,44 +890,19 @@ class Sockets {
* Used for both `getParameter` and `setParameter` since they mostly * Used for both `getParameter` and `setParameter` since they mostly
* overlap. * overlap.
*/ */
boost::asio::local::stream_protocol::socket host_vst_parameters; SocketHandler host_vst_parameters;
/** /**
* Used for processing audio usign the `process()`, `processReplacing()` and * Used for processing audio usign the `process()`, `processReplacing()` and
* `processDoubleReplacing()` functions. * `processDoubleReplacing()` functions.
*/ */
boost::asio::local::stream_protocol::socket host_vst_process_replacing; SocketHandler host_vst_process_replacing;
/** /**
* A control socket that sends data that is not suitable for the other * A control socket that sends data that is not suitable for the other
* sockets. At the moment this is only used to, on startup, send the Windows * sockets. At the moment this is only used to, on startup, send the Windows
* VST plugin's `AEffect` object to the native VST plugin, and to then send * VST plugin's `AEffect` object to the native VST plugin, and to then send
* the configuration (from `config`) back to the Wine host. * the configuration (from `config`) back to the Wine host.
*/ */
boost::asio::local::stream_protocol::socket host_vst_control; SocketHandler host_vst_control;
private:
const boost::asio::local::stream_protocol::endpoint
host_vst_parameters_endpoint;
const boost::asio::local::stream_protocol::endpoint
host_vst_process_replacing_endpoint;
const boost::asio::local::stream_protocol::endpoint
host_vst_control_endpoint;
/**
* All of our socket acceptors. We have to create these before launching the
* Wine process.
*/
struct Acceptors {
boost::asio::local::stream_protocol::acceptor host_vst_parameters;
boost::asio::local::stream_protocol::acceptor
host_vst_process_replacing;
boost::asio::local::stream_protocol::acceptor host_vst_control;
};
/**
* If the `listen` constructor argument was set to `true`, when we'll
* prepare a set of socket acceptors that listen on the socket endpoints.
*/
std::optional<Acceptors> acceptors;
}; };
/** /**
+13 -9
View File
@@ -153,13 +153,13 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback)
// over the `dispatcher()` socket. This would happen whenever the plugin // over the `dispatcher()` socket. This would happen whenever the plugin
// calls `audioMasterIOChanged()` and after the host calls `effOpen()`. // calls `audioMasterIOChanged()` and after the host calls `effOpen()`.
const auto initialization_data = const auto initialization_data =
read_object<EventResult>(sockets.host_vst_control); sockets.host_vst_control.receive_single<EventResult>();
const auto initialized_plugin = const auto initialized_plugin =
std::get<AEffect>(initialization_data.payload); std::get<AEffect>(initialization_data.payload);
// After receiving the `AEffect` values we'll want to send the configuration // After receiving the `AEffect` values we'll want to send the configuration
// back to complete the startup process // back to complete the startup process
write_object(sockets.host_vst_control, config); sockets.host_vst_control.send(config);
update_aeffect(plugin, initialized_plugin); update_aeffect(plugin, initialized_plugin);
} }
@@ -502,11 +502,12 @@ void PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) {
} }
const AudioBuffers request{input_buffers, sample_frames}; const AudioBuffers request{input_buffers, sample_frames};
write_object(sockets.host_vst_process_replacing, request, process_buffer); sockets.host_vst_process_replacing.send(request, process_buffer);
// Write the results back to the `outputs` arrays // Write the results back to the `outputs` arrays
const auto response = read_object<AudioBuffers>( const auto response =
sockets.host_vst_process_replacing, process_buffer); sockets.host_vst_process_replacing.receive_single<AudioBuffers>(
process_buffer);
const auto& response_buffers = const auto& response_buffers =
std::get<std::vector<std::vector<T>>>(response.buffers); std::get<std::vector<std::vector<T>>>(response.buffers);
@@ -555,8 +556,10 @@ float PluginBridge::get_parameter(AEffect* /*plugin*/, int index) {
// called at the same time since they share the same socket // called at the same time since they share the same socket
{ {
std::lock_guard lock(parameters_mutex); std::lock_guard lock(parameters_mutex);
write_object(sockets.host_vst_parameters, request); sockets.host_vst_parameters.send(request);
response = read_object<ParameterResult>(sockets.host_vst_parameters);
response =
sockets.host_vst_parameters.receive_single<ParameterResult>();
} }
logger.log_get_parameter_response(*response.value); logger.log_get_parameter_response(*response.value);
@@ -572,9 +575,10 @@ void PluginBridge::set_parameter(AEffect* /*plugin*/, int index, float value) {
{ {
std::lock_guard lock(parameters_mutex); std::lock_guard lock(parameters_mutex);
write_object(sockets.host_vst_parameters, request); sockets.host_vst_parameters.send(request);
response = read_object<ParameterResult>(sockets.host_vst_parameters); response =
sockets.host_vst_parameters.receive_single<ParameterResult>();
} }
logger.log_set_parameter_response(); logger.log_set_parameter_response();
+15 -31
View File
@@ -121,14 +121,12 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
// of this object will be sent over the `dispatcher()` socket. This would be // of this object will be sent over the `dispatcher()` socket. This would be
// done after the host calls `effOpen()`, and when the plugin calls // done after the host calls `effOpen()`, and when the plugin calls
// `audioMasterIOChanged()`. // `audioMasterIOChanged()`.
write_object(sockets.host_vst_control, sockets.host_vst_control.send(EventResult{
EventResult{.return_value = 0, .return_value = 0, .payload = *plugin, .value_payload = std::nullopt});
.payload = *plugin,
.value_payload = std::nullopt});
// After sending the AEffect struct we'll receive this instance's // After sending the AEffect struct we'll receive this instance's
// configuration as a response // configuration as a response
config = read_object<Configuration>(sockets.host_vst_control); config = sockets.host_vst_control.receive_single<Configuration>();
// This works functionally identically to the `handle_dispatch()` function, // This works functionally identically to the `handle_dispatch()` function,
// but this socket will only handle MIDI events and it will handle them // but this socket will only handle MIDI events and it will handle them
@@ -182,34 +180,27 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
}); });
parameters_handler = Win32Thread([&]() { parameters_handler = Win32Thread([&]() {
while (true) { sockets.host_vst_parameters.receive_multi<Parameter>(
try { [&](Parameter request, std::vector<uint8_t>& buffer) {
// Both `getParameter` and `setParameter` functions are passed // Both `getParameter` and `setParameter` functions are passed
// through on this socket since they have a lot of overlap. The // through on this socket since they have a lot of overlap. The
// presence of the `value` field tells us which one we're // presence of the `value` field tells us which one we're
// dealing with. // dealing with.
auto request =
read_object<Parameter>(sockets.host_vst_parameters);
if (request.value) { if (request.value) {
// `setParameter` // `setParameter`
plugin->setParameter(plugin, request.index, *request.value); plugin->setParameter(plugin, request.index, *request.value);
ParameterResult response{std::nullopt}; ParameterResult response{std::nullopt};
write_object(sockets.host_vst_parameters, response); sockets.host_vst_parameters.send(response, buffer);
} else { } else {
// `getParameter` // `getParameter`
float value = plugin->getParameter(plugin, request.index); float value = plugin->getParameter(plugin, request.index);
ParameterResult response{value}; ParameterResult response{value};
write_object(sockets.host_vst_parameters, response); sockets.host_vst_parameters.send(response, buffer);
}
} catch (const boost::system::system_error&) {
// The plugin has cut off communications, so we can shut down
// this host application
break;
}
} }
}); });
});
process_replacing_handler = Win32Thread([&]() { process_replacing_handler = Win32Thread([&]() {
// These are used as scratch buffers to prevent unnecessary allocations. // These are used as scratch buffers to prevent unnecessary allocations.
@@ -221,10 +212,8 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
std::vector<std::vector<double>> output_buffers_double_precision( std::vector<std::vector<double>> output_buffers_double_precision(
plugin->numOutputs); plugin->numOutputs);
while (true) { sockets.host_vst_process_replacing.receive_multi<AudioBuffers>(
try { [&](AudioBuffers request, std::vector<uint8_t>& buffer) {
auto request = read_object<AudioBuffers>(
sockets.host_vst_process_replacing, process_buffer);
// Let the plugin process the MIDI events that were received // Let the plugin process the MIDI events that were received
// since the last buffer, and then clean up those events. This // since the last buffer, and then clean up those events. This
// approach should not be needed but Kontakt only stores // approach should not be needed but Kontakt only stores
@@ -287,8 +276,8 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
AudioBuffers response{ AudioBuffers response{
output_buffers_single_precision, output_buffers_single_precision,
request.sample_frames}; request.sample_frames};
write_object(sockets.host_vst_process_replacing, sockets.host_vst_process_replacing.send(response,
response, process_buffer); buffer);
}, },
[&](std::vector<std::vector<double>>& input_buffers) { [&](std::vector<std::vector<double>>& input_buffers) {
// Exactly the same as the above, but for double // Exactly the same as the above, but for double
@@ -314,18 +303,13 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
AudioBuffers response{ AudioBuffers response{
output_buffers_double_precision, output_buffers_double_precision,
request.sample_frames}; request.sample_frames};
write_object(sockets.host_vst_process_replacing, sockets.host_vst_process_replacing.send(response,
response, process_buffer); buffer);
}}, }},
request.buffers); request.buffers);
next_audio_buffer_midi_events.clear(); next_audio_buffer_midi_events.clear();
} catch (const boost::system::system_error&) { });
// The plugin has cut off communications, so we can shut down
// this host application
break;
}
}
}); });
} }
-6
View File
@@ -211,12 +211,6 @@ class Vst2Bridge {
*/ */
Sockets<Win32Thread> sockets; Sockets<Win32Thread> sockets;
/**
* A scratch buffer for sending and receiving data during `process` and
* `processReplacing` calls.
*/
std::vector<uint8_t> process_buffer;
/** /**
* The MIDI events that have been received **and processed** since the last * The MIDI events that have been received **and processed** since the last
* call to `processReplacing()`. 99% of plugins make a copy of the MIDI * call to `processReplacing()`. 99% of plugins make a copy of the MIDI