- clean working copy for leaving SVN
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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;
|
||||
|
||||
//==============================================================================
|
||||
Timer::Timer() noexcept
|
||||
: countdownMs (0),
|
||||
periodMs (0),
|
||||
previous (nullptr),
|
||||
next (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Timer::Timer (const Timer&) noexcept
|
||||
: countdownMs (0),
|
||||
periodMs (0),
|
||||
previous (nullptr),
|
||||
next (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Timer::~Timer()
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
void Timer::startTimer (const int interval) noexcept
|
||||
{
|
||||
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock);
|
||||
|
||||
if (periodMs == 0)
|
||||
{
|
||||
countdownMs = interval;
|
||||
periodMs = jmax (1, interval);
|
||||
TimerThread::add (this);
|
||||
}
|
||||
else
|
||||
{
|
||||
TimerThread::resetCounter (this, interval);
|
||||
}
|
||||
}
|
||||
|
||||
void Timer::startTimerHz (int timerFrequencyHz) noexcept
|
||||
{
|
||||
if (timerFrequencyHz > 0)
|
||||
startTimer (1000 / timerFrequencyHz);
|
||||
else
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
void Timer::stopTimer() noexcept
|
||||
{
|
||||
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock);
|
||||
|
||||
if (periodMs > 0)
|
||||
{
|
||||
TimerThread::remove (this);
|
||||
periodMs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void JUCE_CALLTYPE Timer::callPendingTimersSynchronously()
|
||||
{
|
||||
if (TimerThread::instance != nullptr)
|
||||
TimerThread::instance->callTimersSynchronously();
|
||||
}
|
||||
Reference in New Issue
Block a user