mirror of https://github.com/dcoredump/dexed.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
377 lines
11 KiB
377 lines
11 KiB
11 years ago
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE library.
|
||
|
Copyright (c) 2013 - Raw Material Software Ltd.
|
||
|
|
||
|
Permission is granted to use this software under the terms of either:
|
||
|
a) the GPL v2 (or any later version)
|
||
|
b) the Affero GPL v3
|
||
|
|
||
|
Details of these licenses can be found at: www.gnu.org/licenses
|
||
|
|
||
|
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||
|
|
||
|
------------------------------------------------------------------------------
|
||
|
|
||
|
To release a closed-source product which uses JUCE, commercial licenses are
|
||
|
available: visit www.juce.com for more information.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
class Timer::TimerThread : private Thread,
|
||
|
private DeletedAtShutdown,
|
||
|
private AsyncUpdater
|
||
|
{
|
||
|
public:
|
||
|
typedef CriticalSection LockType; // (mysteriously, using a SpinLock here causes problems on some XP machines..)
|
||
|
|
||
|
TimerThread()
|
||
|
: Thread ("Juce Timer"),
|
||
|
firstTimer (nullptr),
|
||
|
callbackNeeded (0)
|
||
|
{
|
||
|
triggerAsyncUpdate();
|
||
|
}
|
||
|
|
||
|
~TimerThread() noexcept
|
||
|
{
|
||
|
stopThread (4000);
|
||
|
|
||
|
jassert (instance == this || instance == nullptr);
|
||
|
if (instance == this)
|
||
|
instance = nullptr;
|
||
|
}
|
||
|
|
||
|
void run() override
|
||
|
{
|
||
|
uint32 lastTime = Time::getMillisecondCounter();
|
||
|
MessageManager::MessageBase::Ptr messageToSend (new CallTimersMessage());
|
||
|
|
||
|
while (! threadShouldExit())
|
||
|
{
|
||
|
const uint32 now = Time::getMillisecondCounter();
|
||
|
|
||
|
if (now == lastTime)
|
||
|
{
|
||
|
wait (1);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const int elapsed = (int) (now >= lastTime ? (now - lastTime)
|
||
|
: (std::numeric_limits<uint32>::max() - (lastTime - now)));
|
||
|
lastTime = now;
|
||
|
|
||
|
const int timeUntilFirstTimer = getTimeUntilFirstTimer (elapsed);
|
||
|
|
||
|
if (timeUntilFirstTimer <= 0)
|
||
|
{
|
||
|
/* If we managed to set the atomic boolean to true then send a message, this is needed
|
||
|
as a memory barrier so the message won't be sent before callbackNeeded is set to true,
|
||
|
but if it fails it means the message-thread changed the value from under us so at least
|
||
|
some processing is happenening and we can just loop around and try again
|
||
|
*/
|
||
|
if (callbackNeeded.compareAndSetBool (1, 0))
|
||
|
{
|
||
|
messageToSend->post();
|
||
|
|
||
|
/* Sometimes our message can get discarded by the OS (e.g. when running as an RTAS
|
||
|
when the app has a modal loop), so this is how long to wait before assuming the
|
||
|
message has been lost and trying again.
|
||
|
*/
|
||
|
const uint32 messageDeliveryTimeout = now + 300;
|
||
|
|
||
|
while (callbackNeeded.get() != 0)
|
||
|
{
|
||
|
wait (4);
|
||
|
|
||
|
if (threadShouldExit())
|
||
|
return;
|
||
|
|
||
|
if (Time::getMillisecondCounter() > messageDeliveryTimeout)
|
||
|
{
|
||
|
messageToSend->post();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// don't wait for too long because running this loop also helps keep the
|
||
|
// Time::getApproximateMillisecondTimer value stay up-to-date
|
||
|
wait (jlimit (1, 50, timeUntilFirstTimer));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void callTimers()
|
||
|
{
|
||
|
const LockType::ScopedLockType sl (lock);
|
||
|
|
||
|
while (firstTimer != nullptr && firstTimer->countdownMs <= 0)
|
||
|
{
|
||
|
Timer* const t = firstTimer;
|
||
|
t->countdownMs = t->periodMs;
|
||
|
|
||
|
removeTimer (t);
|
||
|
addTimer (t);
|
||
|
|
||
|
const LockType::ScopedUnlockType ul (lock);
|
||
|
|
||
|
JUCE_TRY
|
||
|
{
|
||
|
t->timerCallback();
|
||
|
}
|
||
|
JUCE_CATCH_EXCEPTION
|
||
|
}
|
||
|
|
||
|
/* This is needed as a memory barrier to make sure all processing of current timers is done
|
||
|
before the boolean is set. This set should never fail since if it was false in the first place,
|
||
|
we wouldn't get a message (so it can't be changed from false to true from under us), and if we
|
||
|
get a message then the value is true and the other thread can only set it to true again and
|
||
|
we will get another callback to set it to false.
|
||
|
*/
|
||
|
callbackNeeded.set (0);
|
||
|
}
|
||
|
|
||
|
void callTimersSynchronously()
|
||
|
{
|
||
|
if (! isThreadRunning())
|
||
|
{
|
||
|
// (This is relied on by some plugins in cases where the MM has
|
||
|
// had to restart and the async callback never started)
|
||
|
cancelPendingUpdate();
|
||
|
triggerAsyncUpdate();
|
||
|
}
|
||
|
|
||
|
callTimers();
|
||
|
}
|
||
|
|
||
|
static inline void add (Timer* const tim) noexcept
|
||
|
{
|
||
|
if (instance == nullptr)
|
||
|
instance = new TimerThread();
|
||
|
|
||
|
instance->addTimer (tim);
|
||
|
}
|
||
|
|
||
|
static inline void remove (Timer* const tim) noexcept
|
||
|
{
|
||
|
if (instance != nullptr)
|
||
|
instance->removeTimer (tim);
|
||
|
}
|
||
|
|
||
|
static inline void resetCounter (Timer* const tim, const int newCounter) noexcept
|
||
|
{
|
||
|
if (instance != nullptr)
|
||
|
{
|
||
|
tim->countdownMs = newCounter;
|
||
|
tim->periodMs = newCounter;
|
||
|
|
||
|
if ((tim->next != nullptr && tim->next->countdownMs < tim->countdownMs)
|
||
|
|| (tim->previous != nullptr && tim->previous->countdownMs > tim->countdownMs))
|
||
|
{
|
||
|
instance->removeTimer (tim);
|
||
|
instance->addTimer (tim);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static TimerThread* instance;
|
||
|
static LockType lock;
|
||
|
|
||
|
private:
|
||
|
Timer* volatile firstTimer;
|
||
|
Atomic <int> callbackNeeded;
|
||
|
|
||
|
struct CallTimersMessage : public MessageManager::MessageBase
|
||
|
{
|
||
|
CallTimersMessage() {}
|
||
|
|
||
|
void messageCallback() override
|
||
|
{
|
||
|
if (instance != nullptr)
|
||
|
instance->callTimers();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
void addTimer (Timer* const t) noexcept
|
||
|
{
|
||
|
#if JUCE_DEBUG
|
||
|
// trying to add a timer that's already here - shouldn't get to this point,
|
||
|
// so if you get this assertion, let me know!
|
||
|
jassert (! timerExists (t));
|
||
|
#endif
|
||
|
|
||
|
Timer* i = firstTimer;
|
||
|
|
||
|
if (i == nullptr || i->countdownMs > t->countdownMs)
|
||
|
{
|
||
|
t->next = firstTimer;
|
||
|
firstTimer = t;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
while (i->next != nullptr && i->next->countdownMs <= t->countdownMs)
|
||
|
i = i->next;
|
||
|
|
||
|
jassert (i != nullptr);
|
||
|
|
||
|
t->next = i->next;
|
||
|
t->previous = i;
|
||
|
i->next = t;
|
||
|
}
|
||
|
|
||
|
if (t->next != nullptr)
|
||
|
t->next->previous = t;
|
||
|
|
||
|
jassert ((t->next == nullptr || t->next->countdownMs >= t->countdownMs)
|
||
|
&& (t->previous == nullptr || t->previous->countdownMs <= t->countdownMs));
|
||
|
|
||
|
notify();
|
||
|
}
|
||
|
|
||
|
void removeTimer (Timer* const t) noexcept
|
||
|
{
|
||
|
#if JUCE_DEBUG
|
||
|
// trying to remove a timer that's not here - shouldn't get to this point,
|
||
|
// so if you get this assertion, let me know!
|
||
|
jassert (timerExists (t));
|
||
|
#endif
|
||
|
|
||
|
if (t->previous != nullptr)
|
||
|
{
|
||
|
jassert (firstTimer != t);
|
||
|
t->previous->next = t->next;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
jassert (firstTimer == t);
|
||
|
firstTimer = t->next;
|
||
|
}
|
||
|
|
||
|
if (t->next != nullptr)
|
||
|
t->next->previous = t->previous;
|
||
|
|
||
|
t->next = nullptr;
|
||
|
t->previous = nullptr;
|
||
|
}
|
||
|
|
||
|
int getTimeUntilFirstTimer (const int numMillisecsElapsed) const
|
||
|
{
|
||
|
const LockType::ScopedLockType sl (lock);
|
||
|
|
||
|
for (Timer* t = firstTimer; t != nullptr; t = t->next)
|
||
|
t->countdownMs -= numMillisecsElapsed;
|
||
|
|
||
|
return firstTimer != nullptr ? firstTimer->countdownMs : 1000;
|
||
|
}
|
||
|
|
||
|
void handleAsyncUpdate() override
|
||
|
{
|
||
|
startThread (7);
|
||
|
}
|
||
|
|
||
|
#if JUCE_DEBUG
|
||
|
bool timerExists (Timer* const t) const noexcept
|
||
|
{
|
||
|
for (Timer* tt = firstTimer; tt != nullptr; tt = tt->next)
|
||
|
if (tt == t)
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimerThread)
|
||
|
};
|
||
|
|
||
|
Timer::TimerThread* Timer::TimerThread::instance = nullptr;
|
||
|
Timer::TimerThread::LockType Timer::TimerThread::lock;
|
||
|
|
||
|
//==============================================================================
|
||
|
#if JUCE_DEBUG
|
||
|
static SortedSet <Timer*> activeTimers;
|
||
|
#endif
|
||
|
|
||
|
Timer::Timer() noexcept
|
||
|
: countdownMs (0),
|
||
|
periodMs (0),
|
||
|
previous (nullptr),
|
||
|
next (nullptr)
|
||
|
{
|
||
|
#if JUCE_DEBUG
|
||
|
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock);
|
||
|
activeTimers.add (this);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
Timer::Timer (const Timer&) noexcept
|
||
|
: countdownMs (0),
|
||
|
periodMs (0),
|
||
|
previous (nullptr),
|
||
|
next (nullptr)
|
||
|
{
|
||
|
#if JUCE_DEBUG
|
||
|
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock);
|
||
|
activeTimers.add (this);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
Timer::~Timer()
|
||
|
{
|
||
|
stopTimer();
|
||
|
|
||
|
#if JUCE_DEBUG
|
||
|
activeTimers.removeValue (this);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void Timer::startTimer (const int interval) noexcept
|
||
|
{
|
||
|
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock);
|
||
|
|
||
|
#if JUCE_DEBUG
|
||
|
// this isn't a valid object! Your timer might be a dangling pointer or something..
|
||
|
jassert (activeTimers.contains (this));
|
||
|
#endif
|
||
|
|
||
|
if (periodMs == 0)
|
||
|
{
|
||
|
countdownMs = interval;
|
||
|
periodMs = jmax (1, interval);
|
||
|
TimerThread::add (this);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TimerThread::resetCounter (this, interval);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Timer::stopTimer() noexcept
|
||
|
{
|
||
|
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock);
|
||
|
|
||
|
#if JUCE_DEBUG
|
||
|
// this isn't a valid object! Your timer might be a dangling pointer or something..
|
||
|
jassert (activeTimers.contains (this));
|
||
|
#endif
|
||
|
|
||
|
if (periodMs > 0)
|
||
|
{
|
||
|
TimerThread::remove (this);
|
||
|
periodMs = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void JUCE_CALLTYPE Timer::callPendingTimersSynchronously()
|
||
|
{
|
||
|
if (TimerThread::instance != nullptr)
|
||
|
TimerThread::instance->callTimersSynchronously();
|
||
|
}
|