mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
HighResolutionTimer: Add fallback implementation for Windows
This commit is contained in:
parent
57aa8f07a6
commit
b78ff3bf7e
5 changed files with 132 additions and 47 deletions
|
|
@ -226,6 +226,7 @@
|
|||
#include "native/juce_Registry_windows.cpp"
|
||||
#include "native/juce_SystemStats_windows.cpp"
|
||||
#include "native/juce_Threads_windows.cpp"
|
||||
#include "native/juce_PlatformTimer_generic.cpp"
|
||||
#include "native/juce_PlatformTimer_windows.cpp"
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -35,17 +35,21 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
class PlatformTimer final : private Thread
|
||||
class GenericPlatformTimer final : private Thread
|
||||
{
|
||||
public:
|
||||
explicit PlatformTimer (PlatformTimerListener& ptl)
|
||||
explicit GenericPlatformTimer (PlatformTimerListener& ptl)
|
||||
: Thread { "HighResolutionTimerThread" },
|
||||
listener { ptl }
|
||||
{
|
||||
startThread (Priority::highest);
|
||||
if (startThread (Priority::highest))
|
||||
return;
|
||||
|
||||
// This likely suggests there are too many threads running!
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
~PlatformTimer()
|
||||
~GenericPlatformTimer() override
|
||||
{
|
||||
stopThread (-1);
|
||||
}
|
||||
|
|
@ -154,8 +158,12 @@ private:
|
|||
mutable std::mutex runCopyMutex;
|
||||
std::shared_ptr<Timer> timer;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PlatformTimer)
|
||||
JUCE_DECLARE_NON_MOVEABLE (PlatformTimer)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericPlatformTimer)
|
||||
JUCE_DECLARE_NON_MOVEABLE (GenericPlatformTimer)
|
||||
};
|
||||
|
||||
#if ! JUCE_WINDOWS
|
||||
using PlatformTimer = GenericPlatformTimer;
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -51,27 +51,59 @@ public:
|
|||
};
|
||||
|
||||
timerId = timeSetEvent ((UINT) newIntervalMs, 1, callback, (DWORD_PTR) &listener, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
|
||||
intervalMs = timerId != 0 ? newIntervalMs : 0;
|
||||
const auto timerStarted = timerId != 0;
|
||||
|
||||
if (timerStarted)
|
||||
{
|
||||
intervalMs = newIntervalMs;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fallbackTimer == nullptr)
|
||||
{
|
||||
// This assertion indicates that the creation of a high-resolution timer
|
||||
// failed, and the timer is falling back to a less accurate implementation.
|
||||
// Timer callbacks will still fire but the timing precision of the callbacks
|
||||
// will be significantly compromised!
|
||||
// The most likely reason for this is that more than the system limit of 16
|
||||
// HighResolutionTimers are trying to run simultaneously in the same process.
|
||||
// You may be able to reduce the number of HighResolutionTimer instances by
|
||||
// only creating one instance that is shared (see SharedResourcePointer).
|
||||
//
|
||||
// However, if this is a plugin running inside a host, other plugins could
|
||||
// be creating timers in the same process. In most cases it's best to find
|
||||
// an alternative approach than relying on the precision of any timer!
|
||||
#if ! JUCE_UNIT_TESTS
|
||||
jassertfalse;
|
||||
#endif
|
||||
|
||||
fallbackTimer = std::make_unique<GenericPlatformTimer> (listener);
|
||||
}
|
||||
|
||||
fallbackTimer->startTimer (newIntervalMs);
|
||||
intervalMs = fallbackTimer->getIntervalMs();
|
||||
}
|
||||
|
||||
void cancelTimer()
|
||||
{
|
||||
jassert (timerId != 0);
|
||||
if (timerId != 0)
|
||||
timeKillEvent (timerId);
|
||||
else if (fallbackTimer != nullptr)
|
||||
fallbackTimer->cancelTimer();
|
||||
else
|
||||
jassertfalse;
|
||||
|
||||
timeKillEvent (timerId);
|
||||
timerId = 0;
|
||||
intervalMs = 0;
|
||||
}
|
||||
|
||||
int getIntervalMs() const
|
||||
{
|
||||
return intervalMs;
|
||||
}
|
||||
int getIntervalMs() const { return intervalMs; }
|
||||
|
||||
private:
|
||||
PlatformTimerListener& listener;
|
||||
UINT timerId { 0 };
|
||||
int intervalMs { 0 };
|
||||
std::unique_ptr<GenericPlatformTimer> fallbackTimer;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PlatformTimer)
|
||||
JUCE_DECLARE_NON_MOVEABLE (PlatformTimer)
|
||||
|
|
|
|||
|
|
@ -158,9 +158,30 @@ public:
|
|||
|
||||
void runTest() override
|
||||
{
|
||||
constexpr int maximumTimeoutMs {30'000};
|
||||
runBehaviourTestsWithBackgroundThreads<0>();
|
||||
runBehaviourTestsWithBackgroundThreads<16>();
|
||||
runStressTests();
|
||||
}
|
||||
|
||||
beginTest ("Start/stop a timer");
|
||||
template <size_t NumBackgroundThreads>
|
||||
void runBehaviourTestsWithBackgroundThreads()
|
||||
{
|
||||
constexpr int maximumTimeoutMs { 30'000 };
|
||||
|
||||
const auto beginBehaviourTest = [&] (const auto& testName)
|
||||
{
|
||||
beginTest (String (testName) + " (" + String (NumBackgroundThreads) + " background timers)");
|
||||
};
|
||||
|
||||
[[maybe_unused]] std::array<BackgroundTimer, NumBackgroundThreads> backgroundTimers;
|
||||
|
||||
beginBehaviourTest ("Background timer preconditions");
|
||||
{
|
||||
for (auto& t : backgroundTimers)
|
||||
expect (t.isTimerRunning());
|
||||
}
|
||||
|
||||
beginBehaviourTest ("Start/stop a timer");
|
||||
{
|
||||
WaitableEvent timerFiredOnce;
|
||||
WaitableEvent timerFiredTwice;
|
||||
|
|
@ -189,7 +210,7 @@ public:
|
|||
expect (timer.getTimerInterval() == 0);
|
||||
}
|
||||
|
||||
beginTest ("Stop a timer from the timer callback");
|
||||
beginBehaviourTest ("Stop a timer from the timer callback");
|
||||
{
|
||||
WaitableEvent stoppedTimer;
|
||||
|
||||
|
|
@ -206,7 +227,7 @@ public:
|
|||
expect (stoppedTimer.wait (maximumTimeoutMs));
|
||||
}
|
||||
|
||||
beginTest ("Restart a timer from the timer callback");
|
||||
beginBehaviourTest ("Restart a timer from the timer callback");
|
||||
{
|
||||
WaitableEvent restartTimer;
|
||||
WaitableEvent timerRestarted;
|
||||
|
|
@ -246,7 +267,7 @@ public:
|
|||
timer.stopTimer();
|
||||
}
|
||||
|
||||
beginTest ("Calling stopTimer on a timer, waits for any timer callbacks to finish");
|
||||
beginBehaviourTest ("Calling stopTimer on a timer, waits for any timer callbacks to finish");
|
||||
{
|
||||
WaitableEvent timerCallbackStarted;
|
||||
WaitableEvent stoppingTimer;
|
||||
|
|
@ -276,13 +297,13 @@ public:
|
|||
expect (timerCallbackFinished);
|
||||
}
|
||||
|
||||
beginTest ("Calling stopTimer on a timer, waits for any timer callbacks to finish, even if the timer callback calls stopTimer first");
|
||||
beginBehaviourTest ("Calling stopTimer on a timer, waits for any timer callbacks to finish, even if the timer callback calls stopTimer first");
|
||||
{
|
||||
WaitableEvent stoppedFromInsideTimerCallback;
|
||||
WaitableEvent stoppingFromOutsideTimerCallback;
|
||||
std::atomic<bool> timerCallbackFinished { false };
|
||||
|
||||
Timer timer {[&]()
|
||||
Timer timer {[&]
|
||||
{
|
||||
timer.stopTimer();
|
||||
stoppedFromInsideTimerCallback.signal();
|
||||
|
|
@ -300,7 +321,7 @@ public:
|
|||
expect (timerCallbackFinished);
|
||||
}
|
||||
|
||||
beginTest ("Adjusting a timer period from outside the timer callback doesn't cause data races");
|
||||
beginBehaviourTest ("Adjusting a timer period from outside the timer callback doesn't cause data races");
|
||||
{
|
||||
WaitableEvent timerCallbackStarted;
|
||||
WaitableEvent timerRestarted;
|
||||
|
|
@ -343,7 +364,7 @@ public:
|
|||
expect (lastCallbackCount == 2);
|
||||
}
|
||||
|
||||
beginTest ("A timer can be restarted externally, after being stopped internally");
|
||||
beginBehaviourTest ("A timer can be restarted externally, after being stopped internally");
|
||||
{
|
||||
WaitableEvent timerStopped;
|
||||
WaitableEvent timerFiredAfterRestart;
|
||||
|
|
@ -378,7 +399,7 @@ public:
|
|||
expect (timerFiredAfterRestart.wait (maximumTimeoutMs));
|
||||
}
|
||||
|
||||
beginTest ("Calls to `startTimer` and `getTimerInterval` succeed while a callback is blocked");
|
||||
beginBehaviourTest ("Calls to `startTimer` and `getTimerInterval` succeed while a callback is blocked");
|
||||
{
|
||||
WaitableEvent timerBlocked;
|
||||
WaitableEvent unblockTimer;
|
||||
|
|
@ -400,32 +421,33 @@ public:
|
|||
unblockTimer.signal();
|
||||
timer.stopTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void runStressTests()
|
||||
{
|
||||
beginTest ("Stress test");
|
||||
{
|
||||
constexpr auto maxNumTimers { 100 };
|
||||
std::vector<Timer> timers (100);
|
||||
|
||||
std::vector<std::unique_ptr<Timer>> timers;
|
||||
timers.reserve (maxNumTimers);
|
||||
|
||||
for (int i = 0; i < maxNumTimers; ++i)
|
||||
for (auto& timer : timers)
|
||||
{
|
||||
auto timer = std::make_unique<Timer> ([]{});
|
||||
timer->startTimer (1);
|
||||
|
||||
if (! timer->isTimerRunning())
|
||||
break;
|
||||
|
||||
timers.push_back (std::move (timer));
|
||||
timer.startTimer (1);
|
||||
expect (timer.isTimerRunning());
|
||||
}
|
||||
|
||||
expect (timers.size() >= 16);
|
||||
for (auto& timer : timers)
|
||||
{
|
||||
timer.stopTimer();
|
||||
expect (! timer.isTimerRunning());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Timer final : public HighResolutionTimer
|
||||
{
|
||||
public:
|
||||
Timer() = default;
|
||||
|
||||
explicit Timer (std::function<void()> fn)
|
||||
: callback (std::move (fn)) {}
|
||||
|
||||
|
|
@ -434,7 +456,17 @@ public:
|
|||
void hiResTimerCallback() override { callback(); }
|
||||
|
||||
private:
|
||||
std::function<void()> callback;
|
||||
std::function<void()> callback = []{};
|
||||
};
|
||||
|
||||
class BackgroundTimer
|
||||
{
|
||||
public:
|
||||
BackgroundTimer() { timer.startTimer (1); }
|
||||
bool isTimerRunning() const { return timer.isTimerRunning(); }
|
||||
|
||||
private:
|
||||
Timer timer { []{} };
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -83,13 +83,23 @@ public:
|
|||
//==============================================================================
|
||||
/** Starts the timer and sets the length of interval required.
|
||||
|
||||
If the timer has already started, this will reset the timer, so the
|
||||
time between calling this method and the next timer callback
|
||||
will not be less than the interval length passed in.
|
||||
If the timer has already started, this will reset the timer, so the time
|
||||
between calling this method and the next timer callback will not be less
|
||||
than the interval length passed in.
|
||||
|
||||
In exceptional circumstances the dedicated timer thread may not start,
|
||||
if this is a potential concern for your use case, you can call isTimerRunning()
|
||||
to confirm if the timer actually started.
|
||||
if this is a potential concern for your use case, you can call
|
||||
isTimerRunning() to confirm if the timer actually started.
|
||||
|
||||
On Windows the underlying API only allows 16 high-resolution timers to
|
||||
run simultaneously in the same process. A fallback timer will be used
|
||||
when this limit is exceeded but the precision may be significantly
|
||||
compromised. This is a particular concern for plugins, because other
|
||||
plugins in the same host process may already have timers running.
|
||||
To avoid issues, try to use the minimum number of HighResolutionTimers
|
||||
possible. For example, consider using a SharedResourcePointer so that
|
||||
all instances of the same plugin running in the same same process can
|
||||
share a single HighResolutionTimer instance.
|
||||
|
||||
@param intervalInMilliseconds the interval to use (a value of zero or less will stop the timer)
|
||||
*/
|
||||
|
|
@ -97,12 +107,14 @@ public:
|
|||
|
||||
/** Stops the timer.
|
||||
|
||||
This method may block while it waits for pending callbacks to complete. Once it
|
||||
returns, no more callbacks will be made. If it is called from the timer's own thread,
|
||||
it will cancel the timer after the current callback returns.
|
||||
This method may block while it waits for pending callbacks to complete.
|
||||
Once it returns, no more callbacks will be made. If it is called from
|
||||
the timer's own thread, it will cancel the timer after the current
|
||||
callback returns.
|
||||
|
||||
To prevent data races it's normally best practice to call this in the derived classes
|
||||
destructor, even if stopTimer() was called in the hiResTimerCallback().
|
||||
To prevent data races it's normally best practice to call this in the
|
||||
derived classes destructor, even if stopTimer() was called in the
|
||||
hiResTimerCallback().
|
||||
*/
|
||||
void stopTimer();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue