diff --git a/src/common/communication.cpp b/src/common/communication.cpp index 9a2bdc5a..543202ae 100644 --- a/src/common/communication.cpp +++ b/src/common/communication.cpp @@ -95,6 +95,12 @@ EventHandler::EventHandler( 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); } diff --git a/src/common/communication.h b/src/common/communication.h index 1e5e2529..638cc94b 100644 --- a/src/common/communication.h +++ b/src/common/communication.h @@ -338,19 +338,17 @@ class EventHandler { * this is for sending `dispatch()` events or host callbacks. Optional * since it doesn't have to be done on both sides. * @param callback The function used to generate a response out of an event. + * See the definition of `F` for more information. * - * @tparam F A function type in the form of `EventResponse(Event)`. - * - * TODO: Add a boolean flag indicating that this is being called - * from an off thread + * @tparam F A function type in the form of `EventResponse(Event, bool)`. + * The boolean flag is `true` when this event was received on the main + * socket, and `false` otherwise. * * @relates EventHandler::send * @relates passthrough_event */ template void receive(std::optional> logging, F callback) { - assert(acceptor.has_value()); - // As described above we'll handle incoming requests for `socket` on // this thread. We'll also listen for incoming connections on `endpoint` // on another thread. For any incoming connection we'll spawn a new @@ -358,6 +356,9 @@ class EventHandler { // breaks, the listener and any still active threads will be cleaned up // before this function exits. boost::asio::io_context secondary_context{}; + // The previous acceptor has already been shut down by + // `EventHandler::connect()` + acceptor.emplace(secondary_context, endpoint); // This works the exact same was as `active_plugins` and // `next_plugin_id` in `GroupBridge` @@ -382,7 +383,7 @@ class EventHandler { event.value_payload); } - EventResult response = callback(event); + EventResult response = callback(event, false); if (logging) { auto [logger, is_dispatch] = *logging; logger.log_event_response(is_dispatch, event.opcode, @@ -420,7 +421,7 @@ class EventHandler { event.value_payload); } - EventResult response = callback(event); + EventResult response = callback(event, true); if (logging) { auto [logger, is_dispatch] = *logging; logger.log_event_response( @@ -499,8 +500,12 @@ class EventHandler { /** * This acceptor will be used once synchronously on the listening side * during `Sockets::connect()`. When `EventHandler::receive()` is then - * called, we'll asynchronously listen for new incoming socket connections - * on `endpoint` using this same acceptor. + * called, we'll recreate the acceptor asynchronously listen for new + * incoming socket connections on `endpoint` using this same acceptor. This + * is important, because on the case of `vst_host_callback` the acceptor is + * first accepts an initial socket on the plugin side (like all sockets), + * but all additional incoming connections of course have to be listened for + * on the plugin side. */ std::optional acceptor; @@ -650,6 +655,9 @@ boost::filesystem::path generate_endpoint_base(const std::string& plugin_name); * * This is the receiving analogue of the `*DataCovnerter` objects. * + * TODO: Now that `EventHandler::receive` replaced `receive_event()`, refactor + * this to just handle the event directly rather than returning a lambda + * * @param plugin The `AEffect` instance that should be passed to the callback * function. * @param callback The function to call with the arguments received from the diff --git a/src/plugin/plugin-bridge.cpp b/src/plugin/plugin-bridge.cpp index 3ddc2867..6acfdc11 100644 --- a/src/plugin/plugin-bridge.cpp +++ b/src/plugin/plugin-bridge.cpp @@ -121,10 +121,9 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback) // instead of asynchronous IO since communication has to be handled in // lockstep anyway host_callback_handler = std::jthread([&]() { - // TODO: Think of a nicer way to structure this and the similar - // handler in `Vst2Bridge::handle_dispatch_midi_events` sockets.vst_host_callback.receive( - std::pair(logger, false), [&](Event& event) { + std::pair(logger, false), + [&](Event& event, bool /*on_main_thread*/) { // MIDI events sent from the plugin back to the host are a // special case here. They have to sent during the // `processReplacing()` function or else the host will ignore diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 846ae6f8..6d639f98 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -146,32 +146,43 @@ bool Vst2Bridge::should_skip_message_loop() const { void Vst2Bridge::handle_dispatch() { sockets.host_vst_dispatch.receive( - std::nullopt, - passthrough_event( - plugin, - [&](AEffect* plugin, int opcode, int index, intptr_t value, - void* data, float option) -> intptr_t { - // Instead of running `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 - std::promise dispatch_result; - boost::asio::dispatch(io_context, [&]() { - const intptr_t result = dispatch_wrapper( - plugin, opcode, index, value, data, option); + std::nullopt, [&](Event& event, bool on_main_thread) { + // TODO: As per the TODO in `passthrough_event`, this can use a + // round of refactoring now that we never use its returned + // lambda directly anymore + return passthrough_event( + plugin, + [&](AEffect* plugin, int opcode, int index, intptr_t value, + void* data, float option) -> intptr_t { + // On the main thread, instead of running + // `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) { + std::promise dispatch_result; + boost::asio::dispatch(io_context, [&]() { + const intptr_t result = dispatch_wrapper( + plugin, opcode, index, value, data, option); - dispatch_result.set_value(result); - }); + 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(); - })); + return dispatch_result.get_future().get(); + } else { + return dispatch_wrapper(plugin, opcode, index, value, + data, option); + } + })(event); + }); } void Vst2Bridge::handle_dispatch_midi_events() { sockets.host_vst_dispatch_midi_events.receive( - std::nullopt, [&](Event& event) { + std::nullopt, [&](Event& event, bool /*on_main_thread*/) { if (BOOST_LIKELY(event.opcode == effProcessEvents)) { // For 99% of the plugins we can just call // `effProcessReplacing()` and be done with it, but a select few