open_toontown_panda3d/panda/src/express/trueClock.cxx

594 lines
21 KiB
C++

// Filename: trueClock.cxx
// Created by: drose (04Jul00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license. You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////
#include "trueClock.h"
#include "config_express.h"
#include "numeric_types.h"
#include <math.h> // for fabs()
TrueClock *TrueClock::_global_ptr = NULL;
#ifdef WIN32_VC
////////////////////////////////////////////////////////////////////
//
// The Win32 implementation.
//
////////////////////////////////////////////////////////////////////
#include <sys/timeb.h>
#include <windows.h>
static const double _0001 = 1.0 / 1000.0;
static const double _00000001 = 1.0 / 10000000.0;
// This is the interval of time, in seconds, over which to measure the
// high-precision clock rate vs. the time-of-day rate, when
// paranoid-clock is in effect. Reducing it makes the clock respond
// more quickly to changes in rate, but setting it too small may
// introduce erratic behavior, especially if the user has ntp
// configured.
static const double paranoid_clock_interval = 3.0;
// It will be considered a clock jump error if either the
// high-precision clock or the time-of-day clock change by this number
// of seconds without the other jumping by a similar amount.
static const double paranoid_clock_jump_error = 2.0;
// If the we detect a clock jump error but the corrected clock skew is
// currently more than this amount, we hack the clock scale to try to
// compensate.
static const double paranoid_clock_jump_error_max_delta = 1.0;
// If the measured time_scale appears to change by more than this
// factor, it will be reported to the log. Changes to time_scale less
// than this factor are assumed to be within the margin of error.
static const double paranoid_clock_report_scale_factor = 0.1;
// If the high-precision clock, after applying time_scale correction,
// is still more than this number of seconds above or below the
// time-of-day clock, it will be sped up or slowed down slightly until
// it is back in sync.
static const double paranoid_clock_chase_threshold = 0.5;
// This is the minimum factor by which the high-precision clock will
// be sped up or slowed down when it gets out of sync by
// paranoid-clock-chase-threshold.
static const double paranoid_clock_chase_factor = 0.1;
////////////////////////////////////////////////////////////////////
// Function: TrueClock::get_long_time, Win32 implementation
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
double TrueClock::
get_long_time() {
int tc = GetTickCount();
return (double)(tc - _init_tc) * _0001;
}
////////////////////////////////////////////////////////////////////
// Function: TrueClock::get_short_time, Win32 implementation
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
double TrueClock::
get_short_time() {
double time;
if (_has_high_res) {
// Use the high-resolution clock. This is of questionable value,
// since (a) on some OS's and hardware, the low 24 bits can
// occasionally roll over without setting the carry bit, causing
// the time to jump backwards, and (b) reportedly it can set the
// carry bit incorrectly sometimes, causing the time to jump
// forwards, and (c) even when it doesn't do that, it's not very
// accurate and seems to lose seconds of time per hour, and (d)
// someone could be running a program such as Speed Gear which
// munges this value anyway.
PN_int64 count;
QueryPerformanceCounter((LARGE_INTEGER *)&count);
time = (double)(count - _init_count) * _recip_frequency;
} else {
// No high-resolution clock; return the best information we have.
// This doesn't suffer from the rollover problems that
// QueryPerformanceCounter does, but it's not very precise--only
// precise to 50ms on Win98, and 10ms on XP-based systems--and
// Speed Gear still munges it.
int tc = GetTickCount();
time = (double)(tc - _init_tc) * _0001;
}
if (_paranoid_clock) {
// Check for rollforwards, rollbacks, and compensate for Speed
// Gear type programs by verifying against the time of day clock.
time = correct_time(time);
}
return time;
}
////////////////////////////////////////////////////////////////////
// Function: TrueClock::Constructor, Win32 implementation
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
typedef BOOL (WINAPI * PFNSETPROCESSAFFINITYMASK)(HANDLE, DWORD_PTR);
TrueClock::
TrueClock() {
_error_count = 0;
_has_high_res = false;
_time_scale = 1.0;
_time_offset = 0.0;
_tod_offset = 0.0;
_time_scale_changed = false;
_last_reported_time_scale = 1.0;
_report_time_scale_time = 0.0;
if (lock_to_one_cpu) {
HMODULE hker = GetModuleHandle("kernel32");
if (hker != 0) {
PFNSETPROCESSAFFINITYMASK sp = (PFNSETPROCESSAFFINITYMASK)
GetProcAddress(hker, "SetProcessAffinityMask");
if (sp != 0) {
sp(GetCurrentProcess(), 1);
}
}
}
if (get_use_high_res_clock()) {
PN_int64 int_frequency;
_has_high_res =
(QueryPerformanceFrequency((LARGE_INTEGER *)&int_frequency) != 0);
if (_has_high_res) {
if (int_frequency <= 0) {
express_cat.error()
<< "TrueClock::get_real_time() - frequency is negative!" << endl;
_has_high_res = false;
} else {
_frequency = (double)int_frequency;
_recip_frequency = 1.0 / _frequency;
QueryPerformanceCounter((LARGE_INTEGER *)&_init_count);
}
}
}
// Also store the initial tick count. We'll need this for
// get_long_time(), as well as for get_short_time() if we're not
// using the high resolution clock.
_init_tc = GetTickCount();
// And we will need the current time of day to cross-check either of
// the above clocks if paranoid-clock is enabled.
GetSystemTimeAsFileTime((FILETIME *)&_init_tod);
_paranoid_clock = get_paranoid_clock();
_chase_clock = CC_keep_even;
if (_paranoid_clock) {
// If we'll be cross-checking the clock, we'd better start out
// with at least one timestamp, so we'll know if the clock jumps
// just after startup.
_timestamps.push_back(Timestamp(0.0, 0.0));
}
if (!_has_high_res) {
express_cat.warning()
<< "No high resolution clock available." << endl;
} else if (_paranoid_clock) {
express_cat.info()
<< "Not trusting the high resolution clock." << endl;
}
}
////////////////////////////////////////////////////////////////////
// Function: TrueClock::correct_time, Win32 implementation
// Access: Protected
// Description: Ensures that the reported timestamp from the
// high-precision (or even the low-precision) clock is
// valid by verifying against the time-of-day clock.
//
// This attempts to detect sudden jumps in time that
// might be caused by a failure of the high-precision
// clock to roll over properly.
//
// It also corrects for long-term skew of the clock by
// measuring the timing discrepency against the wall
// clock and projecting that discrepency into the
// future. This also should defeat programs such as
// Speed Gear that work by munging the value returned by
// QueryPerformanceCounter() and GetTickCount(), but not
// the wall clock time.
//
// However, relying on wall clock time presents its own
// set of problems, since the time of day might be
// adjusted slightly forward or back from time to time
// in response to ntp messages, or it might even be
// suddenly reset at any time by the user. So we do the
// best we can.
////////////////////////////////////////////////////////////////////
double TrueClock::
correct_time(double time) {
// First, get the current time of day measurement.
PN_uint64 int_tod;
GetSystemTimeAsFileTime((FILETIME *)&int_tod);
double tod = (double)(int_tod - _init_tod) * _00000001;
nassertr(!_timestamps.empty(), time);
// Make sure we didn't experience a sudden jump from the last
// measurement.
double time_delta = (time - _timestamps.back()._time) * _time_scale;
double tod_delta = (tod - _timestamps.back()._tod);
if (time_delta < 0.0 ||
fabs(time_delta - tod_delta) > paranoid_clock_jump_error) {
// A step backward in the high-precision clock, or more than a
// small jump on only one of the clocks, is cause for alarm.
express_cat.debug()
<< "Clock error detected; elapsed time " << time_delta
<< "s on high-resolution counter, and " << tod_delta
<< "s on time-of-day clock.\n";
++_error_count;
// If both are negative, we call it 0. If one is negative, we
// trust the other one. If both are nonnegative, we trust the
// smaller of the two.
double time_adjust = 0.0;
double tod_adjust = 0.0;
if (time_delta < 0.0 && tod < 0.0) {
// Trust neither.
time_adjust = -time_delta;
tod_adjust = -tod_delta;
} else if (time_delta < 0.0 || (tod_delta >= 0.0 && tod_delta < time_delta)) {
// Trust tod.
time_adjust = (tod_delta - time_delta);
} else {
// Trust time.
tod_adjust = (time_delta - tod_delta);
}
_time_offset += time_adjust;
time_delta += time_adjust;
_tod_offset += tod_adjust;
tod_delta += tod_adjust;
// Apply the adjustments to the timestamp queue. We could just
// completely empty the timestamp queue, but that makes it hard to
// catch up if we are getting lots of these "momentary" errors in
// a row.
Timestamps::iterator ti;
for (ti = _timestamps.begin(); ti != _timestamps.end(); ++ti) {
(*ti)._time -= time_adjust / _time_scale;
(*ti)._tod -= tod_adjust;
}
// And now we can record this timestamp, which is now consistent
// with the previous timestamps in the queue.
_timestamps.push_back(Timestamp(time, tod));
// Detecting and filtering this kind of momentary error can help
// protect us from legitimate problems cause by OS or BIOS bugs
// (which might introduce errors into the high precision clock),
// or from sudden changes to the time-of-day by the user, but we
// have to be careful because if the user uses a Speed Gear-type
// program to speed up the clock by an extreme amount, it can look
// like a lot of such "momentary" errors in a row--and if we throw
// them all out, we won't compute _time_scale correctly. To avoid
// this, we hack _time_scale here if we seem to be getting out of
// sync.
double corrected_time = time * _time_scale + _time_offset;
double corrected_tod = tod + _tod_offset;
if (corrected_time - corrected_tod > paranoid_clock_jump_error_max_delta &&
_time_scale > 0.00001) {
express_cat.info()
<< "Force-adjusting time_scale to catch up to errors.\n";
set_time_scale(time, _time_scale * 0.5);
}
} else if (tod_delta < 0.0) {
// A small backwards jump on the time-of-day clock is not a
// concern, since this is technically allowed with ntp enabled.
// We simply ignore the event.
} else {
// Ok, we don't think there was a sudden jump, so carry on.
// The timestamp queue here records the measured timestamps over
// the past _priority_interval seconds. Its main purpose is to
// keep a running observation of _time_scale, so we can detect
// runtime changes of the clock's scale, for instance if the user
// is using a program like Speed Gear and pulls the slider during
// runtime.
// Consider the oldest timestamp in our queue.
Timestamp oldest = _timestamps.front();
double time_age = (time - oldest._time);
double tod_age = (tod - oldest._tod);
double keep_interval = paranoid_clock_interval;
if (tod_age > keep_interval / 2.0 && time_age > 0.0) {
// Adjust the _time_scale value to match the ratio between the
// elapsed time on the high-resolution clock, and the
// time-of-day clock.
double new_time_scale = tod_age / time_age;
// When we adjust _time_scale, we have to be careful to adjust
// _time_offset at the same time, so we don't introduce a
// sudden jump in time.
set_time_scale(time, new_time_scale);
// Check to see if the time scale has changed significantly
// since we last reported it.
double ratio = _time_scale / _last_reported_time_scale;
if (fabs(ratio - 1.0) > paranoid_clock_report_scale_factor) {
_time_scale_changed = true;
_last_reported_time_scale = _time_scale;
// Actually report it a little bit later, to give the time
// scale a chance to settle down.
_report_time_scale_time = tod + _tod_offset + keep_interval;
if (express_cat.is_debug()) {
express_cat.debug()
<< "Will report time scale, now " << 100.0 / _time_scale
<< "%, tod_age = " << tod_age << ", time_age = " << time_age
<< ", ratio = " << ratio << "\n";
}
}
}
// Clean out old entries in the timestamps queue.
if (tod_age > keep_interval) {
while (!_timestamps.empty() &&
tod - _timestamps.front()._tod > keep_interval) {
_timestamps.pop_front();
}
}
// Record this timestamp.
_timestamps.push_back(Timestamp(time, tod));
}
double corrected_time = time * _time_scale + _time_offset;
double corrected_tod = tod + _tod_offset;
if (_time_scale_changed && corrected_tod >= _report_time_scale_time) {
double percent = 100.0 / _time_scale;
// Round percent to the nearest 5% to reduce confusion in the logs.
percent = floor(percent / 20.0 + 0.5) * 20.0;
express_cat.info()
<< "Clock appears to be running at " << percent << "% real time.\n";
_last_reported_time_scale = _time_scale;
_time_scale_changed = false;
}
// By the time we get here, we have a corrected_time and a
// corrected_tod value, both of which should be advancing at about
// the same rate. However, there might be accumulated skew between
// them, since there is some lag in the above algorithm that
// corrects the _time_scale, and clock skew can accumulate while the
// algorithm is catching up.
// Therefore, we have one more line of defense: we check at this
// point for skew, and correct for it by slowing the clock down or
// speeding it up a bit as needed, until we even out the clocks
// again. Rather than adjusting the clock speed with _time_scale
// here, we simply slide _time_offset forward and back as
// needed--that way we don't interfere with the above algorithm,
// which is trying to compute _time_scale accurately.
switch (_chase_clock) {
case CC_slow_down:
if (corrected_time < corrected_tod) {
// We caught up.
_chase_clock = CC_keep_even;
if (express_cat.is_debug()) {
express_cat.debug()
<< "Clock back down to real time.\n";
// Let's report the clock error now, so an app can resync now
// that we're at a good time.
++_error_count;
}
} else {
// Slow down the clock by sliding the offset a bit backward.
double fixup = 1.0 - (1.0 / (corrected_time - corrected_tod));
double correction = time_delta * max(fixup, paranoid_clock_chase_factor);
_time_offset -= correction;
corrected_time -= correction;
}
break;
case CC_keep_even:
if ((corrected_tod - corrected_time) > paranoid_clock_chase_threshold) {
// Oops, we're dropping behind; need to speed up.
_chase_clock = CC_speed_up;
if (express_cat.is_debug()) {
express_cat.debug()
<< "Clock is behind by " << (corrected_tod - corrected_time)
<< "s; speeding up to correct.\n";
}
} else if ((corrected_time - corrected_tod) > paranoid_clock_chase_threshold) {
// Oops, we're going too fast; need to slow down.
_chase_clock = CC_slow_down;
if (express_cat.is_debug()) {
express_cat.debug()
<< "Clock is ahead by " << (corrected_time - corrected_tod)
<< "s; slowing down to correct.\n";
}
}
break;
case CC_speed_up:
if (corrected_time > corrected_tod) {
// We caught up.
_chase_clock = CC_keep_even;
if (express_cat.is_debug()) {
express_cat.debug()
<< "Clock back up to real time.\n";
// Let's report the clock error now, so an app can resync now
// that we're at a good time.
++_error_count;
}
} else {
// Speed up the clock by sliding the offset a bit forward.
double fixup = 1.0 - (1.0 / (corrected_tod - corrected_time));
double correction = time_delta * max(fixup, paranoid_clock_chase_factor);
_time_offset += correction;
corrected_time += correction;
}
break;
}
if (express_cat.is_spam()) {
express_cat.spam()
<< "time " << time << " tod " << corrected_tod
<< " corrected time " << corrected_time << "\n";
}
return corrected_time;
}
////////////////////////////////////////////////////////////////////
// Function: TrueClock::set_time_scale, Win32 implementation
// Access: Protected
// Description: Changes the _time_scale value, recomputing
// _time_offset at the same time so we don't introduce a
// sudden jump in time.
////////////////////////////////////////////////////////////////////
void TrueClock::
set_time_scale(double time, double new_time_scale) {
nassertv(new_time_scale > 0.0);
_time_offset = time * _time_scale + _time_offset - (time * new_time_scale);
_time_scale = new_time_scale;
}
#else // !WIN32_VC
////////////////////////////////////////////////////////////////////
//
// The Posix implementation.
//
////////////////////////////////////////////////////////////////////
#include <sys/time.h>
#include <stdio.h> // for perror
static long _init_sec;
////////////////////////////////////////////////////////////////////
// Function: TrueClock::get_long_time, Posix implementation
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
double TrueClock::
get_long_time() {
struct timeval tv;
int result;
#ifdef GETTIMEOFDAY_ONE_PARAM
result = gettimeofday(&tv);
#else
result = gettimeofday(&tv, (struct timezone *)NULL);
#endif
if (result < 0) {
// Error in gettimeofday().
return 0.0;
}
// We subtract out the time at which the clock was initialized,
// because we don't care about the number of seconds all the way
// back to 1970, and we want to leave the double with as much
// precision as it can get.
return (double)(tv.tv_sec - _init_sec) + (double)tv.tv_usec / 1000000.0;
}
////////////////////////////////////////////////////////////////////
// Function: TrueClock::get_short_time, Posix implementation
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
double TrueClock::
get_short_time() {
struct timeval tv;
int result;
#ifdef GETTIMEOFDAY_ONE_PARAM
result = gettimeofday(&tv);
#else
result = gettimeofday(&tv, (struct timezone *)NULL);
#endif
if (result < 0) {
// Error in gettimeofday().
return 0.0;
}
// We subtract out the time at which the clock was initialized,
// because we don't care about the number of seconds all the way
// back to 1970, and we want to leave the double with as much
// precision as it can get.
return (double)(tv.tv_sec - _init_sec) + (double)tv.tv_usec / 1000000.0;
}
////////////////////////////////////////////////////////////////////
// Function: TrueClock::Constructor, Posix implementation
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
TrueClock::
TrueClock() {
_error_count = 0;
struct timeval tv;
int result;
#ifdef GETTIMEOFDAY_ONE_PARAM
result = gettimeofday(&tv);
#else
result = gettimeofday(&tv, (struct timezone *)NULL);
#endif
if (result < 0) {
perror("gettimeofday");
_init_sec = 0;
} else {
_init_sec = tv.tv_sec;
}
}
#endif