diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index a544a827..e982f557 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -122,7 +122,7 @@ void GroupBridge::handle_plugin_run(size_t plugin_id, HostBridge* bridge) { // potentially corrupt our heap. This way we can also properly join the // thread again. If no active plugins remain, then we'll terminate the // process. - boost::asio::post(main_context.context, [this, plugin_id]() { + main_context.schedule_task([this, plugin_id]() { std::lock_guard lock(active_plugins_mutex); // The join is implicit because we're using Win32Thread (which mimics diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 623f4fa0..891bc9da 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -16,8 +16,6 @@ #include "vst2.h" -#include -#include #include #include @@ -333,15 +331,13 @@ void Vst2Bridge::run() { // instantiated and where the Win32 message loop is // handled. if (unsafe_opcodes.contains(opcode)) { - std::promise dispatch_result; - boost::asio::dispatch(main_context.context, [&]() { - const intptr_t result = dispatch_wrapper( - plugin, opcode, index, value, data, option); - - dispatch_result.set_value(result); - }); - - return dispatch_result.get_future().get(); + return main_context + .run_in_context([&]() { + return dispatch_wrapper(plugin, opcode, + index, value, data, + option); + }) + .get(); } else { return dispatch_wrapper(plugin, opcode, index, value, data, option); diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 29f7201f..5cf3cb9d 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -16,11 +16,8 @@ #include "vst3.h" -#include - #include "../boost-fix.h" -#include #include #include "vst3-impls/component-handler-proxy.h" @@ -115,16 +112,15 @@ void Vst3Bridge::run() { }, [&](const Vst3PluginProxy::Destruct& request) -> Vst3PluginProxy::Destruct::Response { - std::promise latch; - - boost::asio::dispatch(main_context.context, [&]() { - // Remove the instance from within the main IO context so - // removing it doesn't interfere with the Win32 message loop - std::lock_guard lock(object_instances_mutex); - object_instances.erase(request.instance_id); - - latch.set_value(); - }); + main_context + .run_in_context([&]() { + // Remove the instance from within the main IO context + // so removing it doesn't interfere with the Win32 + // message loop + std::lock_guard lock(object_instances_mutex); + object_instances.erase(request.instance_id); + }) + .wait(); // XXX: I don't think we have to wait for the object to be // deleted most of the time, but I can imagine a situation @@ -132,7 +128,6 @@ void Vst3Bridge::run() { // Win32 timer in between where the above closure is being // executed and when the actual host application context on // the plugin side gets deallocated. - latch.get_future().wait(); return Ack{}; }, [&](Vst3PluginProxy::SetState& request) @@ -432,29 +427,29 @@ void Vst3Bridge::run() { // Creating the window and having the plugin embed in it should // be done in the main UI thread - std::promise attach_result{}; - boost::asio::dispatch(main_context.context, [&]() { - Editor& editor_instance = - object_instances[request.owner_instance_id] - .editor.emplace(config, window_class, x11_handle); + return main_context + .run_in_context([&]() { + Editor& editor_instance = + object_instances[request.owner_instance_id] + .editor.emplace(config, window_class, + x11_handle); - const tresult result = - object_instances[request.owner_instance_id] - .plug_view->attached( - editor_instance.get_win32_handle(), - type.c_str()); + const tresult result = + object_instances[request.owner_instance_id] + .plug_view->attached( + editor_instance.get_win32_handle(), + type.c_str()); - // Get rid of the editor again if the plugin didn't embed - // itself in it - if (result != Steinberg::kResultOk) { - object_instances[request.owner_instance_id] - .editor.reset(); - } + // Get rid of the editor again if the plugin didn't + // embed itself in it + if (result != Steinberg::kResultOk) { + object_instances[request.owner_instance_id] + .editor.reset(); + } - attach_result.set_value(result); - }); - - return attach_result.get_future().get(); + return result; + }) + .get(); }, [&](YaPlugView::GetSize& request) -> YaPlugView::GetSize::Response { const tresult result = diff --git a/src/wine-host/utils.h b/src/wine-host/utils.h index 9634673c..c87fc652 100644 --- a/src/wine-host/utils.h +++ b/src/wine-host/utils.h @@ -18,6 +18,7 @@ #include "boost-fix.h" +#include #include #include @@ -27,6 +28,7 @@ #endif #include +#include #include #include @@ -62,6 +64,52 @@ class MainContext { */ void stop(); + /** + * Asynchronously execute a function inside of this main IO context and + * return the results as a future. This is used to make sure that operations + * that may involve the Win32 message loop are all run from the same thread. + */ + template + std::future run_in_context(F fn) { + std::promise result{}; + std::future future = result.get_future(); + boost::asio::dispatch(context, + [result = std::move(result), fn]() mutable { + result.set_value(fn()); + }); + + return future; + } + + /** + * The same as the above, but without returning a value. This allows us to + * wait for the task to have been run. + * + * @overload + */ + template + std::future run_in_context(F fn) { + std::promise result{}; + std::future future = result.get_future(); + boost::asio::dispatch(context, + [result = std::move(result), fn]() mutable { + fn(); + result.set_value(); + }); + + return future; + } + + /** + * Run a task within the IO context. The difference with `run_in_context()` + * is that this version does not guarantee that it's going to be executed as + * soon as possible, and thus we also won't return a future. + */ + template + void schedule_task(F fn) { + boost::asio::post(context, fn); + } + /** * Start a timer to handle events every `event_loop_interval` milliseconds. * @@ -88,8 +136,8 @@ class MainContext { } /** - * The raw IO context. Can and should be used directly for everything that's - * not the event handling loop. + * The raw IO context. Used to bind our sockets onto. Running things within + * this IO context should be done with the functions above. */ boost::asio::io_context context;