From 398ae789e01154fd193fdb47e87a91a4f51a9df7 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 19 May 2021 19:57:21 +0200 Subject: [PATCH] Use MutualRecursionHelper in the VST3 plugin We should tidy all of these functions up a bit and then implement this for the VST2 bridging on the Wine side. --- .../bridges/vst3-impls/plug-view-proxy.h | 36 ++++---- src/plugin/bridges/vst3.h | 87 +++---------------- 2 files changed, 34 insertions(+), 89 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.h b/src/plugin/bridges/vst3-impls/plug-view-proxy.h index ea97321e..6bd944a7 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.h +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.h @@ -136,29 +136,33 @@ class Vst3PlugViewProxyImpl : public Vst3PlugViewProxy { * right now. * * @see Vst3HostBridge::send_mutually_recursive_message + * + * TODO: As mentioned elsewhere, refactor these functions to use + * `std::invoke_result_t` */ template T run_gui_task(F fn) { - std::packaged_task do_call(std::move(fn)); - std::future do_call_response = do_call.get_future(); - // If `Vst3Bridge::send_mutually_recursive_message()` is currently being // called (because the host is calling one of `IPlugView`'s methods from - // its UGI thread) then we'll post a message to an IO context that's - // currently accepting work on the that thread. Since in theory we could - // have nested mutual recursion, we need to keep track of a stack of IO - // contexts. Great. Otherwise we'll schedule the task to be run from an - // event handler registered to the host's run loop. If the host does not - // support `IRunLoop`, we'll just run `f` directly. - if (!bridge.maybe_run_on_mutual_recursion_thread(do_call)) { - if (run_loop_tasks) { - run_loop_tasks->schedule(std::move(do_call)); - } else { - do_call(); - } + // its UGI thread), then we'll call `fn` from that same thread. + // Otherwise we'll schedule the task to be run from an event handler + // registered to the host's run loop, if that exists. Finally if the + // host does not support `IRunLoop`, then we'll just run `fn` directly. + if (const auto result = + bridge.maybe_run_on_mutual_recursion_thread(fn)) { + return *result; } - return do_call_response.get(); + if (run_loop_tasks) { + std::packaged_task do_call(std::move(fn)); + std::future do_call_response = do_call.get_future(); + + run_loop_tasks->schedule(std::move(do_call)); + + return do_call_response.get(); + } else { + return fn(); + } } // From `IPlugView` diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index fa707ae3..11166089 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -20,11 +20,10 @@ #include "../../common/communication/vst3.h" #include "../../common/logging/vst3.h" +#include "../../common/mutual-recursion.h" #include "common.h" #include "vst3-impls/plugin-factory-proxy.h" -#include - // Forward declarations class Vst3PluginProxyImpl; @@ -152,75 +151,24 @@ class Vst3PluginBridge : PluginBridge> { */ template typename T::Response send_mutually_recursive_message(const T& object) { - using TResponse = typename T::Response; - - // This IO context will accept incoming calls from `run_gui_task()` - // until we receive a response. We keep these on a stack as we need to - // support multiple levels of mutual recursion. This could happen during - // `IPlugView::attached() -> IPlugFrame::resizeView() -> - // IPlugView::onSize()`. - std::shared_ptr current_io_context = - std::make_shared(); - { - std::unique_lock lock(mutual_recursion_contexts_mutex); - mutual_recursion_contexts.push_back(current_io_context); - } - - // Instead of directly stopping the IO context, we'll reset this work - // guard instead. This prevents us from accidentally cancelling any - // outstanding tasks. - auto work_guard = boost::asio::make_work_guard(*current_io_context); - - // We will call the function from another thread so we can handle calls - // to from this thread - std::promise response_promise{}; - std::jthread sending_thread([&]() { - set_realtime_priority(true); - - const TResponse response = send_message(object); - - // Stop accepting additional work to be run from the calling thread - // once we receive a response. By resetting the work guard we do not - // cancel any pending tasks, but `current_io_context->run()` will - // stop blocking eventually. - std::lock_guard lock(mutual_recursion_contexts_mutex); - work_guard.reset(); - mutual_recursion_contexts.erase( - std::find(mutual_recursion_contexts.begin(), - mutual_recursion_contexts.end(), current_io_context)); - - response_promise.set_value(response); - }); - - // Accept work from the other thread until we receive a response, at - // which point the context will be stopped - current_io_context->run(); - - return response_promise.get_future().get(); + return mutual_recursion.fork([&]() { return send_message(object); }); } /** * If `send_mutually_recursive_message()` is currently being called, then - * run `cb` on the thread that's currently calling that function. If there's - * currently no mutually recursive function call going on, this will return - * false, and the caller should call `cb` itself. + * run `cb` on the thread that's currently calling that function and return + * the result of the call. If there's currently no mutually recursive + * function call going on, this will return an `std::nullopt`, and the + * caller should call `cb` itself. * - * @return Whether `cb` was scheduled to run on the mutual recursion thread. + * @return The result of calling `fn`, if `fn` was called. * * @see Vst3PlugViewProxyImpl::run_gui_task */ template - bool maybe_run_on_mutual_recursion_thread(F& fn) { - // We're handling an `F&` here because we cannot copy a - // `packged_task()`, and we need to be able to move that actual task - std::unique_lock mutual_recursion_lock(mutual_recursion_contexts_mutex); - if (!mutual_recursion_contexts.empty()) { - boost::asio::dispatch(*mutual_recursion_contexts.back(), - std::move(fn)); - return true; - } else { - return false; - } + std::optional> maybe_run_on_mutual_recursion_thread( + F&& fn) { + return mutual_recursion.maybe_handle(std::forward(fn)); } /** @@ -263,16 +211,9 @@ class Vst3PluginBridge : PluginBridge> { std::mutex plugin_proxies_mutex; /** - * The IO contexts used in `send_mutually_recursive_message()` to be able to - * execute functions from a function's calling thread while we're waiting - * for a response. We need an entire stack of these to support mutual - * recursion, how fun! See the docstring there for more information. When - * this doesn't contain an IO context, this function is not being called and - * `Vst3PlugViewProxyImpl::run_gui_task()` should post the task to - * `Vst3PlugViewProxyImpl::run_loop_tasks`. This works exactly the same as - * the mutual recursion handling in `Vst3Bridge`. + * Used in `Vst3Bridge::send_mutually_recursive_message()` to be able to + * execute functions from that same calling thread while we're waiting for a + * response. This is used in `Vst3PlugViewProxyImpl::run_loop_tasks()`. */ - std::vector> - mutual_recursion_contexts; - std::mutex mutual_recursion_contexts_mutex; + MutualRecursionHelper mutual_recursion; };