123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- /*
- ==============================================================================
- This file is part of the JUCE library.
- Copyright (c) 2017 - ROLI Ltd.
- 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 Timer::TimerThread : private Thread,
- private DeletedAtShutdown,
- private AsyncUpdater
- {
- public:
- using LockType = CriticalSection; // (mysteriously, using a SpinLock here causes problems on some XP machines..)
- TimerThread() : Thread ("JUCE Timer")
- {
- timers.reserve (32);
- triggerAsyncUpdate();
- }
- ~TimerThread() override
- {
- signalThreadShouldExit();
- callbackArrived.signal();
- stopThread (4000);
- jassert (instance == this || instance == nullptr);
- if (instance == this)
- instance = nullptr;
- }
- void run() override
- {
- auto lastTime = Time::getMillisecondCounter();
- ReferenceCountedObjectPtr<CallTimersMessage> messageToSend (new CallTimersMessage());
- while (! threadShouldExit())
- {
- auto now = Time::getMillisecondCounter();
- auto elapsed = (int) (now >= lastTime ? (now - lastTime)
- : (std::numeric_limits<uint32>::max() - (lastTime - now)));
- lastTime = now;
- auto timeUntilFirstTimer = getTimeUntilFirstTimer (elapsed);
- if (timeUntilFirstTimer <= 0)
- {
- if (callbackArrived.wait (0))
- {
- // already a message in flight - do nothing..
- }
- else
- {
- messageToSend->post();
- if (! callbackArrived.wait (300))
- {
- // 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.
- messageToSend->post();
- }
- continue;
- }
- }
- // don't wait for too long because running this loop also helps keep the
- // Time::getApproximateMillisecondTimer value stay up-to-date
- wait (jlimit (1, 100, timeUntilFirstTimer));
- }
- }
- void callTimers()
- {
- auto timeout = Time::getMillisecondCounter() + 100;
- const LockType::ScopedLockType sl (lock);
- while (! timers.empty())
- {
- auto& first = timers.front();
- if (first.countdownMs > 0)
- break;
- auto* timer = first.timer;
- first.countdownMs = timer->timerPeriodMs;
- shuffleTimerBackInQueue (0);
- notify();
- const LockType::ScopedUnlockType ul (lock);
- JUCE_TRY
- {
- timer->timerCallback();
- }
- JUCE_CATCH_EXCEPTION
- // avoid getting stuck in a loop if a timer callback repeatedly takes too long
- if (Time::getMillisecondCounter() > timeout)
- break;
- }
- callbackArrived.signal();
- }
- 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* tim) noexcept
- {
- if (instance == nullptr)
- instance = new TimerThread();
- instance->addTimer (tim);
- }
- static inline void remove (Timer* tim) noexcept
- {
- if (instance != nullptr)
- instance->removeTimer (tim);
- }
- static inline void resetCounter (Timer* tim) noexcept
- {
- if (instance != nullptr)
- instance->resetTimerCounter (tim);
- }
- static TimerThread* instance;
- static LockType lock;
- private:
- struct TimerCountdown
- {
- Timer* timer;
- int countdownMs;
- };
- std::vector<TimerCountdown> timers;
- WaitableEvent callbackArrived;
- struct CallTimersMessage : public MessageManager::MessageBase
- {
- CallTimersMessage() {}
- void messageCallback() override
- {
- if (instance != nullptr)
- instance->callTimers();
- }
- };
- //==============================================================================
- void addTimer (Timer* t)
- {
- // 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 (std::find_if (timers.begin(), timers.end(),
- [t](TimerCountdown i) { return i.timer == t; }) == timers.end());
- auto pos = timers.size();
- timers.push_back ({ t, t->timerPeriodMs });
- t->positionInQueue = pos;
- shuffleTimerForwardInQueue (pos);
- notify();
- }
- void removeTimer (Timer* t)
- {
- auto pos = t->positionInQueue;
- auto lastIndex = timers.size() - 1;
- jassert (pos <= lastIndex);
- jassert (timers[pos].timer == t);
- for (auto i = pos; i < lastIndex; ++i)
- {
- timers[i] = timers[i + 1];
- timers[i].timer->positionInQueue = i;
- }
- timers.pop_back();
- }
- void resetTimerCounter (Timer* t) noexcept
- {
- auto pos = t->positionInQueue;
- jassert (pos < timers.size());
- jassert (timers[pos].timer == t);
- auto lastCountdown = timers[pos].countdownMs;
- auto newCountdown = t->timerPeriodMs;
- if (newCountdown != lastCountdown)
- {
- timers[pos].countdownMs = newCountdown;
- if (newCountdown > lastCountdown)
- shuffleTimerBackInQueue (pos);
- else
- shuffleTimerForwardInQueue (pos);
- notify();
- }
- }
- void shuffleTimerBackInQueue (size_t pos)
- {
- auto numTimers = timers.size();
- if (pos < numTimers - 1)
- {
- auto t = timers[pos];
- for (;;)
- {
- auto next = pos + 1;
- if (next == numTimers || timers[next].countdownMs >= t.countdownMs)
- break;
- timers[pos] = timers[next];
- timers[pos].timer->positionInQueue = pos;
- ++pos;
- }
- timers[pos] = t;
- t.timer->positionInQueue = pos;
- }
- }
- void shuffleTimerForwardInQueue (size_t pos)
- {
- if (pos > 0)
- {
- auto t = timers[pos];
- while (pos > 0)
- {
- auto& prev = timers[(size_t) pos - 1];
- if (prev.countdownMs <= t.countdownMs)
- break;
- timers[pos] = prev;
- timers[pos].timer->positionInQueue = pos;
- --pos;
- }
- timers[pos] = t;
- t.timer->positionInQueue = pos;
- }
- }
- int getTimeUntilFirstTimer (int numMillisecsElapsed)
- {
- const LockType::ScopedLockType sl (lock);
- if (timers.empty())
- return 1000;
- for (auto& t : timers)
- t.countdownMs -= numMillisecsElapsed;
- return timers.front().countdownMs;
- }
- void handleAsyncUpdate() override
- {
- startThread (7);
- }
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimerThread)
- };
- Timer::TimerThread* Timer::TimerThread::instance = nullptr;
- Timer::TimerThread::LockType Timer::TimerThread::lock;
- //==============================================================================
- Timer::Timer() noexcept {}
- Timer::Timer (const Timer&) noexcept {}
- Timer::~Timer()
- {
- stopTimer();
- }
- void Timer::startTimer (int interval) noexcept
- {
- // If you're calling this before (or after) the MessageManager is
- // running, then you're not going to get any timer callbacks!
- JUCE_ASSERT_MESSAGE_MANAGER_EXISTS
- const TimerThread::LockType::ScopedLockType sl (TimerThread::lock);
- bool wasStopped = (timerPeriodMs == 0);
- timerPeriodMs = jmax (1, interval);
- if (wasStopped)
- TimerThread::add (this);
- else
- TimerThread::resetCounter (this);
- }
- 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 (timerPeriodMs > 0)
- {
- TimerThread::remove (this);
- timerPeriodMs = 0;
- }
- }
- void JUCE_CALLTYPE Timer::callPendingTimersSynchronously()
- {
- if (TimerThread::instance != nullptr)
- TimerThread::instance->callTimersSynchronously();
- }
- struct LambdaInvoker : private Timer
- {
- LambdaInvoker (int milliseconds, std::function<void()> f) : function (f)
- {
- startTimer (milliseconds);
- }
- void timerCallback() override
- {
- auto f = function;
- delete this;
- f();
- }
- std::function<void()> function;
- JUCE_DECLARE_NON_COPYABLE (LambdaInvoker)
- };
- void JUCE_CALLTYPE Timer::callAfterDelay (int milliseconds, std::function<void()> f)
- {
- new LambdaInvoker (milliseconds, f);
- }
- } // namespace juce
|