enderalse/SKSE/Plugins/fs_skse_plugin_functions/common/ITimer.cpp
2021-10-06 02:45:46 +02:00

134 lines
2.7 KiB
C++

#include "ITimer.h"
// QueryPerformanceCounter is very accurate, but hardware bugs can cause it to return inaccurate results
// this code uses multimedia timers to check for glitches in QPC
double ITimer::s_secondsPerCount = 0;
TIMECAPS ITimer::s_timecaps = { 0 };
bool ITimer::s_setTime = false;
UInt64 ITimer::s_lastQPC = 0;
UInt64 ITimer::s_qpcWrapMargin = 0;
bool ITimer::s_hasLastQPC = false;
UInt32 ITimer::s_qpcWrapCount = 0;
UInt32 ITimer::s_qpcInaccurateCount = 0;
ITimer::ITimer()
:m_qpcBase(0), m_tickBase(0)
{
Init();
}
ITimer::~ITimer()
{
}
void ITimer::Init(void)
{
if(!s_secondsPerCount)
{
// init qpc
UInt64 countsPerSecond;
BOOL res = QueryPerformanceFrequency((LARGE_INTEGER *)&countsPerSecond);
ASSERT_STR(res, "ITimer: no high-resolution timer support");
s_secondsPerCount = 1.0 / countsPerSecond;
s_qpcWrapMargin = (UInt64)(-((SInt64)(countsPerSecond * 60))); // detect if we've wrapped around by a delta greater than this - also limits max time
_MESSAGE("s_qpcWrapMargin: %016I64X", s_qpcWrapMargin);
_MESSAGE("wrap time: %fs", ((double)0xFFFFFFFFFFFFFFFF) * s_secondsPerCount);
// init multimedia timer
timeGetDevCaps(&s_timecaps, sizeof(s_timecaps));
_MESSAGE("min timer period = %d", s_timecaps.wPeriodMin);
s_setTime = (timeBeginPeriod(s_timecaps.wPeriodMin) == TIMERR_NOERROR);
if(!s_setTime)
_WARNING("couldn't change timer period");
}
}
void ITimer::DeInit(void)
{
if(s_secondsPerCount)
{
if(s_setTime)
{
timeEndPeriod(s_timecaps.wPeriodMin);
s_setTime = false;
}
if(s_qpcWrapCount)
_MESSAGE("s_qpcWrapCount: %d", s_qpcWrapCount);
s_secondsPerCount = 0;
}
}
void ITimer::Start(void)
{
m_qpcBase = GetQPC();
m_tickBase = timeGetTime();
}
double ITimer::GetElapsedTime(void)
{
UInt64 qpcNow = GetQPC();
UInt32 tickNow = timeGetTime();
UInt64 qpcDelta = qpcNow - m_qpcBase;
UInt64 tickDelta = tickNow - m_tickBase;
double qpcSeconds = ((double)qpcDelta) * s_secondsPerCount;
double tickSeconds = ((double)tickDelta) * 0.001; // ticks are in milliseconds
double qpcTickDelta = qpcSeconds - tickSeconds;
if(qpcTickDelta < 0) qpcTickDelta = -qpcTickDelta;
// if they differ by more than one second, something's wrong, return
if(qpcTickDelta > 1)
{
s_qpcInaccurateCount++;
return tickSeconds;
}
else
{
return qpcSeconds;
}
}
UInt64 ITimer::GetQPC(void)
{
UInt64 now;
QueryPerformanceCounter((LARGE_INTEGER *)&now);
if(s_hasLastQPC)
{
UInt64 delta = now - s_lastQPC;
if(delta > s_qpcWrapMargin)
{
// we've gone back in time, return a kludged value
s_lastQPC = now;
now = s_lastQPC + 1;
s_qpcWrapCount++;
}
else
{
s_lastQPC = now;
}
}
else
{
s_hasLastQPC = true;
s_lastQPC = now;
}
return now;
}