From fa01ac843ba4b3a04a912c4ef3d9454fb72944bb Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 30 Oct 2020 13:30:08 +0100 Subject: [PATCH] Replace all non-group sockets with SocketHandler This greatly reduces the amount of boilerplate and potential for error. --- src/common/communication.h | 120 ++++++++++----------------------- src/plugin/plugin-bridge.cpp | 22 +++--- src/wine-host/bridges/vst2.cpp | 46 +++++-------- src/wine-host/bridges/vst2.h | 6 -- 4 files changed, 63 insertions(+), 131 deletions(-) diff --git a/src/common/communication.h b/src/common/communication.h index ade9eab2..ad93dc63 100644 --- a/src/common/communication.h +++ b/src/common/communication.h @@ -211,6 +211,8 @@ class SocketHandler { * packets arriving out of order. * * @see write_object + * @see SocketHandler::receive_single + * @see SocketHandler::receive_multi */ template inline void send(const T& object, std::vector& buffer) { @@ -244,7 +246,7 @@ class SocketHandler { * @relates SocketHandler::send * * @see read_object - * @see SocketHandler::receive + * @see SocketHandler::receive_multi */ template inline T receive_single(std::vector& buffer) { @@ -270,8 +272,10 @@ class SocketHandler { * we'd probably want to do some more stuff after sending a reply, calling * `send()` is the responsibility of this function. * - * @tparam F A function type in the form of `void(T)` that does something - * with the object, and then calls `send()`. + * @tparam F A function type in the form of `void(T, std::vector&)` + * 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 * @@ -279,13 +283,13 @@ class SocketHandler { * @see SocketHandler::receive_single */ template - void receive(F callback) { + void receive_multi(F callback) { std::vector buffer{}; while (true) { try { auto object = receive_single(buffer); - callback(std::move(object)); + callback(std::move(object), buffer); } catch (const boost::system::system_error&) { // This happens when the sockets got closed because the plugin // is being shut down @@ -804,61 +808,40 @@ class Sockets { 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}, - }; - } - } + host_vst_parameters(io_context, + (base_dir / "host_vst_parameters.sock").string(), + listen), + host_vst_process_replacing( + io_context, + (base_dir / "host_vst_process_replacing.sock").string(), + listen), + host_vst_control(io_context, + (base_dir / "host_vst_control.sock").string(), + listen) {} /** * Cleans up the directory containing the socket endpoints when yabridge * shuts down if it still exists. */ ~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 - } - } - // 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(); + + // 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 + 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 + } } /** @@ -870,17 +853,9 @@ class Sockets { 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); - } + host_vst_parameters.connect(); + host_vst_process_replacing.connect(); + host_vst_control.connect(); } /** @@ -915,44 +890,19 @@ class Sockets { * Used for both `getParameter` and `setParameter` since they mostly * overlap. */ - boost::asio::local::stream_protocol::socket host_vst_parameters; + SocketHandler host_vst_parameters; /** * Used for processing audio usign the `process()`, `processReplacing()` and * `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 * 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 * the configuration (from `config`) back to the Wine host. */ - boost::asio::local::stream_protocol::socket 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; + SocketHandler host_vst_control; }; /** diff --git a/src/plugin/plugin-bridge.cpp b/src/plugin/plugin-bridge.cpp index bbd8b757..7ecc6ce9 100644 --- a/src/plugin/plugin-bridge.cpp +++ b/src/plugin/plugin-bridge.cpp @@ -153,13 +153,13 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback) // over the `dispatcher()` socket. This would happen whenever the plugin // calls `audioMasterIOChanged()` and after the host calls `effOpen()`. const auto initialization_data = - read_object(sockets.host_vst_control); + sockets.host_vst_control.receive_single(); const auto initialized_plugin = std::get(initialization_data.payload); // After receiving the `AEffect` values we'll want to send the configuration // back to complete the startup process - write_object(sockets.host_vst_control, config); + sockets.host_vst_control.send(config); 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}; - 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 - const auto response = read_object( - sockets.host_vst_process_replacing, process_buffer); + const auto response = + sockets.host_vst_process_replacing.receive_single( + process_buffer); const auto& response_buffers = std::get>>(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 { std::lock_guard lock(parameters_mutex); - write_object(sockets.host_vst_parameters, request); - response = read_object(sockets.host_vst_parameters); + sockets.host_vst_parameters.send(request); + + response = + sockets.host_vst_parameters.receive_single(); } 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); - write_object(sockets.host_vst_parameters, request); + sockets.host_vst_parameters.send(request); - response = read_object(sockets.host_vst_parameters); + response = + sockets.host_vst_parameters.receive_single(); } logger.log_set_parameter_response(); diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 4f2f7601..0e2f7b1f 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -121,14 +121,12 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context, // of this object will be sent over the `dispatcher()` socket. This would be // done after the host calls `effOpen()`, and when the plugin calls // `audioMasterIOChanged()`. - write_object(sockets.host_vst_control, - EventResult{.return_value = 0, - .payload = *plugin, - .value_payload = std::nullopt}); + sockets.host_vst_control.send(EventResult{ + .return_value = 0, .payload = *plugin, .value_payload = std::nullopt}); // After sending the AEffect struct we'll receive this instance's // configuration as a response - config = read_object(sockets.host_vst_control); + config = sockets.host_vst_control.receive_single(); // This works functionally identically to the `handle_dispatch()` function, // but this socket will only handle MIDI events and it will handle them @@ -182,33 +180,26 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context, }); parameters_handler = Win32Thread([&]() { - while (true) { - try { + sockets.host_vst_parameters.receive_multi( + [&](Parameter request, std::vector& buffer) { // Both `getParameter` and `setParameter` functions are passed // through on this socket since they have a lot of overlap. The // presence of the `value` field tells us which one we're // dealing with. - auto request = - read_object(sockets.host_vst_parameters); if (request.value) { // `setParameter` plugin->setParameter(plugin, request.index, *request.value); ParameterResult response{std::nullopt}; - write_object(sockets.host_vst_parameters, response); + sockets.host_vst_parameters.send(response, buffer); } else { // `getParameter` float value = plugin->getParameter(plugin, request.index); 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([&]() { @@ -221,10 +212,8 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context, std::vector> output_buffers_double_precision( plugin->numOutputs); - while (true) { - try { - auto request = read_object( - sockets.host_vst_process_replacing, process_buffer); + sockets.host_vst_process_replacing.receive_multi( + [&](AudioBuffers request, std::vector& buffer) { // Let the plugin process the MIDI events that were received // since the last buffer, and then clean up those events. This // approach should not be needed but Kontakt only stores @@ -287,8 +276,8 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context, AudioBuffers response{ output_buffers_single_precision, request.sample_frames}; - write_object(sockets.host_vst_process_replacing, - response, process_buffer); + sockets.host_vst_process_replacing.send(response, + buffer); }, [&](std::vector>& input_buffers) { // Exactly the same as the above, but for double @@ -314,18 +303,13 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context, AudioBuffers response{ output_buffers_double_precision, request.sample_frames}; - write_object(sockets.host_vst_process_replacing, - response, process_buffer); + sockets.host_vst_process_replacing.send(response, + buffer); }}, request.buffers); 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; - } - } + }); }); } diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 514c682d..db1b68ac 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -211,12 +211,6 @@ class Vst2Bridge { */ Sockets sockets; - /** - * A scratch buffer for sending and receiving data during `process` and - * `processReplacing` calls. - */ - std::vector process_buffer; - /** * The MIDI events that have been received **and processed** since the last * call to `processReplacing()`. 99% of plugins make a copy of the MIDI