Allow mutual recursion on all CLAP callbacks

This commit is contained in:
Robbert van der Helm
2022-10-30 16:47:37 +01:00
parent 9dbaec4071
commit ab3d61cc91
4 changed files with 175 additions and 135 deletions
@@ -128,6 +128,10 @@ class clap_plugin_proxy {
/**
* Asynchronously run a function on the host's main thread, returning the
* result as a future.
*
* Instead of calling this directly, `ClapBridge::run_on_main_thread()`
* should be used instead. That also handles mutually recursive main thread
* callbacks.
*/
template <std::invocable F>
std::future<std::invoke_result_t<F>> run_on_main_thread(F&& fn) {
+89 -88
View File
@@ -60,11 +60,10 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread([host = plugin_proxy.host_]() {
host->request_restart(host);
})
.wait();
run_on_main_thread(plugin_proxy, [host = plugin_proxy
.host_]() {
host->request_restart(host);
}).wait();
return Ack{};
},
@@ -73,11 +72,10 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread([host = plugin_proxy.host_]() {
host->request_process(host);
})
.wait();
run_on_main_thread(plugin_proxy, [host = plugin_proxy
.host_]() {
host->request_process(host);
}).wait();
return Ack{};
},
@@ -88,15 +86,16 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
return plugin_proxy
.run_on_main_thread(
[&, host = plugin_proxy.host_,
audio_ports = plugin_proxy.host_extensions_
.audio_ports]() {
return audio_ports
->is_rescan_flag_supported(
host, request.flag);
})
return run_on_main_thread(
plugin_proxy,
[&, host = plugin_proxy.host_,
audio_ports =
plugin_proxy.host_extensions_
.audio_ports]() {
return audio_ports
->is_rescan_flag_supported(
host, request.flag);
})
.get();
},
[&](const clap::ext::audio_ports::host::Rescan& request)
@@ -104,13 +103,13 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread(
[&, host = plugin_proxy.host_,
audio_ports =
plugin_proxy.host_extensions_.audio_ports]() {
audio_ports->rescan(host, request.flags);
})
run_on_main_thread(
plugin_proxy,
[&, host = plugin_proxy.host_,
audio_ports =
plugin_proxy.host_extensions_.audio_ports]() {
audio_ports->rescan(host, request.flags);
})
.wait();
return Ack{};
@@ -120,13 +119,13 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread(
[&, host = plugin_proxy.host_,
audio_ports_config = plugin_proxy.host_extensions_
.audio_ports_config]() {
audio_ports_config->rescan(host);
})
run_on_main_thread(
plugin_proxy,
[&, host = plugin_proxy.host_,
audio_ports_config = plugin_proxy.host_extensions_
.audio_ports_config]() {
audio_ports_config->rescan(host);
})
.wait();
return Ack{};
@@ -185,13 +184,12 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread(
[&, host = plugin_proxy.host_,
latency =
plugin_proxy.host_extensions_.latency]() {
latency->changed(host);
})
run_on_main_thread(
plugin_proxy,
[&, host = plugin_proxy.host_,
latency = plugin_proxy.host_extensions_.latency]() {
latency->changed(host);
})
.wait();
return Ack{};
@@ -201,13 +199,13 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread(
[&, host = plugin_proxy.host_,
note_name =
plugin_proxy.host_extensions_.note_name]() {
note_name->changed(host);
})
run_on_main_thread(
plugin_proxy,
[&, host = plugin_proxy.host_,
note_name =
plugin_proxy.host_extensions_.note_name]() {
note_name->changed(host);
})
.wait();
return Ack{};
@@ -219,14 +217,15 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
return plugin_proxy
.run_on_main_thread(
[host = plugin_proxy.host_,
note_ports = plugin_proxy.host_extensions_
.note_ports]() {
return note_ports->supported_dialects(
host);
})
return run_on_main_thread(
plugin_proxy,
[host = plugin_proxy.host_,
note_ports =
plugin_proxy.host_extensions_
.note_ports]() {
return note_ports
->supported_dialects(host);
})
.get();
},
[&](const clap::ext::note_ports::host::Rescan& request)
@@ -234,13 +233,13 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread(
[&, host = plugin_proxy.host_,
note_ports =
plugin_proxy.host_extensions_.note_ports]() {
note_ports->rescan(host, request.flags);
})
run_on_main_thread(
plugin_proxy,
[&, host = plugin_proxy.host_,
note_ports =
plugin_proxy.host_extensions_.note_ports]() {
note_ports->rescan(host, request.flags);
})
.wait();
return Ack{};
@@ -250,12 +249,14 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread(
[&, host = plugin_proxy.host_,
params = plugin_proxy.host_extensions_.params]() {
params->rescan(host, request.flags);
})
// TODO: Handle mutual recursion here and for latency
// changes
run_on_main_thread(
plugin_proxy,
[&, host = plugin_proxy.host_,
params = plugin_proxy.host_extensions_.params]() {
params->rescan(host, request.flags);
})
.wait();
return Ack{};
@@ -265,13 +266,13 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread(
[&, host = plugin_proxy.host_,
params = plugin_proxy.host_extensions_.params]() {
params->clear(host, request.param_id,
request.flags);
})
run_on_main_thread(
plugin_proxy,
[&, host = plugin_proxy.host_,
params = plugin_proxy.host_extensions_.params]() {
params->clear(host, request.param_id,
request.flags);
})
.wait();
return Ack{};
@@ -281,12 +282,12 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread(
[&, host = plugin_proxy.host_,
state = plugin_proxy.host_extensions_.state]() {
state->mark_dirty(host);
})
run_on_main_thread(
plugin_proxy,
[&, host = plugin_proxy.host_,
state = plugin_proxy.host_extensions_.state]() {
state->mark_dirty(host);
})
.wait();
return Ack{};
@@ -296,13 +297,13 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path)
const auto& [plugin_proxy, _] =
get_proxy(request.owner_instance_id);
plugin_proxy
.run_on_main_thread(
[&, host = plugin_proxy.host_,
voice_info =
plugin_proxy.host_extensions_.voice_info]() {
voice_info->changed(host);
})
run_on_main_thread(
plugin_proxy,
[&, host = plugin_proxy.host_,
voice_info =
plugin_proxy.host_extensions_.voice_info]() {
voice_info->changed(host);
})
.wait();
return Ack{};
+81 -46
View File
@@ -142,45 +142,81 @@ class ClapPluginBridge : PluginBridge<ClapSockets<std::jthread>> {
}
// TODO: Do we need this for CLAP? If we do, update the docstring
// /**
// * Send a message, and allow other threads to call functions on _this
// * thread_ while we're waiting for a response. This lets us execute
// * functions from the host's GUI thread while it is also calling
// functions
// * from that same thread. Because of that, we also know that while this
// * function is being called the host won't be able to handle any
// `IRunLoop`
// * events. We need this to support REAPER, because REAPER requires
// function
// * calls involving the GUI to be run from the GUI thread. Grep for
// * `run_gui_task` for instances of this.
// *
// * We use the same trick in `ClapBridge`.
// */
// template <typename T>
// typename T::Response send_mutually_recursive_message(const T& object) {
// return mutual_recursion_.fork([&]() { return send_message(object);
// });
// }
/**
* Send a message meant to be executed on the main thread, and allow other
* threads to call functions on _this thread_ while we're waiting for a
* response. This lets us execute functions from the host's main thread
* while it is also calling functions from that same thread. Because of
* that, we also know that while this function is being called the host
* won't be able to handle any `clap_host::request_callback()` requests. We
* need this for a couple situations, like a plugin calling
* `clap_host_*::rescan()` during state loading.
*
* We use the same trick in `ClapBridge`.
*/
template <typename T>
typename T::Response send_mutually_recursive_main_thread_message(
const T& object) {
return mutual_recursion_.fork(
[&]() { return send_main_thread_message(object); });
}
// /**
// * If `send_mutually_recursive_message()` is currently being called, then
// * run `fn` 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 `fn` itself.
// *
// * @return The result of calling `fn`, if `fn` was called.
// *
// * @see ClapPlugViewProxyImpl::run_gui_task
// */
// template <std::invocable F>
// std::optional<std::invoke_result_t<F>>
// maybe_run_on_mutual_recursion_thread(
// F&& fn) {
// return mutual_recursion_.maybe_handle(std::forward<F>(fn));
// }
/**
* Run a callback on the host's GUI thread.
*
* If `send_mutually_recursive_main_thread_message()` is currently being
* called, then run `fn` on the thread that's currently calling that
* function and return the result of the call.
*
* Otherwise, use `clap_plugin_proxy::run_on_main_thread()` to use CLAP's
* `clap_plugin::request_callback()` mechanic.
*
* @return The result of calling `fn`
*
* @see clap_plugin_proxy::run_on_main_thread
*/
template <std::invocable F>
std::future<std::invoke_result_t<F>> run_on_main_thread(
clap_plugin_proxy& plugin,
F&& fn) {
using Result = std::invoke_result_t<F>;
// If `ClapBridge::send_mutually_recursive_main_thread_message()` is
// currently being called, then we'll call `fn` from that same thread.
// Otherwise we'll schedule the task to be run using the host's main
// thread using `clap_host::request_callback()`. This is needed because
// `request_callback()` won't do anything if that thread is currently
// blocked.
// Modifying the `mutual_recursion_` methods to handle `void` correctly
// would lead to a lot more template soup, so we'll just work around it
// here.
// TODO: At some point, do improve the API so it can handle void without
// workaorunds
if constexpr (std::is_void_v<Result>) {
if (const auto result =
mutual_recursion_.maybe_handle([f = std::forward<F>(fn)]() {
f();
return Ack{};
})) {
// Apparently there's no way to just create a ready future
std::promise<void> result_promise;
result_promise.set_value();
return result_promise.get_future();
}
} else {
if (const auto result =
mutual_recursion_.maybe_handle(std::forward<F>(fn))) {
std::promise<Result> result_promise;
result_promise.set_value(std::move(*result));
return result_promise.get_future();
}
}
return plugin.run_on_main_thread(std::forward<F>(fn));
}
/**
* The logging facility used for this instance of yabridge. Wraps around
@@ -230,12 +266,11 @@ class ClapPluginBridge : PluginBridge<ClapSockets<std::jthread>> {
*/
std::shared_mutex plugin_proxies_mutex_;
// TODO: Do we need this in CLAP?
// /**
// * Used in `ClapBridge::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 `ClapPlugViewProxyImpl::run_loop_tasks()`.
// */
// MutualRecursionHelper<std::jthread> mutual_recursion_;
/**
* Used in `ClapBridge::send_mutually_recursive_message()` to be able to
* execute functions from that same calling thread while we're waiting for a
* response. See the uses for `send_mutually_recursive_message()` for use
* cases where this is needed.
*/
MutualRecursionHelper<std::jthread> mutual_recursion_;
};
@@ -143,7 +143,7 @@ class Vst3PlugViewProxyImpl : public Vst3PlugViewProxy {
// 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 call `fn` from that same thread.
// its GUI 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.