1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

VST3 Client: Properly notify host about added/removed event loop FDs

This commit is contained in:
reuk 2022-03-14 21:50:05 +00:00
parent 7815d2007b
commit c91514f57f
No known key found for this signature in database
GPG key ID: 9ADCD339CFC98A11
4 changed files with 287 additions and 161 deletions

View file

@ -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

View file

@ -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<int> getRegisteredFds();
};
} // namespace juce

View file

@ -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<void (int)>&& cb, short eventMask)
void registerFdCallback (int fd, std::function<void()>&& 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::function<void()>> (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);
callbacks.erase (fd);
const auto iter = getPollfd (fd);
if (iter != pfds.end() && iter->fd == fd)
pfds.erase (iter);
else
jassertfalse;
jassert (pfdsAreSorted());
}
{
auto removePredicate = [=] (const std::pair<int, std::function<void (int)>>& cb) { return cb.first == fd; };
fdReadCallbacks.erase (std::remove_if (std::begin (fdReadCallbacks), std::end (fdReadCallbacks), removePredicate),
std::end (fdReadCallbacks));
}
{
auto removePredicate = [=] (const pollfd& pfd) { return pfd.fd == fd; };
pfds.erase (std::remove_if (std::begin (pfds), std::end (pfds), removePredicate),
std::end (pfds));
}
listeners.call ([] (auto& l) { l.fdCallbacksChanged(); });
}
bool dispatchPendingEvents()
{
const ScopedLock sl (lock);
callbackStorage.clear();
getFunctionsToCallThisTime (callbackStorage);
if (poll (&pfds.front(), static_cast<nfds_t> (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;
}();
pfd.revents = 0;
auto fd = pfd.fd;
for (auto& fdAndCallback : fdReadCallbacks)
{
if (fdAndCallback.first == fd)
{
{
ScopedValueSetter<bool> 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;
}
}
}
return eventWasSent;
// CriticalSection should be available during the callback
if (auto* callback = fn.get())
(*callback)();
}
void sleepUntilNextEvent (int timeoutMs)
{
poll (&pfds.front(), static_cast<nfds_t> (pfds.size()), timeoutMs);
}
std::vector<std::pair<int, std::function<void (int)>>> getFdReadCallbacks()
bool sleepUntilNextEvent (int timeoutMs)
{
const ScopedLock sl (lock);
return fdReadCallbacks;
return poll (pfds.data(), static_cast<nfds_t> (pfds.size()), timeoutMs) != 0;
}
std::vector<int> getRegisteredFds()
{
const ScopedLock sl (lock);
std::vector<int> 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); }
//==============================================================================
JUCE_DECLARE_SINGLETON (InternalRunLoop, false)
private:
using SharedCallback = std::shared_ptr<std::function<void()>>;
/* Appends any functions that need to be called to the passed-in vector.
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<SharedCallback>& functions)
{
const ScopedLock sl (lock);
if (! sleepUntilNextEvent (0))
return;
for (auto& pfd : pfds)
{
if (std::exchange (pfd.revents, 0) != 0)
{
const auto iter = callbacks.find (pfd.fd);
if (iter != callbacks.end())
functions.emplace_back (iter->second);
}
}
}
std::vector<pollfd>::iterator getPollfd (int fd)
{
return std::lower_bound (pfds.begin(), pfds.end(), fd, [] (auto descriptor, auto toFind)
{
return descriptor.fd < toFind;
});
}
bool pfdsAreSorted() const
{
return std::is_sorted (pfds.begin(), pfds.end(), [] (auto a, auto b) { return a.fd < b.fd; });
}
CriticalSection lock;
std::vector<std::pair<int, std::function<void (int)>>> fdReadCallbacks;
std::map<int, SharedCallback> callbacks;
std::vector<SharedCallback> callbackStorage;
std::vector<pollfd> pfds;
bool shouldDeferModifyingReadCallbacks = false;
std::vector<std::function<void()>> deferredReadCallbackModifications;
ListenerList<LinuxEventLoopInternal::Listener> listeners;
};
JUCE_IMPLEMENT_SINGLETON (InternalRunLoop)
@ -313,7 +356,7 @@ bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages)
void LinuxEventLoop::registerFdCallback (int fd, std::function<void (int)> 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
JUCE_API std::vector<std::pair<int, std::function<void (int)>>> getFdReadCallbacks();
JUCE_API std::vector<std::pair<int, std::function<void (int)>>> getFdReadCallbacks()
//==============================================================================
void LinuxEventLoopInternal::registerLinuxEventLoopListener (LinuxEventLoopInternal::Listener& listener)
{
using namespace juce;
if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating())
return runLoop->getFdReadCallbacks();
runLoop->addListener (listener);
}
void LinuxEventLoopInternal::deregisterLinuxEventLoopListener (LinuxEventLoopInternal::Listener& listener)
{
if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating())
runLoop->removeListener (listener);
}
void LinuxEventLoopInternal::invokeEventLoopCallbackForFd (int fd)
{
if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating())
runLoop->dispatchEvent (fd);
}
std::vector<int> LinuxEventLoopInternal::getRegisteredFds()
{
if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating())
return runLoop->getRegisteredFds();
jassertfalse;
return {};
}
} // namespace juce