diff --git a/CHANGELOG.md b/CHANGELOG.md index e68b213e..c4c82d71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ Versioning](https://semver.org/spec/v2.0.0.html). - Added a dependency on the headers-only [`ghc::filesystem`](https://github.com/gulrak/filesystem) library to replace Boost.Filesystem. +- Added a dependency on the headers-only [Asio](http://think-async.com/Asio/) + library to replace Boost.Asio. - Fixed a deprecation warning in the Meson build, causing the minimum supported Meson version to be bumped up to **Meson 0.56** from 0.55. diff --git a/README.md b/README.md index 5bde1bbf..a088fb5f 100644 --- a/README.md +++ b/README.md @@ -770,6 +770,7 @@ the following dependencies: The following dependencies are included in the repository as a Meson wrap: +- [Asio](http://think-async.com/Asio/) - [bitsery](https://github.com/fraillt/bitsery) - [function2](https://github.com/Naios/function2) - [`ghc::filesystem`](https://github.com/gulrak/filesystem) diff --git a/meson.build b/meson.build index 99281fa0..9951fd35 100644 --- a/meson.build +++ b/meson.build @@ -46,7 +46,7 @@ compiler_options = [ '-fvisibility-inlines-hidden', # Disable the use of concepts in Boost.Asio until Boost 1.73 gets released # https://github.com/boostorg/asio/issues/312 - # TODO: Rename after switching to non-Boost ASIO + # TODO: Rename after switching out Boost.Process '-DBOOST_ASIO_DISABLE_CONCEPTS', # Boost.Process's auto detection for vfork() support doesn't seem to work # TODO: Remove after adding our own library @@ -232,6 +232,7 @@ endif # These are all headers-only libraries, and thus won't require separate 32-bit # and 64-bit versions +asio_dep = dependency('asio', version : '>=1.22.0') boost_dep = dependency('boost', version : '>=1.66', static : with_static_boost) if meson.version().version_compare('>=0.60') # Bitsery's CMake build definition is capitalized for some reason @@ -295,6 +296,7 @@ shared_library( dependencies : [ configuration_dep, + asio_dep, boost_dep, with_32bit_libraries ? boost_filesystem_32bit_dep @@ -320,6 +322,7 @@ if with_vst3 dependencies : [ configuration_dep, + asio_dep, boost_dep, with_32bit_libraries ? boost_filesystem_32bit_dep diff --git a/src/common/audio-shm.h b/src/common/audio-shm.h index 92f7d42c..283925f6 100644 --- a/src/common/audio-shm.h +++ b/src/common/audio-shm.h @@ -19,7 +19,7 @@ #include #ifdef __WINE__ -#include "../wine-host/boost-fix.h" +#include "../wine-host/asio-fix.h" #endif #include #include diff --git a/src/common/communication/common.cpp b/src/common/communication/common.cpp index a2b9068f..ebdca2a3 100644 --- a/src/common/communication/common.cpp +++ b/src/common/communication/common.cpp @@ -17,6 +17,7 @@ #include "common.h" #include +#include #include "../utils.h" diff --git a/src/common/communication/common.h b/src/common/communication/common.h index fb030bec..bd528ce5 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -24,12 +24,12 @@ #include #ifdef __WINE__ -#include "../wine-host/boost-fix.h" +#include "../wine-host/asio-fix.h" #endif -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -79,51 +79,49 @@ using SerializationBuffer = boost::container::small_vector; */ using SerializationBufferBase = boost::container::small_vector_base; -namespace boost { namespace asio { template -inline BOOST_ASIO_MUTABLE_BUFFER buffer( +inline ASIO_MUTABLE_BUFFER buffer( boost::container::small_vector_base& data) - BOOST_ASIO_NOEXCEPT { - return BOOST_ASIO_MUTABLE_BUFFER( + ASIO_NOEXCEPT { + return ASIO_MUTABLE_BUFFER( data.size() ? &data[0] : 0, data.size() * sizeof(PodType) -#if defined(BOOST_ASIO_ENABLE_BUFFER_DEBUGGING) +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) , detail::buffer_debug_check::iterator>(data.begin()) -#endif // BOOST_ASIO_ENABLE_BUFFER_DEBUGGING +#endif // ASIO_ENABLE_BUFFER_DEBUGGING ); } -// These are copied verbatim `boost::asio::buffer(std::vector&, std::size_t)`, since `boost::container::small_vector` is // compatible with the STL vector. template -inline BOOST_ASIO_MUTABLE_BUFFER buffer( +inline ASIO_MUTABLE_BUFFER buffer( boost::container::small_vector_base& data, - std::size_t max_size_in_bytes) BOOST_ASIO_NOEXCEPT { - return BOOST_ASIO_MUTABLE_BUFFER( + std::size_t max_size_in_bytes) ASIO_NOEXCEPT { + return ASIO_MUTABLE_BUFFER( data.size() ? &data[0] : 0, data.size() * sizeof(PodType) < max_size_in_bytes ? data.size() * sizeof(PodType) : max_size_in_bytes -#if defined(BOOST_ASIO_ENABLE_BUFFER_DEBUGGING) +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) , detail::buffer_debug_check::iterator>(data.begin()) -#endif // BOOST_ASIO_ENABLE_BUFFER_DEBUGGING +#endif // ASIO_ENABLE_BUFFER_DEBUGGING ); } } // namespace asio -} // namespace boost /** * Serialize an object using bitsery and write it to a socket. This will write * both the size of the serialized object and the object itself over the socket. * - * @param socket The Boost.Asio socket to write to. + * @param socket The Asio socket to write to. * @param object The object to write to the stream. * @param buffer The buffer to write to. This is useful for sending audio and * chunk data since that can vary in size by a lot. @@ -149,10 +147,9 @@ inline void write_object(Socket& socket, // bit bridge. This won't make any function difference aside from the // 32-bit host application having to convert between 64 and 32 bit // integers. - boost::asio::write(socket, - boost::asio::buffer(std::array{size})); + asio::write(socket, asio::buffer(std::array{size})); const size_t bytes_written = - boost::asio::write(socket, boost::asio::buffer(buffer, size)); + asio::write(socket, asio::buffer(buffer, size)); assert(bytes_written == size); } @@ -171,7 +168,7 @@ inline void write_object(Socket& socket, const T& object) { * Deserialize an object by reading it from a socket. This should be used * together with `write_object`. This will block until the object is available. * - * @param socket The Boost.Asio socket to read from. + * @param socket The Asio socket to read from. * @param object The object to serialize into. There are also overrides that * create a new default initialized `T` * @param buffer The buffer to read into. This is useful for sending audio and @@ -180,7 +177,7 @@ inline void write_object(Socket& socket, const T& object) { * @return The deserialized object. * * @throw std::runtime_error If the conversion to an object was not successful. - * @throw boost::system::system_error If the socket is closed or gets closed + * @throw std::system_error If the socket is closed or gets closed * while reading. * * @relates write_object @@ -191,18 +188,17 @@ inline T& read_object(Socket& socket, SerializationBufferBase& buffer) { // See the note above on the use of `uint64_t` instead of `size_t` std::array message_length; - boost::asio::read(socket, boost::asio::buffer(message_length), - boost::asio::transfer_exactly(sizeof(message_length))); + asio::read(socket, asio::buffer(message_length), + asio::transfer_exactly(sizeof(message_length))); // Make sure the buffer is large enough const size_t size = message_length[0]; buffer.resize(size); - // `boost::asio::read/write` will handle all the packet splitting and + // `asio::read/write` will handle all the packet splitting and // merging for us, since local domain sockets have packet limits somewhere // in the hundreds of kilobytes - boost::asio::read(socket, boost::asio::buffer(buffer), - boost::asio::transfer_exactly(size)); + asio::read(socket, asio::buffer(buffer), asio::transfer_exactly(size)); auto [_, success] = bitsery::quickDeserialization>( @@ -371,8 +367,8 @@ class SocketHandler { * * @see Sockets::connect */ - SocketHandler(boost::asio::io_context& io_context, - boost::asio::local::stream_protocol::endpoint endpoint, + SocketHandler(asio::io_context& io_context, + asio::local::stream_protocol::endpoint endpoint, bool listen) : endpoint_(endpoint), socket_(io_context) { if (listen) { @@ -397,13 +393,13 @@ class SocketHandler { /** * Close the socket. Both sides that are actively listening will be thrown a - * `boost::system_error` when this happens. + * `std::system_error` when this happens. */ void close() { // The shutdown can fail when the socket is already closed - boost::system::error_code err; - socket_.shutdown( - boost::asio::local::stream_protocol::socket::shutdown_both, err); + std::error_code err; + socket_.shutdown(asio::local::stream_protocol::socket::shutdown_both, + err); socket_.close(); } @@ -414,7 +410,7 @@ class SocketHandler { * @param buffer The buffer to use for the serialization. This is used to * prevent excess allocations when sending audio. * - * @throw boost::system::system_error If the socket is closed or gets closed + * @throw std::system_error If the socket is closed or gets closed * during sending. * * @warning This operation is not atomic, and calling this function with the @@ -454,7 +450,7 @@ class SocketHandler { * * @throw std::runtime_error If the conversion to an object was not * successful. - * @throw boost::system::system_error If the socket is closed or gets closed + * @throw std::system_error If the socket is closed or gets closed * while reading. * * @note This function can safely be called within the lambda of @@ -513,7 +509,7 @@ class SocketHandler { receive_single(object, buffer); callback(object, buffer); - } catch (const boost::system::system_error&) { + } catch (const std::system_error&) { // This happens when the sockets got closed because the plugin // is being shut down break; @@ -522,14 +518,14 @@ class SocketHandler { } private: - boost::asio::local::stream_protocol::endpoint endpoint_; - boost::asio::local::stream_protocol::socket socket_; + asio::local::stream_protocol::endpoint endpoint_; + asio::local::stream_protocol::socket socket_; /** * Will be used in `connect()` on the listening side to establish the * connection. */ - std::optional acceptor_; + std::optional acceptor_; }; /** @@ -572,8 +568,8 @@ class AdHocSocketHandler { * * @see Sockets::connect */ - AdHocSocketHandler(boost::asio::io_context& io_context, - boost::asio::local::stream_protocol::endpoint endpoint, + AdHocSocketHandler(asio::io_context& io_context, + asio::local::stream_protocol::endpoint endpoint, bool listen) : io_context_(io_context), endpoint_(endpoint), socket_(io_context) { if (listen) { @@ -606,13 +602,13 @@ class AdHocSocketHandler { /** * Close the socket. Both sides that are actively listening will be thrown a - * `boost::system_error` when this happens. + * `std::system_error` when this happens. */ void close() { // The shutdown can fail when the socket is already closed - boost::system::error_code err; - socket_.shutdown( - boost::asio::local::stream_protocol::socket::shutdown_both, err); + std::error_code err; + socket_.shutdown(asio::local::stream_protocol::socket::shutdown_both, + err); socket_.close(); while (currently_listening_) { @@ -637,15 +633,15 @@ class AdHocSocketHandler { * socket. This is either the primary `socket`, or a new ad hock socket if * this function is currently being called from another thread. */ - template F> - std::invoke_result_t send( + template F> + std::invoke_result_t send( F&& callback) { // A bit of template and constexpr nastiness to allow us to either // return a value from the callback (for when writing the response to a // new object) or to return void (when we deserialize into an existing // object) - constexpr bool returns_void = std::is_void_v>; + constexpr bool returns_void = std::is_void_v< + std::invoke_result_t>; // XXX: Maybe at some point we should benchmark how often this // ad hoc socket spawning mechanism gets used. If some hosts @@ -667,12 +663,12 @@ class AdHocSocketHandler { } } else { try { - boost::asio::local::stream_protocol::socket secondary_socket( + asio::local::stream_protocol::socket secondary_socket( io_context_); secondary_socket.connect(endpoint_); return callback(secondary_socket); - } catch (const boost::system::system_error&) { + } catch (const std::system_error&) { // So, what do we do when noone is listening on the endpoint // yet? This can happen with plugin groups when the Wine // host process does an `audioMaster()` call before the @@ -721,8 +717,8 @@ class AdHocSocketHandler { * same thing as `primary_callback`, but secondary sockets may need some * different handling. */ - template F, - std::invocable G> + template F, + std::invocable G> void receive_multi(std::optional> logger, F&& primary_callback, G&& secondary_callback) { @@ -738,7 +734,7 @@ class AdHocSocketHandler { // thread to handle the request. When `socket` closes and this loop // breaks, the listener and any still active threads will be cleaned up // before this function exits. - boost::asio::io_context secondary_context{}; + asio::io_context secondary_context{}; // The previous acceptor has already been shut down by // `AdHocSocketHandler::connect()` @@ -751,21 +747,21 @@ class AdHocSocketHandler { std::mutex active_secondary_requests_mutex{}; accept_requests( *acceptor_, logger, - [&](boost::asio::local::stream_protocol::socket secondary_socket) { + [&](asio::local::stream_protocol::socket secondary_socket) { const size_t request_id = next_request_id.fetch_add(1); // We have to make sure to keep moving these sockets into the // threads that will handle them std::lock_guard lock(active_secondary_requests_mutex); active_secondary_requests[request_id] = Thread( - [&, request_id](boost::asio::local::stream_protocol::socket - secondary_socket) { + [&, request_id]( + asio::local::stream_protocol::socket secondary_socket) { secondary_callback(secondary_socket); // When we have processed this request, we'll join the // thread again with the thread that's handling // `secondary_context` - boost::asio::post(secondary_context, [&, request_id]() { + asio::post(secondary_context, [&, request_id]() { std::lock_guard lock( active_secondary_requests_mutex); @@ -791,7 +787,7 @@ class AdHocSocketHandler { while (true) { try { primary_callback(socket_); - } catch (const boost::system::system_error&) { + } catch (const std::system_error&) { // This happens when the sockets got closed because the plugin // is being shut down break; @@ -814,7 +810,7 @@ class AdHocSocketHandler { * * @overload */ - template F> + template F> void receive_multi(std::optional> logger, F&& callback) { receive_multi(logger, callback, std::forward(callback)); @@ -831,16 +827,15 @@ class AdHocSocketHandler { * should only be passed on the plugin side. * @param callback A function that handles the new socket connection. */ - template F> - void accept_requests( - boost::asio::local::stream_protocol::acceptor& acceptor, - std::optional> logger, - F&& callback) { + template F> + void accept_requests(asio::local::stream_protocol::acceptor& acceptor, + std::optional> logger, + F&& callback) { acceptor.async_accept( [&, logger, callback]( - const boost::system::error_code& error, - boost::asio::local::stream_protocol::socket secondary_socket) { - if (error.failed()) { + const std::error_code& error, + asio::local::stream_protocol::socket secondary_socket) { + if (error) { // On the Wine side it's expected that the primary socket // connection will be dropped during shutdown, so we can // silently ignore any related socket errors on the Wine @@ -865,10 +860,10 @@ class AdHocSocketHandler { * bound to this context. In `receive_multi()` we'll create a new IO context * since we want to do all listening there on a dedicated thread. */ - boost::asio::io_context& io_context_; + asio::io_context& io_context_; - boost::asio::local::stream_protocol::endpoint endpoint_; - boost::asio::local::stream_protocol::socket socket_; + asio::local::stream_protocol::endpoint endpoint_; + asio::local::stream_protocol::socket socket_; /** * This acceptor will be used once synchronously on the listening side @@ -880,7 +875,7 @@ class AdHocSocketHandler { * but all additional incoming connections of course have to be listened for * on the plugin side. */ - std::optional acceptor_; + std::optional acceptor_; /** * After the socket gets closed, we do some cleanup at the end of diff --git a/src/common/communication/vst2.cpp b/src/common/communication/vst2.cpp index 61e47a0d..2fb3e0f6 100644 --- a/src/common/communication/vst2.cpp +++ b/src/common/communication/vst2.cpp @@ -73,7 +73,7 @@ intptr_t DefaultDataConverter::return_value(const int /*opcode*/, } Vst2EventResult DefaultDataConverter::send_event( - boost::asio::local::stream_protocol::socket& socket, + asio::local::stream_protocol::socket& socket, const Vst2Event& event, SerializationBufferBase& buffer) const { write_object(socket, event, buffer); diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index 39df3ad1..c9fea0cd 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -86,7 +86,7 @@ class DefaultDataConverter { * specific opcodes to allow mutually recursive calling sequences. */ virtual Vst2EventResult send_event( - boost::asio::local::stream_protocol::socket& socket, + asio::local::stream_protocol::socket& socket, const Vst2Event& event, SerializationBufferBase& buffer) const; }; @@ -137,8 +137,8 @@ class Vst2EventHandler : public AdHocSocketHandler { * * @see Sockets::connect */ - Vst2EventHandler(boost::asio::io_context& io_context, - boost::asio::local::stream_protocol::endpoint endpoint, + Vst2EventHandler(asio::io_context& io_context, + asio::local::stream_protocol::endpoint endpoint, bool listen) : AdHocSocketHandler(io_context, endpoint, listen) {} @@ -206,7 +206,7 @@ class Vst2EventHandler : public AdHocSocketHandler { // that potentially need to have their responses handled on the same // calling thread (i.e. mutual recursion). const Vst2EventResult response = this->send( - [&](boost::asio::local::stream_protocol::socket& socket) { + [&](asio::local::stream_protocol::socket& socket) { return data_converter.send_event(socket, event, serialization_buffer()); }); @@ -250,7 +250,7 @@ class Vst2EventHandler : public AdHocSocketHandler { // Reading, processing, and writing back event data from the sockets // works in the same way regardless of which socket we're using const auto process_event = - [&](boost::asio::local::stream_protocol::socket& socket, + [&](asio::local::stream_protocol::socket& socket, bool on_main_thread) { SerializationBufferBase& buffer = serialization_buffer(); @@ -276,10 +276,10 @@ class Vst2EventHandler : public AdHocSocketHandler { this->receive_multi( logging ? std::optional(std::ref(logging->first.logger_)) : std::nullopt, - [&](boost::asio::local::stream_protocol::socket& socket) { + [&](asio::local::stream_protocol::socket& socket) { process_event(socket, true); }, - [&](boost::asio::local::stream_protocol::socket& socket) { + [&](asio::local::stream_protocol::socket& socket) { process_event(socket, false); }); } @@ -342,7 +342,7 @@ class Vst2Sockets final : public Sockets { * * @see Vst2Sockets::connect */ - Vst2Sockets(boost::asio::io_context& io_context, + Vst2Sockets(asio::io_context& io_context, const ghc::filesystem::path& endpoint_base_dir, bool listen) : Sockets(endpoint_base_dir), diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index be75d5cc..73aebe03 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -57,8 +57,8 @@ class Vst3MessageHandler : public AdHocSocketHandler { * * @see Sockets::connect */ - Vst3MessageHandler(boost::asio::io_context& io_context, - boost::asio::local::stream_protocol::endpoint endpoint, + Vst3MessageHandler(asio::io_context& io_context, + asio::local::stream_protocol::endpoint endpoint, bool listen) : AdHocSocketHandler(io_context, endpoint, listen) {} @@ -138,7 +138,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { // messages from arriving out of order. `AdHocSocketHandler::send()` // will either use a long-living primary socket, or if that's currently // in use it will spawn a new socket for us. - this->send([&](boost::asio::local::stream_protocol::socket& socket) { + this->send([&](asio::local::stream_protocol::socket& socket) { write_object(socket, Request(object), buffer); read_object(socket, response_object, buffer); }); @@ -205,7 +205,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { // we receive works in the same way regardless of which socket we're // using const auto process_message = - [&](boost::asio::local::stream_protocol::socket& socket) { + [&](asio::local::stream_protocol::socket& socket) { // The persistent buffer is only used when the // `persistent_buffers` template value is enabled, but we'll // always use the thread local persistent object. Because of @@ -310,7 +310,7 @@ class Vst3Sockets final : public Sockets { * * @see Vst3Sockets::connect */ - Vst3Sockets(boost::asio::io_context& io_context, + Vst3Sockets(asio::io_context& io_context, const ghc::filesystem::path& endpoint_base_dir, bool listen) : Sockets(endpoint_base_dir), @@ -513,7 +513,7 @@ class Vst3Sockets final : public Sockets { audio_processor_buffer); } - boost::asio::io_context& io_context_; + asio::io_context& io_context_; /** * Every `IAudioProcessor` or `IComponent` instance (which likely implements diff --git a/src/common/logging/common.h b/src/common/logging/common.h index 9b2035b1..70f700cb 100644 --- a/src/common/logging/common.h +++ b/src/common/logging/common.h @@ -21,12 +21,21 @@ #include #ifdef __WINE__ -#include "../wine-host/boost-fix.h" +#include "../wine-host/asio-fix.h" #endif -#include -#include +#include +#include + +// FIXME: Remove when we get rid of the patched_async_pipe +#include +#include +#include + +// FIXME: Get rid of Boost.Process and all of the wrangling below #include +#include +#include #include "../utils.h" @@ -39,12 +48,200 @@ * * Check if this is still needed for other distros after Arch starts packaging * Boost 1.73. + * + * FIXME: This has been adopted to work with standalone Asio, we should replace + * this when we replace Boost.Process */ -class patched_async_pipe : public boost::process::async_pipe { - public: - using boost::process::async_pipe::async_pipe; +class patched_async_pipe { + ::asio::posix::stream_descriptor _source; + ::asio::posix::stream_descriptor _sink; + public: + typedef int native_handle_type; + typedef ::asio::posix::stream_descriptor handle_type; typedef typename handle_type::executor_type executor_type; + + executor_type get_executor() { return _source.get_executor(); } + + inline patched_async_pipe(asio::io_context& ios) + : patched_async_pipe(ios, ios) {} + + inline patched_async_pipe(asio::io_context& ios_source, + asio::io_context& ios_sink) + : _source(ios_source), _sink(ios_sink) { + int fds[2]; + if (::pipe(fds) == -1) + boost::process::detail::throw_last_error("pipe(2) failed"); + + _source.assign(fds[0]); + _sink.assign(fds[1]); + }; + + inline patched_async_pipe(const patched_async_pipe& lhs); + patched_async_pipe(patched_async_pipe&& lhs) + : _source(std::move(lhs._source)), _sink(std::move(lhs._sink)) { + lhs._source = + ::asio::posix::stream_descriptor{lhs._source.get_executor()}; + lhs._sink = ::asio::posix::stream_descriptor{lhs._sink.get_executor()}; + } + + template > + explicit patched_async_pipe( + ::asio::io_context& ios_source, + ::asio::io_context& ios_sink, + const boost::process::detail::posix::basic_pipe& p) + : _source(ios_source, p.native_source()), + _sink(ios_sink, p.native_sink()) {} + + template > + explicit patched_async_pipe( + asio::io_context& ios, + const boost::process::detail::posix::basic_pipe& p) + : patched_async_pipe(ios, ios, p) {} + + template > + inline patched_async_pipe& operator=( + const boost::process::detail::posix::basic_pipe& p); + inline patched_async_pipe& operator=(const patched_async_pipe& rhs); + + inline patched_async_pipe& operator=(patched_async_pipe&& lhs); + + ~patched_async_pipe() { + std::error_code ec; + close(ec); + } + + template > + inline explicit + operator boost::process::detail::posix::basic_pipe() const; + + void cancel() { + if (_sink.is_open()) + _sink.cancel(); + if (_source.is_open()) + _source.cancel(); + } + + void close() { + if (_sink.is_open()) + _sink.close(); + if (_source.is_open()) + _source.close(); + } + void close(std::error_code& ec) { + if (_sink.is_open()) + _sink.close(ec); + if (_source.is_open()) + _source.close(ec); + } + + bool is_open() const { return _sink.is_open() || _source.is_open(); } + void async_close() { + if (_sink.is_open()) + asio::post(_sink.get_executor(), [this] { _sink.close(); }); + if (_source.is_open()) + asio::post(_source.get_executor(), [this] { _source.close(); }); + } + + template + std::size_t read_some(const MutableBufferSequence& buffers) { + return _source.read_some(buffers); + } + template + std::size_t write_some(const MutableBufferSequence& buffers) { + return _sink.write_some(buffers); + } + + template + std::size_t read_some(const MutableBufferSequence& buffers, + std::error_code& ec) noexcept { + return _source.read_some(buffers, ec); + } + template + std::size_t write_some(const MutableBufferSequence& buffers, + std::error_code& ec) noexcept { + return _sink.write_some(buffers, ec); + } + + native_handle_type native_source() const { + return const_cast(_source) + .native_handle(); + } + native_handle_type native_sink() const { + return const_cast(_sink) + .native_handle(); + } + + template + ASIO_INITFN_RESULT_TYPE(ReadHandler, void(std::error_code, std::size_t)) + async_read_some(const MutableBufferSequence& buffers, + ReadHandler&& handler) { + return _source.async_read_some(buffers, + std::forward(handler)); + } + + template + ASIO_INITFN_RESULT_TYPE(WriteHandler, void(std::error_code, std::size_t)) + async_write_some(const ConstBufferSequence& buffers, + WriteHandler&& handler) { + return _sink.async_write_some(buffers, + std::forward(handler)); + } + + const handle_type& sink() const& { return _sink; } + const handle_type& source() const& { return _source; } + + handle_type&& sink() && { return std::move(_sink); } + handle_type&& source() && { return std::move(_source); } + + handle_type source(::asio::io_context& ios) && { + ::asio::posix::stream_descriptor stolen(ios, _source.release()); + return stolen; + } + handle_type sink(::asio::io_context& ios) && { + ::asio::posix::stream_descriptor stolen(ios, _sink.release()); + return stolen; + } + + handle_type source(::asio::io_context& ios) const& { + auto source_in = const_cast<::asio::posix::stream_descriptor&>(_source) + .native_handle(); + return ::asio::posix::stream_descriptor(ios, ::dup(source_in)); + } + handle_type sink(::asio::io_context& ios) const& { + auto sink_in = const_cast<::asio::posix::stream_descriptor&>(_sink) + .native_handle(); + return ::asio::posix::stream_descriptor(ios, ::dup(sink_in)); + } +}; + +// Even more of a mess, we can't use the nice `bp::std_out = ...`/`bp::std_err = +// ...` syntax anymore. +template +struct patched_async_pipe_out + : public boost::process::detail::posix::pipe_out { + patched_async_pipe& pipe; + template + patched_async_pipe_out(AsyncPipe& p) + : boost::process::detail::posix::pipe_out(p.native_sink(), + p.native_source()), + pipe(p) {} + + template + static void close(Pipe& pipe, Executor&) { + std::error_code ec; + std::move(pipe).sink().close(ec); + } + + template + void on_error(Executor& exec, const std::error_code&) { + close(pipe, exec); + } + + template + void on_success(Executor& exec) { + close(pipe, exec); + } }; /** @@ -160,21 +357,21 @@ class Logger { * Write output from an async pipe to the log on a line by line basis. * Useful for logging the Wine process's STDOUT and STDERR streams. * - * @param pipe Some Boost.Asio stream that can be read from. Probably either + * @param pipe Some Asio stream that can be read from. Probably either * `patched_async_pipe` or a stream descriptor. * @param buffer The buffer that will be used to read from `pipe`. * @param prefix Text to prepend to the line before writing to the log. */ template void async_log_pipe_lines(T& pipe, - boost::asio::streambuf& buffer, + asio::streambuf& buffer, std::string prefix = "") { - boost::asio::async_read_until( + asio::async_read_until( pipe, buffer, '\n', - [&, prefix](const boost::system::error_code& error, size_t) { + [&, prefix](const std::error_code& error, size_t) { // When we get an error code then that likely means that the // pipe has been clsoed and we have reached the end of the file - if (error.failed()) { + if (error) { return; } diff --git a/src/common/mutual-recursion.h b/src/common/mutual-recursion.h index 13af9fab..8921b385 100644 --- a/src/common/mutual-recursion.h +++ b/src/common/mutual-recursion.h @@ -21,10 +21,10 @@ #include #ifdef __WINE__ -#include "../wine-host/boost-fix.h" +#include "../wine-host/asio-fix.h" #endif -#include -#include +#include +#include /** * A helper to allow mutually recursive calling sequences with remote function @@ -83,8 +83,8 @@ class MutualRecursionHelper { // as we need to support multiple levels of mutual recursion. This can // for instance happen during `IPlugView::attached() -> // IPlugFrame::resizeView() -> IPlugView::onSize()`. - std::shared_ptr current_io_context = - std::make_shared(); + 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); @@ -93,7 +93,7 @@ class MutualRecursionHelper { // 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); + auto work_guard = asio::make_work_guard(*current_io_context); // We will call the function from another thread so we can handle calls // to `handle()`/`maybe_handle()` from this thread @@ -168,7 +168,7 @@ class MutualRecursionHelper { // pretend that we're not doing any async things here std::packaged_task do_call(std::forward(fn)); std::future do_call_response = do_call.get_future(); - boost::asio::dispatch(*mutual_recursion_contexts_.back(), + asio::dispatch(*mutual_recursion_contexts_.back(), std::move(do_call)); mutual_recursion_lock.unlock(); @@ -186,7 +186,7 @@ class MutualRecursionHelper { * active one. If the stack is empty, then there's currently no mutual * recursion going on. */ - std::vector> + std::vector> mutual_recursion_contexts_; std::mutex mutual_recursion_contexts_mutex_; }; diff --git a/src/plugin/bridges/common.h b/src/plugin/bridges/common.h index 9792f41e..b771f0a4 100644 --- a/src/plugin/bridges/common.h +++ b/src/plugin/bridges/common.h @@ -74,7 +74,7 @@ class PluginBridge { * found, or if it could not locate and load a VST3 module. */ template F> PluginBridge(PluginType plugin_type, F&& create_socket_instance) // This is still correct for VST3 plugins because we can configure an @@ -367,7 +367,7 @@ class PluginBridge { // sockets and we'll be hanging here indefinitely. To prevent this, // we'll periodically poll whether the Wine process is still running, // and throw when it is not. The alternative would be to rewrite this to - // using `async_accept`, Boost.Asio timers, and another IO context, but + // using `async_accept`, Asio timers, and another IO context, but // I feel like this a much simpler solution. host_watchdog_handler_ = std::jthread([&](std::stop_token st) { using namespace std::literals::chrono_literals; @@ -441,7 +441,7 @@ class PluginBridge { */ const PluginInfo info_; - boost::asio::io_context io_context_; + asio::io_context io_context_; /** * The sockets used for communication with the Wine process. @@ -485,7 +485,7 @@ class PluginBridge { std::future has_realtime_priority_; /** - * Runs the Boost.Asio `io_context_` thread for logging the Wine process + * Runs the Asio `io_context_` thread for logging the Wine process * STDOUT and STDERR messages. */ std::jthread wine_io_handler_; diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 3f2a180a..9350222c 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -38,7 +38,7 @@ Vst2PluginBridge& get_bridge_instance(const AEffect& plugin) noexcept { Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) : PluginBridge( PluginType::vst2, - [](boost::asio::io_context& io_context, const PluginInfo& info) { + [](asio::io_context& io_context, const PluginInfo& info) { return Vst2Sockets( io_context, generate_endpoint_base(info.native_library_path_.filename() @@ -199,7 +199,7 @@ Vst2PluginBridge::~Vst2PluginBridge() noexcept { // The `stop()` method will cause the IO context to just drop all of its // outstanding work immediately io_context_.stop(); - } catch (const boost::system::system_error&) { + } catch (const std::system_error&) { // It could be that the sockets have already been closed or that the // process has already exited (at which point we probably won't be // executing this, but maybe if all the stars align) @@ -538,7 +538,7 @@ intptr_t Vst2PluginBridge::dispatch(AEffect* /*plugin*/, return_value = sockets_.host_vst_dispatch_.send_event( converter, std::pair(logger_, true), opcode, index, value, data, option); - } catch (const boost::system::system_error&) { + } catch (const std::system_error&) { // Thrown when the socket gets closed because the VST plugin // loaded into the Wine process crashed during shutdown logger_.log("The plugin crashed during shutdown, ignoring"); diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index a396fb04..ce010bca 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -18,7 +18,7 @@ #include -#include +#include #include #include "../../common/communication/vst2.h" diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.h b/src/plugin/bridges/vst3-impls/plug-view-proxy.h index dfb4e3d3..868450e6 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.h +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.h @@ -32,7 +32,7 @@ class RunLoopTasks : public Steinberg::Linux::IEventHandler { public: /** * Register an event handler in the host's run loop so we can schedule tasks - * to be run from there. This works very much like how we use Boost.Asio IO + * to be run from there. This works very much like how we use Asio IO * contexts everywhere else to run functions on other threads. All of this * is backed by a dummy Unix domain socket, although REAPER will call the * event handler regardless of whether the file descriptor is ready or not. diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 56793224..507b93d2 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -27,7 +27,7 @@ using namespace std::literals::string_literals; Vst3PluginBridge::Vst3PluginBridge() : PluginBridge( PluginType::vst3, - [](boost::asio::io_context& io_context, const PluginInfo& info) { + [](asio::io_context& io_context, const PluginInfo& info) { return Vst3Sockets( io_context, generate_endpoint_base(info.native_library_path_.filename() @@ -415,7 +415,7 @@ Vst3PluginBridge::~Vst3PluginBridge() noexcept { // Drop all work make sure all sockets are closed plugin_host_->terminate(); io_context_.stop(); - } catch (const boost::system::system_error&) { + } catch (const std::system_error&) { // It could be that the sockets have already been closed or that the // process has already exited (at which point we probably won't be // executing this, but maybe if all the stars align) diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index febdc619..e31e4d90 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -16,7 +16,7 @@ #include "host-process.h" -#include +#include #include #include @@ -25,7 +25,7 @@ namespace bp = boost::process; namespace fs = ghc::filesystem; -HostProcess::HostProcess(boost::asio::io_context& io_context, +HostProcess::HostProcess(asio::io_context& io_context, Logger& logger, const Configuration& config, Sockets& sockets) @@ -53,7 +53,7 @@ HostProcess::HostProcess(boost::asio::io_context& io_context, HostProcess::~HostProcess() noexcept {} -IndividualHost::IndividualHost(boost::asio::io_context& io_context, +IndividualHost::IndividualHost(asio::io_context& io_context, Logger& logger, const Configuration& config, Sockets& sockets, @@ -115,7 +115,7 @@ void IndividualHost::terminate() { host_.wait(); } -GroupHost::GroupHost(boost::asio::io_context& io_context, +GroupHost::GroupHost(asio::io_context& io_context, Logger& logger, const Configuration& config, Sockets& sockets, @@ -140,7 +140,7 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, plugin_info.plugin_arch_); const auto connect = [&io_context, host_request, endpoint_base_dir, group_socket_path]() { - boost::asio::local::stream_protocol::socket group_socket(io_context); + asio::local::stream_protocol::socket group_socket(io_context); group_socket.connect(group_socket_path.string()); write_object(group_socket, host_request); @@ -151,7 +151,7 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, try { // Request an existing group host process to host our plugin connect(); - } catch (const boost::system::system_error&) { + } catch (const std::system_error&) { // In case we could not connect to the socket, then we'll start a // new group host process. This process is detached immediately // because it should run independently of this yabridge instance as @@ -178,7 +178,7 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, try { connect(); return; - } catch (const boost::system::system_error&) { + } catch (const std::system_error&) { // Keep trying to connect until either connection gets // accepted or the group host crashes } @@ -191,7 +191,7 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, // connect once more, before concluding that we failed. try { connect(); - } catch (const boost::system::system_error&) { + } catch (const std::system_error&) { startup_failed_ = true; } }); diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index 509ed71f..66376c2b 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -18,8 +18,8 @@ #include -#include -#include +#include +#include #include #include #include @@ -91,8 +91,11 @@ class HostProcess { // FIXME: Replace Boost.Filesystem host_path.string(), #endif // WITH_WINEDBG - boost::process::std_out = stdout_pipe_, - boost::process::std_err = stderr_pipe_, + // FIXME: This won't work with our patched async_pipe version + // boost::process::std_out = stdout_pipe_, + // boost::process::std_err = stderr_pipe_, + patched_async_pipe_out<1, -1>(stdout_pipe_), + patched_async_pipe_out<2, -1>(stderr_pipe_), // NOTE: If the Wine process outlives the host, then it may cause // issues if our process is still keeping the host's file // descriptors alive that. This can prevent Ardour from @@ -140,7 +143,7 @@ class HostProcess { * with the plugin. When the plugin shuts down, we'll close all of the * sockets used by the plugin. */ - HostProcess(boost::asio::io_context& io_context, + HostProcess(asio::io_context& io_context, Logger& logger, const Configuration& config, Sockets& sockets); @@ -171,8 +174,8 @@ class HostProcess { */ Logger& logger_; - boost::asio::streambuf stdout_buffer_; - boost::asio::streambuf stderr_buffer_; + asio::streambuf stdout_buffer_; + asio::streambuf stderr_buffer_; }; /** @@ -201,7 +204,7 @@ class IndividualHost : public HostProcess { * @throw std::runtime_error When `plugin_path` does not point to a valid * 32-bit or 64-bit .dll file. */ - IndividualHost(boost::asio::io_context& io_context, + IndividualHost(asio::io_context& io_context, Logger& logger, const Configuration& config, Sockets& sockets, @@ -249,7 +252,7 @@ class GroupHost : public HostProcess { * @param host_request The information about the plugin we should launch a * host process for. This object will be sent to the group host process. */ - GroupHost(boost::asio::io_context& io_context, + GroupHost(asio::io_context& io_context, Logger& logger, const Configuration& config, Sockets& sockets, diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp index 62d1c2f9..be71c148 100644 --- a/src/plugin/utils.cpp +++ b/src/plugin/utils.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -466,7 +465,7 @@ bool send_notification(const std::string& title, << "\">" << xml_escape(this_library.filename().string()) << ""; - } catch (const boost::system::system_error&) { + } catch (const std::system_error&) { // I don't think this can fail in the way we're using it, but the // last thing we want is our notification informing the user of an // exception to trigger another exception diff --git a/src/wine-host/asio-fix.h b/src/wine-host/asio-fix.h new file mode 100644 index 00000000..e96424e4 --- /dev/null +++ b/src/wine-host/asio-fix.h @@ -0,0 +1,52 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020-2022 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +// Libraries like (Boost.)Asio think we're compiling on Windows or using a MSVC +// toolchain. This will cause them to make incorrect assumptions which platform +// specific features are available. The only way around this I could think of +// was to just temporarily undefine the macros these libraries use to detect +// it's running under a WIN32 environment. If anyone knows a better way to do +// this, please let me know! + +#pragma push_macro("WIN32") +#pragma push_macro("_WIN32") +#pragma push_macro("__WIN32__") +#pragma push_macro("_WIN64") + +#undef WIN32 +#undef _WIN32 +#undef __WIN32__ +#undef _WIN64 + +// This would be the minimal include needed to get Asio to work. The commented +// out includes are the actual header that would cause compile errors if not +// included here, but including headers from the detail directory directly +// didn't sound like a great idea. + +// FIXME: Remove Boost stuff +#include +#include +#include +#include +// #include +// #include + +#pragma pop_macro("WIN32") +#pragma pop_macro("_WIN32") +#pragma pop_macro("__WIN32__") +#pragma pop_macro("_WIN64") diff --git a/src/wine-host/bridges/common.h b/src/wine-host/bridges/common.h index e7be446a..e70e854b 100644 --- a/src/wine-host/bridges/common.h +++ b/src/wine-host/bridges/common.h @@ -16,7 +16,7 @@ #pragma once -#include "../boost-fix.h" +#include "../asio-fix.h" #include diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index 8940dc5a..1d0ec489 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -16,7 +16,7 @@ #include "group.h" -#include "../boost-fix.h" +#include "../asio-fix.h" #include #include @@ -49,23 +49,23 @@ using namespace std::literals::chrono_literals; * @throw std::runtime_error If another process is already listening on the * endpoint. */ -boost::asio::local::stream_protocol::acceptor create_acceptor_if_inactive( - boost::asio::io_context& io_context, - boost::asio::local::stream_protocol::endpoint& endpoint); +asio::local::stream_protocol::acceptor create_acceptor_if_inactive( + asio::io_context& io_context, + asio::local::stream_protocol::endpoint& endpoint); /** * Create a logger prefix containing the group name based on the socket path. */ std::string create_logger_prefix(const fs::path& socket_path); -StdIoCapture::StdIoCapture(boost::asio::io_context& io_context, - int file_descriptor) +StdIoCapture::StdIoCapture(asio::io_context& io_context, int file_descriptor) : pipe_(io_context), target_fd_(file_descriptor), original_fd_copy_(dup(file_descriptor)) { // We'll use the second element of these two file descriptors to reopen // `file_descriptor`, and the first one to read the captured contents from if (::pipe(pipe_fd_) != 0) { + std::cerr << "Could not create pipe" << std::endl; throw std::system_error(errno, std::system_category()); } @@ -171,12 +171,12 @@ void GroupBridge::handle_incoming_connections() { void GroupBridge::accept_requests() { group_socket_acceptor_.async_accept( - [&](const boost::system::error_code& error, - boost::asio::local::stream_protocol::socket socket) { + [&](const std::error_code& error, + asio::local::stream_protocol::socket socket) { std::lock_guard lock(active_plugins_mutex_); // Stop the whole process when the socket gets closed unexpectedly - if (error.failed()) { + if (error) { logger_.log("Error while listening for incoming connections:"); logger_.log(error.message()); @@ -284,14 +284,13 @@ void GroupBridge::async_handle_events() { [&]() { return !is_event_loop_inhibited(); }); } -boost::asio::local::stream_protocol::acceptor create_acceptor_if_inactive( - boost::asio::io_context& io_context, - boost::asio::local::stream_protocol::endpoint& endpoint) { +asio::local::stream_protocol::acceptor create_acceptor_if_inactive( + asio::io_context& io_context, + asio::local::stream_protocol::endpoint& endpoint) { // First try to listen on the endpoint normally try { - return boost::asio::local::stream_protocol::acceptor(io_context, - endpoint); - } catch (const boost::system::system_error&) { + return asio::local::stream_protocol::acceptor(io_context, endpoint); + } catch (const std::system_error&) { // If this failed, then either there is a stale socket file or another // process is already is already listening. In the last case we will // simply throw so the other process can handle the request. @@ -312,8 +311,7 @@ boost::asio::local::stream_protocol::acceptor create_acceptor_if_inactive( // At this point we can remove the stale socket and start listening fs::remove(endpoint_path); - return boost::asio::local::stream_protocol::acceptor(io_context, - endpoint); + return asio::local::stream_protocol::acceptor(io_context, endpoint); } } @@ -322,10 +320,10 @@ void GroupBridge::maybe_schedule_shutdown( std::lock_guard lock(shutdown_timer_mutex_); shutdown_timer_.expires_after(delay); - shutdown_timer_.async_wait([this](const boost::system::error_code& error) { + shutdown_timer_.async_wait([this](const std::error_code& error) { // A previous timer gets canceled automatically when another plugin // exits - if (error.failed()) { + if (error) { return; } diff --git a/src/wine-host/bridges/group.h b/src/wine-host/bridges/group.h index ddeae468..4eb73399 100644 --- a/src/wine-host/bridges/group.h +++ b/src/wine-host/bridges/group.h @@ -19,9 +19,9 @@ #include #include -#include "../boost-fix.h" +#include "../asio-fix.h" -#include +#include #include "../common/logging/common.h" #include "../utils.h" @@ -49,7 +49,7 @@ class StdIoCapture { * * @throw std::system_error If the pipe could not be created. */ - StdIoCapture(boost::asio::io_context& io_context, int file_descriptor); + StdIoCapture(asio::io_context& io_context, int file_descriptor); /** * On cleanup, close the outgoing file descriptor from the pipe and restore @@ -65,9 +65,9 @@ class StdIoCapture { /** * The pipe endpoint where all output from the original file descriptor gets - * redirected to. This can be read from like any other `Boost.Asio` stream. + * redirected to. This can be read from like any other Asio stream. */ - boost::asio::posix::stream_descriptor pipe_; + asio::posix::stream_descriptor pipe_; private: /** @@ -116,7 +116,7 @@ class GroupBridge { * where `` is a numerical hash as explained in the * `create_logger_prefix()` function in `./group.cpp`. * - * @throw boost::system::system_error If we can't listen on the socket. + * @throw std::system_error If we can't listen on the socket. * @throw std::system_error If the pipe could not be created. * * @note Creating an `GroupBridge` instance has the side effect that the @@ -220,10 +220,10 @@ class GroupBridge { * related operation should be run from the same thread, we can't just add * another thread to the main IO context. */ - boost::asio::io_context stdio_context_; + asio::io_context stdio_context_; - boost::asio::streambuf stdout_buffer_; - boost::asio::streambuf stderr_buffer_; + asio::streambuf stdout_buffer_; + asio::streambuf stderr_buffer_; /** * Contains a pipe used for capturing this process's STDOUT stream. Needed * to be able to process the output generated by Wine and plugins and to be @@ -241,12 +241,12 @@ class GroupBridge { */ Win32Thread stdio_handler_; - boost::asio::local::stream_protocol::endpoint group_socket_endpoint_; + asio::local::stream_protocol::endpoint group_socket_endpoint_; /** * The UNIX domain socket acceptor that will be used to listen for incoming * connections to spawn new plugins within this process. */ - boost::asio::local::stream_protocol::acceptor group_socket_acceptor_; + asio::local::stream_protocol::acceptor group_socket_acceptor_; /** * A map of threads that are currently hosting a plugin within this process @@ -281,7 +281,7 @@ class GroupBridge { * * @see handle_plugin_run */ - boost::asio::steady_timer shutdown_timer_; + asio::steady_timer shutdown_timer_; /** * A mutex to prevent two threads from simultaneously modifying the shutdown * timer when multiple plugins exit at the same time. diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index bd338cc2..b6d0b599 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -623,7 +623,7 @@ class HostCallbackDataConverter : public DefaultDataConverter { } Vst2EventResult send_event( - boost::asio::local::stream_protocol::socket& socket, + asio::local::stream_protocol::socket& socket, const Vst2Event& event, SerializationBufferBase& buffer) const override { if (mutually_recursive_callbacks.contains(event.opcode)) { diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 8ed91256..98e36a77 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -16,7 +16,7 @@ #pragma once -#include "../boost-fix.h" +#include "../asio-fix.h" #include #include diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 1e745654..dba7e83d 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -228,17 +228,17 @@ DeferredWin32Window::~DeferredWin32Window() noexcept { // `IPlugView::~IPlugView`. Delaying this seems to be a best of both // worlds solution that works as expected in every host I've tested. try { - std::shared_ptr destroy_timer = - std::make_shared(main_context_.context_); + std::shared_ptr destroy_timer = + std::make_shared(main_context_.context_); destroy_timer->expires_after(1s); // Note that we capture a copy of `destroy_timer` here. This way we // don't have to manage the timer instance ourselves as it will just // clean itself up after this lambda gets called. destroy_timer->async_wait( - [destroy_timer, handle = handle_, x11_connection = x11_connection_]( - const boost::system::error_code& error) { - if (error.failed()) { + [destroy_timer, handle = handle_, + x11_connection = x11_connection_](const std::error_code& error) { + if (error) { return; } diff --git a/src/wine-host/group-host.cpp b/src/wine-host/group-host.cpp index 351be6be..eb11a763 100644 --- a/src/wine-host/group-host.cpp +++ b/src/wine-host/group-host.cpp @@ -76,21 +76,19 @@ __cdecl // Blocks the main thread until all plugins have exited bridge.handle_incoming_connections(); - } catch (const boost::system::system_error& error) { + } catch (const std::system_error& error) { // If another process is already listening on the socket, we'll just // print a message and exit quietly. This could happen if the host // starts multiple yabridge instances that all use the same plugin group // at the same time. + // The same error is also used if we could not create a pipe. Since that + // error is so rare, we'll just print to STDERR before that happens to + // differentiate the two cases. std::cerr << "Another process is already listening on this group's " "socket, connecting to the existing process:" << std::endl; std::cerr << error.what() << std::endl; - return 0; - } catch (const std::system_error& error) { - std::cerr << "Could not create pipe:" << std::endl; - std::cerr << error.what() << std::endl; - return 0; } diff --git a/src/wine-host/meson.build b/src/wine-host/meson.build index ea102d6b..3ca29216 100644 --- a/src/wine-host/meson.build +++ b/src/wine-host/meson.build @@ -10,6 +10,7 @@ if is_64bit_system host_64bit_deps = [ configuration_dep, + asio_dep, boost_dep, bitsery_dep, function2_dep, @@ -35,6 +36,7 @@ if with_bitbridge host_32bit_deps = [ configuration_dep, + asio_dep, boost_dep, ghc_filesystem_dep, bitsery_dep, diff --git a/src/wine-host/utils.cpp b/src/wine-host/utils.cpp index 2e8638df..f450b9b2 100644 --- a/src/wine-host/utils.cpp +++ b/src/wine-host/utils.cpp @@ -175,8 +175,8 @@ void MainContext::async_handle_watchdog_timer( // Try to keep a steady framerate, but add in delays to let other events // get handled if the GUI message handling somehow takes very long. watchdog_timer_.expires_at(std::chrono::steady_clock::now() + interval); - watchdog_timer_.async_wait([&](const boost::system::error_code& error) { - if (error.failed()) { + watchdog_timer_.async_wait([&](const std::error_code& error) { + if (error) { return; } diff --git a/src/wine-host/utils.h b/src/wine-host/utils.h index 966a0143..697a8a60 100644 --- a/src/wine-host/utils.h +++ b/src/wine-host/utils.h @@ -16,7 +16,7 @@ #pragma once -#include "boost-fix.h" +#include "asio-fix.h" #include #include @@ -24,8 +24,8 @@ #include #include -#include -#include +#include +#include #include #include "../common/utils.h" @@ -152,7 +152,7 @@ class Win32Timer { }; /** - * A wrapper around `boost::asio::io_context()` to serve as the application's + * A wrapper around `asio::io_context()` to serve as the application's * main IO context, run from the GUI thread. A single instance is shared for all * plugins in a plugin group so that several important events can be handled on * the main thread, which can be required because in the Win32 model all GUI @@ -254,7 +254,7 @@ class MainContext { std::packaged_task call_fn(std::forward(fn)); std::future result = call_fn.get_future(); - boost::asio::dispatch(context_, std::move(call_fn)); + asio::dispatch(context_, std::move(call_fn)); return result; } @@ -266,7 +266,7 @@ class MainContext { */ template void schedule_task(F&& fn) { - boost::asio::post(context_, std::forward(fn)); + asio::post(context_, std::forward(fn)); } /** @@ -294,8 +294,8 @@ class MainContext { std::max(events_timer_.expiry() + timer_interval_, std::chrono::steady_clock::now() + timer_interval_ / 4)); events_timer_.async_wait( - [&, handler, predicate](const boost::system::error_code& error) { - if (error.failed()) { + [&, handler, predicate](const std::error_code& error) { + if (error) { return; } @@ -311,7 +311,7 @@ class MainContext { * 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_; + asio::io_context context_; private: /** @@ -334,7 +334,7 @@ class MainContext { /** * The timer used to periodically handle X11 events and Win32 messages. */ - boost::asio::steady_timer events_timer_; + asio::steady_timer events_timer_; /** * The time between timer ticks in `async_handle_events`. This gets @@ -349,7 +349,7 @@ class MainContext { /** * The IO context used for the watchdog described below. */ - boost::asio::io_context watchdog_context_; + asio::io_context watchdog_context_; /** * The timer used to periodically check if the host processes are still @@ -357,7 +357,7 @@ class MainContext { * itself) when the host has exited and the sockets are somehow not closed * yet.. */ - boost::asio::steady_timer watchdog_timer_; + asio::steady_timer watchdog_timer_; /** * All of the bridges we're watching as part of our watchdog. We're storing diff --git a/src/wine-host/xdnd-proxy.h b/src/wine-host/xdnd-proxy.h index f0593495..131ad343 100644 --- a/src/wine-host/xdnd-proxy.h +++ b/src/wine-host/xdnd-proxy.h @@ -18,7 +18,7 @@ #include -#include "boost-fix.h" +#include "asio-fix.h" // Use the native version of xcb #pragma push_macro("_WIN32") diff --git a/subprojects/.gitignore b/subprojects/.gitignore index 4d080df7..121947fb 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore @@ -1,6 +1,7 @@ /*/* # The above pattern doesn't match submodules +/asio /bitsery /function2 /ghc_filesystem @@ -8,6 +9,7 @@ /vst3 # And we obviously don't want to ignore our overlays +!/packagefiles/asio !/packagefiles/bitsery !/packagefiles/function2 !/packagefiles/ghc_filesystem diff --git a/subprojects/asio.wrap b/subprojects/asio.wrap new file mode 100644 index 00000000..6c24ea38 --- /dev/null +++ b/subprojects/asio.wrap @@ -0,0 +1,9 @@ +[wrap-git] +url = https://github.com/chriskohlhoff/asio.git +# This is tag asio-1-22-1 +revision = bba12d10501418fd3789ce01c9f86a77d37df7ed +depth = 1 +patch_directory = asio + +[provide] +asio = asio_dep diff --git a/subprojects/packagefiles/asio/meson.build b/subprojects/packagefiles/asio/meson.build new file mode 100644 index 00000000..c99c65dd --- /dev/null +++ b/subprojects/packagefiles/asio/meson.build @@ -0,0 +1,3 @@ +project('asio', 'cpp', version : '1.22.1') + +asio_dep = declare_dependency(include_directories : include_directories('asio/include'))