diff --git a/src/plugin/bridges/clap-impls/plugin-proxy.h b/src/plugin/bridges/clap-impls/plugin-proxy.h index b941c0df..2b3234aa 100644 --- a/src/plugin/bridges/clap-impls/plugin-proxy.h +++ b/src/plugin/bridges/clap-impls/plugin-proxy.h @@ -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::future> run_on_main_thread(F&& fn) { diff --git a/src/plugin/bridges/clap.cpp b/src/plugin/bridges/clap.cpp index b49fa1d2..91aba83b 100644 --- a/src/plugin/bridges/clap.cpp +++ b/src/plugin/bridges/clap.cpp @@ -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{}; diff --git a/src/plugin/bridges/clap.h b/src/plugin/bridges/clap.h index 2162b60d..5e5282bb 100644 --- a/src/plugin/bridges/clap.h +++ b/src/plugin/bridges/clap.h @@ -142,45 +142,81 @@ class ClapPluginBridge : PluginBridge> { } // 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::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::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::optional> - // maybe_run_on_mutual_recursion_thread( - // F&& fn) { - // return mutual_recursion_.maybe_handle(std::forward(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::future> run_on_main_thread( + clap_plugin_proxy& plugin, + F&& fn) { + using Result = std::invoke_result_t; + + // 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) { + if (const auto result = + mutual_recursion_.maybe_handle([f = std::forward(fn)]() { + f(); + return Ack{}; + })) { + // Apparently there's no way to just create a ready future + std::promise result_promise; + result_promise.set_value(); + + return result_promise.get_future(); + } + } else { + if (const auto result = + mutual_recursion_.maybe_handle(std::forward(fn))) { + std::promise result_promise; + result_promise.set_value(std::move(*result)); + + return result_promise.get_future(); + } + } + + return plugin.run_on_main_thread(std::forward(fn)); + } /** * The logging facility used for this instance of yabridge. Wraps around @@ -230,12 +266,11 @@ class ClapPluginBridge : PluginBridge> { */ 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 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 mutual_recursion_; }; diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.h b/src/plugin/bridges/vst3-impls/plug-view-proxy.h index 3401ee0e..95cb6dff 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.h +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.h @@ -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.