diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index 59ea7348bb..a1ec1af91e 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -70,9 +70,8 @@ JUCE_BEGIN_NO_SANITIZE ("vptr") #endif #if JUCE_LINUX || JUCE_BSD + #include "juce_events/native/juce_linux_EventLoopInternal.h" #include - - std::vector>> getFdReadCallbacks(); #endif #if JUCE_MAC @@ -103,14 +102,20 @@ using namespace Steinberg; //============================================================================== #if JUCE_LINUX || JUCE_BSD -class EventHandler final : public Steinberg::Linux::IEventHandler +class EventHandler final : public Steinberg::Linux::IEventHandler, + private LinuxEventLoopInternal::Listener { public: - EventHandler() = default; + EventHandler() + { + LinuxEventLoopInternal::registerLinuxEventLoopListener (*this); + } - ~EventHandler() + ~EventHandler() override { - jassert (hostRunLoops.size() == 0); + jassert (hostRunLoops.empty()); + + LinuxEventLoopInternal::deregisterLinuxEventLoopListener (*this); if (! messageThread->isRunning()) messageThread->start(); @@ -126,11 +131,7 @@ public: void PLUGIN_API onFDIsSet (Steinberg::Linux::FileDescriptor fd) override { updateCurrentMessageThread(); - - auto it = fdCallbackMap.find (fd); - - if (it != fdCallbackMap.end()) - it->second (fd); + LinuxEventLoopInternal::invokeEventLoopCallbackForFd (fd); } //============================================================================== @@ -138,19 +139,7 @@ public: { if (auto* runLoop = getRunLoopFromFrame (plugFrame)) { - if (hostRunLoops.contains (runLoop)) - runLoop->unregisterEventHandler (this); - - hostRunLoops.add (runLoop); - - fdCallbackMap.clear(); - - for (auto& cb : getFdReadCallbacks()) - { - fdCallbackMap[cb.first] = cb.second; - runLoop->registerEventHandler (this, cb.first); - } - + refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.insert (runLoop); }); updateCurrentMessageThread(); } } @@ -158,67 +147,58 @@ public: void unregisterHandlerForFrame (IPlugFrame* plugFrame) { if (auto* runLoop = getRunLoopFromFrame (plugFrame)) - { - hostRunLoops.remove (runLoop); - - if (! hostRunLoops.contains (runLoop)) - runLoop->unregisterEventHandler (this); - } + refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.erase (runLoop); }); } private: - //============================================================================= - class HostRunLoopInterfaces + //============================================================================== + /* Connects all known FDs to a single host event loop instance. */ + class AttachedEventLoop { public: - HostRunLoopInterfaces() = default; + AttachedEventLoop() = default; - void add (Steinberg::Linux::IRunLoop* runLoop) + AttachedEventLoop (Steinberg::Linux::IRunLoop* loopIn, Steinberg::Linux::IEventHandler* handlerIn) + : loop (loopIn), handler (handlerIn) { - if (auto* refCountedRunLoop = find (runLoop)) - { - ++(refCountedRunLoop->refCount); - return; - } - - runLoops.push_back ({ runLoop, 1 }); + for (auto& fd : LinuxEventLoopInternal::getRegisteredFds()) + loop->registerEventHandler (handler, fd); } - void remove (Steinberg::Linux::IRunLoop* runLoop) + AttachedEventLoop (AttachedEventLoop&& other) noexcept { - if (auto* refCountedRunLoop = find (runLoop)) - if (--(refCountedRunLoop->refCount) == 0) - runLoops.erase (std::find (runLoops.begin(), runLoops.end(), runLoop)); + swap (other); } - size_t size() const noexcept { return runLoops.size(); } - bool contains (Steinberg::Linux::IRunLoop* runLoop) { return find (runLoop) != nullptr; } - - private: - struct RefCountedRunLoop + AttachedEventLoop& operator= (AttachedEventLoop&& other) noexcept { - Steinberg::Linux::IRunLoop* runLoop = nullptr; - int refCount = 0; + swap (other); + return *this; + } - bool operator== (const Steinberg::Linux::IRunLoop* other) const noexcept { return runLoop == other; } - }; + AttachedEventLoop (const AttachedEventLoop&) = delete; + AttachedEventLoop& operator= (const AttachedEventLoop&) = delete; - RefCountedRunLoop* find (const Steinberg::Linux::IRunLoop* runLoop) + ~AttachedEventLoop() { - auto iter = std::find (runLoops.begin(), runLoops.end(), runLoop); - - if (iter != runLoops.end()) - return &(*iter); + if (loop == nullptr) + return; - return nullptr; + loop->unregisterEventHandler (handler); } - std::vector runLoops; + private: + void swap (AttachedEventLoop& other) + { + std::swap (other.loop, loop); + std::swap (other.handler, handler); + } - JUCE_DECLARE_NON_MOVEABLE (HostRunLoopInterfaces) - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HostRunLoopInterfaces) + Steinberg::Linux::IRunLoop* loop = nullptr; + Steinberg::Linux::IEventHandler* handler = nullptr; }; + //============================================================================== static Steinberg::Linux::IRunLoop* getRunLoopFromFrame (IPlugFrame* plugFrame) { Steinberg::Linux::IRunLoop* runLoop = nullptr; @@ -241,12 +221,44 @@ private: } } + void fdCallbacksChanged() override + { + // The set of active FDs has changed, so deregister from the current event loop and then + // re-register the current set of FDs. + refreshAttachedEventLoop ([]{}); + } + + /* Deregisters from any attached event loop, updates the set of known event loops, and then + attaches all FDs to the first known event loop. + + The same event loop instance is shared between all plugin instances. Every time an event + loop is added or removed, this function should be called to register all FDs with a + suitable event loop. + + Note that there's no API to deregister a single FD for a given event loop. Instead, we must + deregister all FDs, and then register all known FDs again. + */ + template + void refreshAttachedEventLoop (Callback&& modifyKnownRunLoops) + { + // Deregister the old event loop. + // It's important to call the destructor from the old attached loop before calling the + // constructor of the new attached loop. + attachedEventLoop = AttachedEventLoop(); + + modifyKnownRunLoops(); + + // If we still know about an extant event loop, attach to it. + if (hostRunLoops.begin() != hostRunLoops.end()) + attachedEventLoop = AttachedEventLoop (*hostRunLoops.begin(), this); + } + SharedResourcePointer messageThread; std::atomic refCount { 1 }; - HostRunLoopInterfaces hostRunLoops; - std::unordered_map> fdCallbackMap; + std::multiset hostRunLoops; + AttachedEventLoop attachedEventLoop; //============================================================================== JUCE_DECLARE_NON_MOVEABLE (EventHandler) diff --git a/modules/juce_events/juce_events.cpp b/modules/juce_events/juce_events.cpp index f068c0a2f2..c6678d5d28 100644 --- a/modules/juce_events/juce_events.cpp +++ b/modules/juce_events/juce_events.cpp @@ -87,6 +87,7 @@ #endif #elif JUCE_LINUX || JUCE_BSD + #include "native/juce_linux_EventLoopInternal.h" #include "native/juce_linux_Messaging.cpp" #elif JUCE_ANDROID diff --git a/modules/juce_events/native/juce_linux_EventLoopInternal.h b/modules/juce_events/native/juce_linux_EventLoopInternal.h new file mode 100644 index 0000000000..81ab3a3150 --- /dev/null +++ b/modules/juce_events/native/juce_linux_EventLoopInternal.h @@ -0,0 +1,55 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +struct LinuxEventLoopInternal +{ + /** + @internal + + Receives notifications when a fd callback is added or removed. + + This is useful for VST3 plugins that host other VST3 plugins. In this scenario, the 'inner' + plugin may want to register additional file descriptors on top of those registered by the + 'outer' plugin. In order for this to work, the outer plugin must in turn pass this request + on to the host, to indicate that the outer plugin wants to receive FD callbacks for both the + inner and outer plugin. + */ + struct Listener + { + virtual ~Listener() = default; + virtual void fdCallbacksChanged() = 0; + }; + + /** @internal */ + static void registerLinuxEventLoopListener (Listener&); + /** @internal */ + static void deregisterLinuxEventLoopListener (Listener&); + /** @internal */ + static void invokeEventLoopCallbackForFd (int); + /** @internal */ + static std::vector getRegisteredFds(); +}; + +} // namespace juce diff --git a/modules/juce_events/native/juce_linux_Messaging.cpp b/modules/juce_events/native/juce_linux_Messaging.cpp index 5cc766558d..dcf5833466 100644 --- a/modules/juce_events/native/juce_linux_Messaging.cpp +++ b/modules/juce_events/native/juce_linux_Messaging.cpp @@ -108,125 +108,168 @@ private: JUCE_IMPLEMENT_SINGLETON (InternalMessageQueue) //============================================================================== +/* + Stores callbacks associated with file descriptors (FD). + + The callback for a particular FD should be called whenever that file has data to read. + + For standalone apps, the main thread will call poll to wait for new data on any FD, and then + call the associated callbacks for any FDs that changed. + + For plugins, the host (generally) provides some kind of run loop mechanism instead. + - In VST2 plugins, the host should call effEditIdle at regular intervals, and plugins can + dispatch all pending events inside this callback. The host doesn't know about any of the + plugin's FDs, so it's possible there will be a bit of latency between an FD becoming ready, + and its associated callback being called. + - In VST3 plugins, it's possible to register each FD individually with the host. In this case, + the facilities in LinuxEventLoopInternal can be used to observe added/removed FD callbacks, + and the host can be notified whenever the set of FDs changes. The host will call onFDIsSet + whenever a particular FD has data ready. This call should be forwarded through to + InternalRunLoop::dispatchEvent. +*/ struct InternalRunLoop { public: - InternalRunLoop() - { - fdReadCallbacks.reserve (16); - } + InternalRunLoop() = default; - void registerFdCallback (int fd, std::function&& cb, short eventMask) + void registerFdCallback (int fd, std::function&& cb, short eventMask) { - const ScopedLock sl (lock); - - if (shouldDeferModifyingReadCallbacks) { - deferredReadCallbackModifications.emplace_back ([this, fd, cb, eventMask]() mutable - { - registerFdCallback (fd, std::move (cb), eventMask); - }); - return; + const ScopedLock sl (lock); + + callbacks.emplace (fd, std::make_shared> (std::move (cb))); + + const auto iter = getPollfd (fd); + + if (iter == pfds.end() || iter->fd != fd) + pfds.insert (iter, { fd, eventMask, 0 }); + else + jassertfalse; + + jassert (pfdsAreSorted()); } - fdReadCallbacks.push_back ({ fd, std::move (cb) }); - pfds.push_back ({ fd, eventMask, 0 }); + listeners.call ([] (auto& l) { l.fdCallbacksChanged(); }); } void unregisterFdCallback (int fd) { - const ScopedLock sl (lock); - - if (shouldDeferModifyingReadCallbacks) { - deferredReadCallbackModifications.emplace_back ([this, fd] { unregisterFdCallback (fd); }); - return; - } + const ScopedLock sl (lock); - { - auto removePredicate = [=] (const std::pair>& cb) { return cb.first == fd; }; + callbacks.erase (fd); - fdReadCallbacks.erase (std::remove_if (std::begin (fdReadCallbacks), std::end (fdReadCallbacks), removePredicate), - std::end (fdReadCallbacks)); - } + const auto iter = getPollfd (fd); - { - auto removePredicate = [=] (const pollfd& pfd) { return pfd.fd == fd; }; + if (iter != pfds.end() && iter->fd == fd) + pfds.erase (iter); + else + jassertfalse; - pfds.erase (std::remove_if (std::begin (pfds), std::end (pfds), removePredicate), - std::end (pfds)); + jassert (pfdsAreSorted()); } + + listeners.call ([] (auto& l) { l.fdCallbacksChanged(); }); } bool dispatchPendingEvents() { - const ScopedLock sl (lock); + callbackStorage.clear(); + getFunctionsToCallThisTime (callbackStorage); - if (poll (&pfds.front(), static_cast (pfds.size()), 0) == 0) - return false; + // CriticalSection should be available during the callback + for (auto& fn : callbackStorage) + (*fn)(); - bool eventWasSent = false; + return ! callbackStorage.empty(); + } - for (auto& pfd : pfds) + void dispatchEvent (int fd) const + { + const auto fn = [&] { - if (pfd.revents == 0) - continue; + const ScopedLock sl (lock); + const auto iter = callbacks.find (fd); + return iter != callbacks.end() ? iter->second : nullptr; + }(); + + // CriticalSection should be available during the callback + if (auto* callback = fn.get()) + (*callback)(); + } + + bool sleepUntilNextEvent (int timeoutMs) + { + const ScopedLock sl (lock); + return poll (pfds.data(), static_cast (pfds.size()), timeoutMs) != 0; + } + + std::vector getRegisteredFds() + { + const ScopedLock sl (lock); + std::vector result; + result.reserve (callbacks.size()); + std::transform (callbacks.begin(), + callbacks.end(), + std::back_inserter (result), + [] (const auto& pair) { return pair.first; }); + return result; + } + + void addListener (LinuxEventLoopInternal::Listener& listener) { listeners.add (&listener); } + void removeListener (LinuxEventLoopInternal::Listener& listener) { listeners.remove (&listener); } - pfd.revents = 0; + //============================================================================== + JUCE_DECLARE_SINGLETON (InternalRunLoop, false) + +private: + using SharedCallback = std::shared_ptr>; + + /* Appends any functions that need to be called to the passed-in vector. - auto fd = pfd.fd; + We take a copy of each shared function so that the functions can be called without + locking or racing in the event that the function attempts to register/deregister a + new FD callback. + */ + void getFunctionsToCallThisTime (std::vector& functions) + { + const ScopedLock sl (lock); + + if (! sleepUntilNextEvent (0)) + return; - for (auto& fdAndCallback : fdReadCallbacks) + for (auto& pfd : pfds) + { + if (std::exchange (pfd.revents, 0) != 0) { - if (fdAndCallback.first == fd) - { - { - ScopedValueSetter insideFdReadCallback (shouldDeferModifyingReadCallbacks, true); - fdAndCallback.second (fd); - } - - if (! deferredReadCallbackModifications.empty()) - { - for (auto& deferredRegisterEvent : deferredReadCallbackModifications) - deferredRegisterEvent(); - - deferredReadCallbackModifications.clear(); - - // elements may have been removed from the fdReadCallbacks/pfds array so we really need - // to call poll again - return true; - } - - eventWasSent = true; - } + const auto iter = callbacks.find (pfd.fd); + + if (iter != callbacks.end()) + functions.emplace_back (iter->second); } } - - return eventWasSent; } - void sleepUntilNextEvent (int timeoutMs) + std::vector::iterator getPollfd (int fd) { - poll (&pfds.front(), static_cast (pfds.size()), timeoutMs); + return std::lower_bound (pfds.begin(), pfds.end(), fd, [] (auto descriptor, auto toFind) + { + return descriptor.fd < toFind; + }); } - std::vector>> getFdReadCallbacks() + bool pfdsAreSorted() const { - const ScopedLock sl (lock); - return fdReadCallbacks; + return std::is_sorted (pfds.begin(), pfds.end(), [] (auto a, auto b) { return a.fd < b.fd; }); } - //============================================================================== - JUCE_DECLARE_SINGLETON (InternalRunLoop, false) - -private: CriticalSection lock; - std::vector>> fdReadCallbacks; + std::map callbacks; + std::vector callbackStorage; std::vector pfds; - bool shouldDeferModifyingReadCallbacks = false; - std::vector> deferredReadCallbackModifications; + ListenerList listeners; }; JUCE_IMPLEMENT_SINGLETON (InternalRunLoop) @@ -313,7 +356,7 @@ bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) void LinuxEventLoop::registerFdCallback (int fd, std::function readCallback, short eventMask) { if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating()) - runLoop->registerFdCallback (fd, std::move (readCallback), eventMask); + runLoop->registerFdCallback (fd, [cb = std::move (readCallback), fd] { cb (fd); }, eventMask); } void LinuxEventLoop::unregisterFdCallback (int fd) @@ -322,16 +365,31 @@ void LinuxEventLoop::unregisterFdCallback (int fd) runLoop->unregisterFdCallback (fd); } -} // namespace juce +//============================================================================== +void LinuxEventLoopInternal::registerLinuxEventLoopListener (LinuxEventLoopInternal::Listener& listener) +{ + if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating()) + runLoop->addListener (listener); +} + +void LinuxEventLoopInternal::deregisterLinuxEventLoopListener (LinuxEventLoopInternal::Listener& listener) +{ + if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating()) + runLoop->removeListener (listener); +} -JUCE_API std::vector>> getFdReadCallbacks(); -JUCE_API std::vector>> getFdReadCallbacks() +void LinuxEventLoopInternal::invokeEventLoopCallbackForFd (int fd) { - using namespace juce; + if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating()) + runLoop->dispatchEvent (fd); +} +std::vector LinuxEventLoopInternal::getRegisteredFds() +{ if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating()) - return runLoop->getFdReadCallbacks(); + return runLoop->getRegisteredFds(); - jassertfalse; return {}; } + +} // namespace juce