From 2a199d5225c7fd76e79497a221d97dcaffe8f6b9 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 14 Jan 2021 15:25:44 +0000 Subject: [PATCH] AUv3: Fix hangs loading multiple AUv3 instances in Logic --- .../AU/juce_AUv3_Wrapper.mm | 136 ++++++++++-------- 1 file changed, 74 insertions(+), 62 deletions(-) diff --git a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm index e274c72927..42718f2039 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -73,6 +73,8 @@ #define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3) +#include + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness") using namespace juce; @@ -1750,7 +1752,7 @@ public: { JUCE_ASSERT_MESSAGE_THREAD - if (processorHolder != nullptr) + if (processorHolder.get() != nullptr) JuceAudioUnitv3::removeEditor (getAudioProcessor()); } @@ -1795,22 +1797,25 @@ public: void viewDidLayoutSubviews() { - if (processorHolder != nullptr && [myself view] != nullptr) + if (auto holder = processorHolder.get()) { - if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) + if ([myself view] != nullptr) { - if (processorHolder->viewConfiguration != nullptr) - editor->hostMIDIControllerIsAvailable (processorHolder->viewConfiguration->hostHasMIDIController); + if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) + { + if (holder->viewConfiguration != nullptr) + editor->hostMIDIControllerIsAvailable (holder->viewConfiguration->hostHasMIDIController); - editor->setBounds (convertToRectInt ([[myself view] bounds])); + editor->setBounds (convertToRectInt ([[myself view] bounds])); - if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) - { - #if JUCE_IOS - [peerView setNeedsDisplay]; - #else - [peerView setNeedsDisplay: YES]; - #endif + if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) + { + #if JUCE_IOS + [peerView setNeedsDisplay]; + #else + [peerView setNeedsDisplay: YES]; + #endif + } } } } @@ -1818,21 +1823,21 @@ public: void didReceiveMemoryWarning() { - if (processorHolder != nullptr) - if (auto* processor = processorHolder->get()) + if (auto ptr = processorHolder.get()) + if (auto* processor = ptr->get()) processor->memoryWarningReceived(); } void viewDidAppear (bool) { - if (processorHolder != nullptr) + if (processorHolder.get() != nullptr) if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) editor->setVisible (true); } void viewDidDisappear (bool) { - if (processorHolder != nullptr) + if (processorHolder.get() != nullptr) if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) editor->setVisible (false); } @@ -1846,65 +1851,72 @@ public: //============================================================================== AUAudioUnit* createAudioUnit (const AudioComponentDescription& descr, NSError** error) { - AUAudioUnit* retval = nil; + const auto holder = [&] + { + if (auto initialisedHolder = processorHolder.get()) + return initialisedHolder; - if (! MessageManager::getInstance()->isThisTheMessageThread()) + waitForExecutionOnMainThread ([this] { [myself view]; }); + return processorHolder.get(); + }(); + + if (holder == nullptr) + return nullptr; + + return (new JuceAudioUnitv3 (holder, descr, 0, error))->getAudioUnit(); + } + +private: + template + static void waitForExecutionOnMainThread (Callback&& callback) + { + if (MessageManager::getInstance()->isThisTheMessageThread()) { - WaitableEvent creationEvent; + callback(); + return; + } - // AUv3 headers say that we may block this thread and that the message thread is guaranteed - // to be unblocked - struct AUCreator : public CallbackMessage - { - JuceAUViewController& owner; - AudioComponentDescription pDescr; - NSError** pError; - AUAudioUnit*& outAU; - WaitableEvent& e; - - AUCreator (JuceAUViewController& parent, const AudioComponentDescription& paramDescr, NSError** paramError, - AUAudioUnit*& outputAU, WaitableEvent& event) - : owner (parent), pDescr (paramDescr), pError (paramError), outAU (outputAU), e (event) - {} - - void messageCallback() override - { - outAU = owner.createAudioUnitOnMessageThread (pDescr, pError); - e.signal(); - } - }; + std::promise promise; + + MessageManager::callAsync ([&] + { + callback(); + promise.set_value(); + }); - (new AUCreator (*this, descr, error, retval, creationEvent))->post(); - creationEvent.wait (-1); + promise.get_future().get(); + } + + // There's a chance that createAudioUnit will be called from a background + // thread while the processorHolder is being updated on the main thread. + class LockedProcessorHolder + { + public: + AudioProcessorHolder::Ptr get() const + { + const ScopedLock lock (mutex); + return holder; } - else + + LockedProcessorHolder& operator= (const AudioProcessorHolder::Ptr& other) { - retval = createAudioUnitOnMessageThread (descr, error); + const ScopedLock lock (mutex); + holder = other; + return *this; } - return [retval autorelease]; - } + private: + mutable CriticalSection mutex; + AudioProcessorHolder::Ptr holder; + }; -private: //============================================================================== AUViewController* myself; - AudioProcessorHolder::Ptr processorHolder = nullptr; + LockedProcessorHolder processorHolder; Rectangle preferredSize { 1, 1 }; //============================================================================== - AUAudioUnit* createAudioUnitOnMessageThread (const AudioComponentDescription& descr, NSError** error) - { - JUCE_ASSERT_MESSAGE_THREAD - - [myself view]; // this will call [view load] and ensure that the AudioProcessor has been instantiated - - if (processorHolder == nullptr) - return nullptr; - - return (new JuceAudioUnitv3 (processorHolder, descr, 0, error))->getAudioUnit(); - } - - AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } + AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder.get(); } }; //==============================================================================