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.
This commit is contained in:
Robbert van der Helm
2021-05-19 19:57:21 +02:00
parent 11cfd15308
commit 398ae789e0
2 changed files with 34 additions and 89 deletions
+20 -16
View File
@@ -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 <typename T, typename F>
T run_gui_task(F fn) {
std::packaged_task<T()> do_call(std::move(fn));
std::future<T> 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<T()> do_call(std::move(fn));
std::future<T> 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`
+14 -73
View File
@@ -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 <boost/asio/dispatch.hpp>
// Forward declarations
class Vst3PluginProxyImpl;
@@ -152,75 +151,24 @@ class Vst3PluginBridge : PluginBridge<Vst3Sockets<std::jthread>> {
*/
template <typename T>
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<boost::asio::io_context> current_io_context =
std::make_shared<boost::asio::io_context>();
{
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<TResponse> 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 <typename F>
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<std::invoke_result_t<F>> maybe_run_on_mutual_recursion_thread(
F&& fn) {
return mutual_recursion.maybe_handle(std::forward<F>(fn));
}
/**
@@ -263,16 +211,9 @@ class Vst3PluginBridge : PluginBridge<Vst3Sockets<std::jthread>> {
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<std::shared_ptr<boost::asio::io_context>>
mutual_recursion_contexts;
std::mutex mutual_recursion_contexts_mutex;
MutualRecursionHelper<std::jthread> mutual_recursion;
};