396 lines
12 KiB
C++
396 lines
12 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
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
|
|
{
|
|
|
|
//==============================================================================
|
|
class InternalMessageQueue
|
|
{
|
|
public:
|
|
InternalMessageQueue()
|
|
{
|
|
auto err = ::socketpair (AF_LOCAL, SOCK_STREAM, 0, msgpipe);
|
|
jassertquiet (err == 0);
|
|
|
|
LinuxEventLoop::registerFdCallback (getReadHandle(),
|
|
[this] (int fd)
|
|
{
|
|
while (auto msg = popNextMessage (fd))
|
|
{
|
|
JUCE_TRY
|
|
{
|
|
msg->messageCallback();
|
|
}
|
|
JUCE_CATCH_EXCEPTION
|
|
}
|
|
});
|
|
}
|
|
|
|
~InternalMessageQueue()
|
|
{
|
|
LinuxEventLoop::unregisterFdCallback (getReadHandle());
|
|
|
|
close (getReadHandle());
|
|
close (getWriteHandle());
|
|
|
|
clearSingletonInstance();
|
|
}
|
|
|
|
//==============================================================================
|
|
void postMessage (MessageManager::MessageBase* const msg) noexcept
|
|
{
|
|
ScopedLock sl (lock);
|
|
queue.add (msg);
|
|
|
|
if (bytesInSocket < maxBytesInSocketQueue)
|
|
{
|
|
bytesInSocket++;
|
|
|
|
ScopedUnlock ul (lock);
|
|
unsigned char x = 0xff;
|
|
auto numBytes = write (getWriteHandle(), &x, 1);
|
|
ignoreUnused (numBytes);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
JUCE_DECLARE_SINGLETON (InternalMessageQueue, false)
|
|
|
|
private:
|
|
CriticalSection lock;
|
|
ReferenceCountedArray <MessageManager::MessageBase> queue;
|
|
|
|
int msgpipe[2];
|
|
int bytesInSocket = 0;
|
|
static constexpr int maxBytesInSocketQueue = 128;
|
|
|
|
int getWriteHandle() const noexcept { return msgpipe[0]; }
|
|
int getReadHandle() const noexcept { return msgpipe[1]; }
|
|
|
|
MessageManager::MessageBase::Ptr popNextMessage (int fd) noexcept
|
|
{
|
|
const ScopedLock sl (lock);
|
|
|
|
if (bytesInSocket > 0)
|
|
{
|
|
--bytesInSocket;
|
|
|
|
ScopedUnlock ul (lock);
|
|
unsigned char x;
|
|
auto numBytes = read (fd, &x, 1);
|
|
ignoreUnused (numBytes);
|
|
}
|
|
|
|
return queue.removeAndReturn (0);
|
|
}
|
|
};
|
|
|
|
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() = default;
|
|
|
|
void registerFdCallback (int fd, std::function<void()>&& cb, short eventMask)
|
|
{
|
|
{
|
|
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());
|
|
}
|
|
|
|
listeners.call ([] (auto& l) { l.fdCallbacksChanged(); });
|
|
}
|
|
|
|
void unregisterFdCallback (int fd)
|
|
{
|
|
{
|
|
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());
|
|
}
|
|
|
|
listeners.call ([] (auto& l) { l.fdCallbacksChanged(); });
|
|
}
|
|
|
|
bool dispatchPendingEvents()
|
|
{
|
|
callbackStorage.clear();
|
|
getFunctionsToCallThisTime (callbackStorage);
|
|
|
|
// CriticalSection should be available during the callback
|
|
for (auto& fn : callbackStorage)
|
|
(*fn)();
|
|
|
|
return ! callbackStorage.empty();
|
|
}
|
|
|
|
void dispatchEvent (int fd) const
|
|
{
|
|
const auto fn = [&]
|
|
{
|
|
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<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::map<int, SharedCallback> callbacks;
|
|
std::vector<SharedCallback> callbackStorage;
|
|
std::vector<pollfd> pfds;
|
|
|
|
ListenerList<LinuxEventLoopInternal::Listener> listeners;
|
|
};
|
|
|
|
JUCE_IMPLEMENT_SINGLETON (InternalRunLoop)
|
|
|
|
//==============================================================================
|
|
namespace LinuxErrorHandling
|
|
{
|
|
static bool keyboardBreakOccurred = false;
|
|
|
|
static void keyboardBreakSignalHandler (int sig)
|
|
{
|
|
if (sig == SIGINT)
|
|
keyboardBreakOccurred = true;
|
|
}
|
|
|
|
static void installKeyboardBreakHandler()
|
|
{
|
|
struct sigaction saction;
|
|
sigset_t maskSet;
|
|
sigemptyset (&maskSet);
|
|
saction.sa_handler = keyboardBreakSignalHandler;
|
|
saction.sa_mask = maskSet;
|
|
saction.sa_flags = 0;
|
|
sigaction (SIGINT, &saction, nullptr);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
void MessageManager::doPlatformSpecificInitialisation()
|
|
{
|
|
if (JUCEApplicationBase::isStandaloneApp())
|
|
LinuxErrorHandling::installKeyboardBreakHandler();
|
|
|
|
InternalRunLoop::getInstance();
|
|
InternalMessageQueue::getInstance();
|
|
}
|
|
|
|
void MessageManager::doPlatformSpecificShutdown()
|
|
{
|
|
InternalMessageQueue::deleteInstance();
|
|
InternalRunLoop::deleteInstance();
|
|
}
|
|
|
|
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message)
|
|
{
|
|
if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating())
|
|
{
|
|
queue->postMessage (message);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void MessageManager::broadcastMessage (const String&)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
// this function expects that it will NEVER be called simultaneously for two concurrent threads
|
|
bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (LinuxErrorHandling::keyboardBreakOccurred)
|
|
JUCEApplicationBase::quit();
|
|
|
|
if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating())
|
|
{
|
|
if (runLoop->dispatchPendingEvents())
|
|
break;
|
|
|
|
if (returnIfNoPendingMessages)
|
|
return false;
|
|
|
|
runLoop->sleepUntilNextEvent (2000);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//==============================================================================
|
|
void LinuxEventLoop::registerFdCallback (int fd, std::function<void (int)> readCallback, short eventMask)
|
|
{
|
|
if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating())
|
|
runLoop->registerFdCallback (fd, [cb = std::move (readCallback), fd] { cb (fd); }, eventMask);
|
|
}
|
|
|
|
void LinuxEventLoop::unregisterFdCallback (int fd)
|
|
{
|
|
if (auto* runLoop = InternalRunLoop::getInstanceWithoutCreating())
|
|
runLoop->unregisterFdCallback (fd);
|
|
}
|
|
|
|
//==============================================================================
|
|
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);
|
|
}
|
|
|
|
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();
|
|
|
|
return {};
|
|
}
|
|
|
|
} // namespace juce
|