diff --git a/README.md b/README.md index c216d4b9..f37e039a 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,6 @@ This branch is still very far removed from being in a usable state. Below is an incomplete list of things that still have to be done before this can be used: - Interfaces left to implement: - - `IConnectionPoint::notify()`, and support for indirectly connecting objects - through connction proxies - `IEditController2` - All other mandatory interfaces - All other optional interfaces diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 30f76421..7c68b823 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -94,8 +94,17 @@ bool Vst3Logger::log_request(bool is_host_vst, const YaConnectionPoint::Connect& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id - << ": IConnectionPoint::connect(other = )"; + << ": IConnectionPoint::connect(other = "; + std::visit( + overload{[&](const native_size_t& other_instance_id) { + message << ""; + }, + [&](const Vst3ConnectionPointProxy::ConstructArgs&) { + message << ""; + }}, + request.other); + message << ")"; }); } @@ -103,8 +112,14 @@ bool Vst3Logger::log_request(bool is_host_vst, const YaConnectionPoint::Disconnect& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id - << ": IConnectionPoint::disconnect(other = )"; + << ": IConnectionPoint::disconnect(other = "; + if (request.other_instance_id) { + message << ""; + } else { + message << ""; + } + message << ")"; }); } diff --git a/src/common/serialization/vst3/connection-point-proxy.cpp b/src/common/serialization/vst3/connection-point-proxy.cpp index 8bf0cf61..5800c21b 100644 --- a/src/common/serialization/vst3/connection-point-proxy.cpp +++ b/src/common/serialization/vst3/connection-point-proxy.cpp @@ -16,13 +16,6 @@ #include "connection-point-proxy.h" -Vst3ConnectionPointProxy::ConstructArgs::ConstructArgs() {} - -Vst3ConnectionPointProxy::ConstructArgs::ConstructArgs( - Steinberg::IPtr object, - size_t owner_instance_id) - : owner_instance_id(owner_instance_id), connection_point_args(object) {} - Vst3ConnectionPointProxy::Vst3ConnectionPointProxy(const ConstructArgs&& args) : YaConnectionPoint(std::move(args.connection_point_args)), arguments(std::move(args)){FUNKNOWN_CTOR} diff --git a/src/common/serialization/vst3/connection-point-proxy.h b/src/common/serialization/vst3/connection-point-proxy.h index 48b255aa..f6585625 100644 --- a/src/common/serialization/vst3/connection-point-proxy.h +++ b/src/common/serialization/vst3/connection-point-proxy.h @@ -38,40 +38,10 @@ */ class Vst3ConnectionPointProxy : public YaConnectionPoint { public: - /** - * These are the arguments for constructing a - * `Vst3ConnectionPointProxyImpl`. - */ - struct ConstructArgs { - ConstructArgs(); - - /** - * Read from an existing object. We will try to mimic this object, so - * we'll support any interfaces this object also supports. - * - * This is not necessary in this case since the object has to support - * `IConnectionPoint`, but let's stay consistent with the overall style - * here. - */ - ConstructArgs(Steinberg::IPtr object, - size_t owner_instance_id); - - /** - * The unique instance identifier of the proxy object instance this - * connection proxy has been passed to and thus belongs to. This way we - * can refer to the correct 'actual' `IConnectionPoint` instance when - * the plugin calls `notify()` on this proxy object. - */ - native_size_t owner_instance_id; - - YaConnectionPoint::ConstructArgs connection_point_args; - - template - void serialize(S& s) { - s.value8b(owner_instance_id); - s.object(connection_point_args); - } - }; + // We had to define this in `YaConnectionPoint` to work around circular + // includes + using ConstructArgs = + YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs; /** * Instantiate this instance with arguments read from an actual diff --git a/src/common/serialization/vst3/plugin/connection-point.cpp b/src/common/serialization/vst3/plugin/connection-point.cpp index 5114521d..c0c6d443 100644 --- a/src/common/serialization/vst3/plugin/connection-point.cpp +++ b/src/common/serialization/vst3/plugin/connection-point.cpp @@ -23,5 +23,14 @@ YaConnectionPoint::ConstructArgs::ConstructArgs( : supported( Steinberg::FUnknownPtr(object)) {} +YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs:: + Vst3ConnectionPointProxyConstructArgs() {} + +YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs:: + Vst3ConnectionPointProxyConstructArgs( + Steinberg::IPtr object, + size_t owner_instance_id) + : owner_instance_id(owner_instance_id), connection_point_args(object) {} + YaConnectionPoint::YaConnectionPoint(const ConstructArgs&& args) : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin/connection-point.h b/src/common/serialization/vst3/plugin/connection-point.h index 7e21758f..213cdcad 100644 --- a/src/common/serialization/vst3/plugin/connection-point.h +++ b/src/common/serialization/vst3/plugin/connection-point.h @@ -16,7 +16,10 @@ #pragma once +#include + #include +#include #include #include "../../common.h" @@ -32,8 +35,6 @@ * monolithic proxy class we can easily directly connect different objects by * checking if they're a `Vst3PluginProxy` and then fetching that object's * instance ID (if the host doesn't place a proxy object here). - * - * TODO: Make sure we somehow handle proxies created by the host here. */ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { public: @@ -60,6 +61,45 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { } }; + protected: + /** + * These are the arguments for constructing a + * `Vst3ConnectionPointProxyImpl`. + * + * It's defined here to work around circular includes. + */ + struct Vst3ConnectionPointProxyConstructArgs { + Vst3ConnectionPointProxyConstructArgs(); + + /** + * Read from an existing object. We will try to mimic this object, so + * we'll support any interfaces this object also supports. + * + * This is not necessary in this case since the object has to support + * `IConnectionPoint`, but let's stay consistent with the overall style + * here. + */ + Vst3ConnectionPointProxyConstructArgs(Steinberg::IPtr object, + size_t owner_instance_id); + + /** + * The unique instance identifier of the proxy object instance this + * connection proxy has been passed to and thus belongs to. This way we + * can refer to the correct 'actual' `IConnectionPoint` instance when + * the plugin calls `notify()` on this proxy object. + */ + native_size_t owner_instance_id; + + ConstructArgs connection_point_args; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.object(connection_point_args); + } + }; + + public: /** * Instantiate this instance with arguments read from another interface * implementation. @@ -69,10 +109,10 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { inline bool supported() const { return arguments.supported; } /** - * Message to pass through a call to - * `IConnectionPoint::connect(other_instance_id)` to the Wine plugin host. - * At the moment this is only implemented for directly connecting objects - * created by the plugin without any proxies in between them. + * Message to pass through a call to `IConnectionPoint::connect(other)` to + * the Wine plugin host. If the host directly connects two objects, then + * we'll connect them directly as well. Otherwise all messages have to be + * routed through the host. */ struct Connect { using Response = UniversalTResult; @@ -82,24 +122,32 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { /** * The other object this object should be connected to. When connecting * two `Vst3PluginProxy` objects, we can directly connect the underlying - * objects on the Wine side. + * objects on the Wine side using their instance IDs. Otherwise we'll + * create a proxy object for the connection proxy provided by the host + * that the plugin can use to send messages to. */ - native_size_t other_instance_id; + std::variant + other; template void serialize(S& s) { s.value8b(instance_id); - s.value8b(other_instance_id); + s.ext(other, + bitsery::ext::StdVariant{ + [](S& s, native_size_t& other_instance_id) { + s.value8b(other_instance_id); + }, + [](S& s, Vst3ConnectionPointProxyConstructArgs& args) { + s.object(args); + }}); } }; virtual tresult PLUGIN_API connect(IConnectionPoint* other) override = 0; /** - * Message to pass through a call to - * `IConnectionPoint::disconnect(other_instance_id)` to the Wine plugin - * host. At the moment this is only implemented for directly connecting - * objects created by the plugin without any proxies in between them. + * Message to pass through a call to `IConnectionPoint::disconnect(other)` + * to the Wine plugin host. */ struct Disconnect { using Response = UniversalTResult; @@ -107,15 +155,19 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { native_size_t instance_id; /** - * The other object backed by a `Vst3PluginProxy` this object was - * connected to and should be disconnected from. When connecting. + * If we connected two objects directly, then this is the instance ID of + * that object. Otherwise we'll just destroy the smart pointer pointing + * to our `IConnectionPoint` proxy object. */ - native_size_t other_instance_id; + std::optional other_instance_id; template void serialize(S& s) { s.value8b(instance_id); - s.value8b(other_instance_id); + s.ext(other_instance_id, bitsery::ext::StdOptional{}, + [](S& s, native_size_t& instance_id) { + s.value8b(instance_id); + }); } }; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 0351b338..de929ccc 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -214,17 +214,19 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) { tresult PLUGIN_API Vst3PluginProxyImpl::connect(IConnectionPoint* other) { // When the host is trying to connect two plugin proxy objects, we can just // identify the other object by its instance IDs and then connect the - // objects in the Wine plugin host directly + // objects in the Wine plugin host directly. Otherwise we'll have to set up + // a proxy for the host's connection proxy so the messages can be routed + // through that. if (auto other_proxy = dynamic_cast(other)) { return bridge.send_message(YaConnectionPoint::Connect{ - .instance_id = instance_id(), - .other_instance_id = other_proxy->instance_id()}); + .instance_id = instance_id(), .other = other_proxy->instance_id()}); } else { - // TODO: Add support for `ConnectionProxy` and similar objects - bridge.logger.log( - "WARNING: The host passed a proxy proxy object to " - "'IConnectionPoint::connect()'. This is currently not supported."); - return Steinberg::kNotImplemented; + connection_point_proxy = other; + + return bridge.send_message(YaConnectionPoint::Connect{ + .instance_id = instance_id(), + .other = + Vst3ConnectionPointProxy::ConstructArgs(other, instance_id())}); } } @@ -235,12 +237,12 @@ tresult PLUGIN_API Vst3PluginProxyImpl::disconnect(IConnectionPoint* other) { .instance_id = instance_id(), .other_instance_id = other_proxy->instance_id()}); } else { - // TODO: Add support for `ConnectionProxy` and similar objects - bridge.logger.log( - "WARNING: The host passed a proxy proxy object to " - "'IConnectionPoint::disconnect()'. This is currently not " - "supported."); - return Steinberg::kNotImplemented; + const tresult result = bridge.send_message( + YaConnectionPoint::Disconnect{.instance_id = instance_id(), + .other_instance_id = std::nullopt}); + connection_point_proxy.reset(); + + return result; } } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index cc86beef..5f3c58f1 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -21,6 +21,7 @@ #include #include "vst3-impls/component-handler-proxy.h" +#include "vst3-impls/connection-point-proxy.h" #include "vst3-impls/host-context-proxy.h" #include "vst3-impls/plug-frame-proxy.h" @@ -168,24 +169,55 @@ void Vst3Bridge::run() { return Vst3PluginProxy::GetStateResponse{ .result = result, .updated_state = std::move(stream)}; }, - [&](const YaConnectionPoint::Connect& request) + [&](YaConnectionPoint::Connect& request) -> YaConnectionPoint::Connect::Response { - // We can directly connect the underlying objects - // TODO: Add support for connecting objects through a proxy - // object provided by the host - return object_instances[request.instance_id] - .connection_point->connect( - object_instances[request.other_instance_id] - .connection_point); + // If the host directly connected the underlying objects then we + // can directly connect them as well. Otherwise we'll have to go + // through a connection proxy (to proxy the host's connection + // proxy). + return std::visit( + overload{ + [&](const native_size_t& other_instance_id) -> tresult { + return object_instances[request.instance_id] + .connection_point->connect( + object_instances[other_instance_id] + .connection_point); + }, + [&](Vst3ConnectionPointProxy::ConstructArgs& args) + -> tresult { + object_instances[request.instance_id] + .connection_point_proxy = Steinberg::owned( + new Vst3ConnectionPointProxyImpl( + *this, std::move(args))); + + return object_instances[request.instance_id] + .connection_point->connect( + object_instances[request.instance_id] + .connection_point_proxy); + }}, + request.other); }, [&](const YaConnectionPoint::Disconnect& request) -> YaConnectionPoint::Disconnect::Response { - // TODO: Add support for connecting objects through a proxy - // object provided by the host - return object_instances[request.instance_id] - .connection_point->disconnect( - object_instances[request.other_instance_id] - .connection_point); + // If the objects were connected directly we can also disconnect + // them directly. Otherwise we'll disconnect them from our proxy + // object and then destroy that proxy object. + if (request.other_instance_id) { + return object_instances[request.instance_id] + .connection_point->disconnect( + object_instances[*request.other_instance_id] + .connection_point); + } else { + const tresult result = + object_instances[request.instance_id] + .connection_point->disconnect( + object_instances[*request.other_instance_id] + .connection_point_proxy); + object_instances[*request.other_instance_id] + .connection_point_proxy.reset(); + + return result; + } }, [&](YaConnectionPoint::Notify& request) -> YaConnectionPoint::Notify::Response { diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 0f5927dc..de216c19 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -26,9 +26,6 @@ #include "../editor.h" #include "common.h" -// Forward declarations -class Vst3PlugFrameProxyImpl; - /** * A holder for plugin object instance created from the factory. This stores all * relevant interface smart pointers to that object so we can handle control @@ -57,6 +54,18 @@ struct InstanceInterfaces { */ Steinberg::IPtr host_context_proxy; + /** + * If the host connects two objects indirectly using a connection proxy (as + * allowed by the VST3 specification), then we also can't connect the + * objects directly on the Wine side. In that case we'll have to create this + * proxy object, pass it to the plugin, and if the plugin then calls + * `IConnectionPoint::notify()` on it we'll pass that call through to the + * `IConnectionPoint` instance passed to us by the host (which will then in + * turn call `IConnectionPoint::notify()` on our plugin proxy object). + * Proxies for days. + */ + Steinberg::IPtr connection_point_proxy; + /** * After a call to `IEditController::setComponentHandler()`, we'll create a * proxy of that component handler just like we did for the plugin object.