Panda3D
 All Classes Functions Variables Enumerations
trueClock.cxx
1 // Filename: trueClock.cxx
2 // Created by: drose (04Jul00)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 
16 #include "trueClock.h"
17 #include "config_express.h"
18 #include "numeric_types.h"
19 
20 #include <math.h> // for fabs()
21 
22 TrueClock *TrueClock::_global_ptr = NULL;
23 
24 #if defined(WIN32_VC) || defined(WIN64_VC)
25 
26 ////////////////////////////////////////////////////////////////////
27 //
28 // The Win32 implementation.
29 //
30 ////////////////////////////////////////////////////////////////////
31 
32 #include <sys/timeb.h>
33 #ifndef WIN32_LEAN_AND_MEAN
34 #define WIN32_LEAN_AND_MEAN 1
35 #endif
36 #include <windows.h>
37 
38 static const double _0001 = 1.0 / 1000.0;
39 static const double _00000001 = 1.0 / 10000000.0;
40 
41 // This is the interval of time, in seconds, over which to measure the
42 // high-precision clock rate vs. the time-of-day rate, when
43 // paranoid-clock is in effect. Reducing it makes the clock respond
44 // more quickly to changes in rate, but setting it too small may
45 // introduce erratic behavior, especially if the user has ntp
46 // configured.
47 static const double paranoid_clock_interval = 3.0;
48 
49 // It will be considered a clock jump error if either the
50 // high-precision clock or the time-of-day clock change by this number
51 // of seconds without the other jumping by a similar amount.
52 static const double paranoid_clock_jump_error = 2.0;
53 
54 // If the we detect a clock jump error but the corrected clock skew is
55 // currently more than this amount, we hack the clock scale to try to
56 // compensate.
57 static const double paranoid_clock_jump_error_max_delta = 1.0;
58 
59 // If the measured time_scale appears to change by more than this
60 // factor, it will be reported to the log. Changes to time_scale less
61 // than this factor are assumed to be within the margin of error.
62 static const double paranoid_clock_report_scale_factor = 0.1;
63 
64 // If the high-precision clock, after applying time_scale correction,
65 // is still more than this number of seconds above or below the
66 // time-of-day clock, it will be sped up or slowed down slightly until
67 // it is back in sync.
68 static const double paranoid_clock_chase_threshold = 0.5;
69 
70 // This is the minimum factor by which the high-precision clock will
71 // be sped up or slowed down when it gets out of sync by
72 // paranoid-clock-chase-threshold.
73 static const double paranoid_clock_chase_factor = 0.1;
74 
75 ////////////////////////////////////////////////////////////////////
76 // Function: TrueClock::get_long_time, Win32 implementation
77 // Access: Published
78 // Description:
79 ////////////////////////////////////////////////////////////////////
80 double TrueClock::
81 get_long_time() {
82  int tc = GetTickCount();
83  return (double)(tc - _init_tc) * _0001;
84 }
85 
86 ////////////////////////////////////////////////////////////////////
87 // Function: TrueClock::get_short_raw_time, Win32 implementation
88 // Access: Published
89 // Description:
90 ////////////////////////////////////////////////////////////////////
91 double TrueClock::
92 get_short_raw_time() {
93  double time;
94 
95  if (_has_high_res) {
96  // Use the high-resolution clock. This is of questionable value,
97  // since (a) on some OS's and hardware, the low 24 bits can
98  // occasionally roll over without setting the carry bit, causing
99  // the time to jump backwards, and (b) reportedly it can set the
100  // carry bit incorrectly sometimes, causing the time to jump
101  // forwards, and (c) even when it doesn't do that, it's not very
102  // accurate and seems to lose seconds of time per hour, and (d)
103  // someone could be running a program such as Speed Gear which
104  // munges this value anyway.
105  PN_int64 count;
106  QueryPerformanceCounter((LARGE_INTEGER *)&count);
107 
108  time = (double)(count - _init_count) * _recip_frequency;
109 
110  } else {
111  // No high-resolution clock; return the best information we have.
112  // This doesn't suffer from the rollover problems that
113  // QueryPerformanceCounter does, but it's not very precise--only
114  // precise to 50ms on Win98, and 10ms on XP-based systems--and
115  // Speed Gear still munges it.
116  int tc = GetTickCount();
117  time = (double)(tc - _init_tc) * _0001;
118  }
119 
120  return time;
121 }
122 
123 ////////////////////////////////////////////////////////////////////
124 // Function: TrueClock::set_cpu_affinity, Win32 implementation
125 // Access: Published
126 // Description:
127 ////////////////////////////////////////////////////////////////////
128 typedef BOOL (WINAPI * PFNSETPROCESSAFFINITYMASK)(HANDLE, DWORD_PTR);
129 typedef BOOL (WINAPI * PFNGETPROCESSAFFINITYMASK)(HANDLE, PDWORD_PTR, PDWORD_PTR);
130 
131 bool TrueClock::
132 set_cpu_affinity(PN_uint32 mask) const {
133  HMODULE hker = GetModuleHandle("kernel32");
134  if (hker != 0) {
135  PFNGETPROCESSAFFINITYMASK gp = (PFNGETPROCESSAFFINITYMASK)
136  GetProcAddress(hker, "GetProcessAffinityMask");
137  PFNSETPROCESSAFFINITYMASK sp = (PFNSETPROCESSAFFINITYMASK)
138  GetProcAddress(hker, "SetProcessAffinityMask");
139  if (gp != 0 && sp != 0) {
140  DWORD proc_mask;
141  DWORD sys_mask;
142  if (gp(GetCurrentProcess(), (PDWORD_PTR)&proc_mask, (PDWORD_PTR)&sys_mask)) {
143  // make sure we don't reference CPUs that don't exist
144  proc_mask = mask & sys_mask;
145  if (proc_mask) {
146  return sp(GetCurrentProcess(), proc_mask) != 0;
147  }
148  }
149  }
150  }
151  return false;
152 }
153 
154 ////////////////////////////////////////////////////////////////////
155 // Function: TrueClock::Constructor, Win32 implementation
156 // Access: Protected
157 // Description:
158 ////////////////////////////////////////////////////////////////////
159 TrueClock::
160 TrueClock() {
161  _error_count = 0;
162  _has_high_res = false;
163 
164  _time_scale = 1.0;
165  _time_offset = 0.0;
166  _tod_offset = 0.0;
167  _time_scale_changed = false;
168  _last_reported_time_scale = 1.0;
169  _report_time_scale_time = 0.0;
170 
171  ConfigVariableBool lock_to_one_cpu
172  ("lock-to-one-cpu", false,
173  PRC_DESC("Set this to true if you want the entire process to use one "
174  "CPU, even on multi-core and multi-CPU workstations. This is "
175  "mainly a hack to solve a bug in which QueryPerformanceCounter "
176  "returns inconsistent results on multi-core machines. "));
177 
178  if (lock_to_one_cpu) {
179  set_cpu_affinity(0x01);
180  }
181 
182  if (get_use_high_res_clock()) {
183  PN_int64 int_frequency;
184  _has_high_res =
185  (QueryPerformanceFrequency((LARGE_INTEGER *)&int_frequency) != 0);
186  if (_has_high_res) {
187  if (int_frequency <= 0) {
188  clock_cat.error()
189  << "TrueClock::get_real_time() - frequency is negative!" << endl;
190  _has_high_res = false;
191 
192  } else {
193  _frequency = (double)int_frequency;
194  _recip_frequency = 1.0 / _frequency;
195 
196  QueryPerformanceCounter((LARGE_INTEGER *)&_init_count);
197  }
198  }
199  }
200 
201  // Also store the initial tick count. We'll need this for
202  // get_long_time(), as well as for get_short_time() if we're not
203  // using the high resolution clock.
204  _init_tc = GetTickCount();
205 
206  // And we will need the current time of day to cross-check either of
207  // the above clocks if paranoid-clock is enabled.
208  GetSystemTimeAsFileTime((FILETIME *)&_init_tod);
209 
210  _chase_clock = CC_keep_even;
211 
212  // In case we'll be cross-checking the clock, we'd better start out
213  // with at least one timestamp, so we'll know if the clock jumps
214  // just after startup.
215  _timestamps.push_back(Timestamp(0.0, 0.0));
216 
217  if (!_has_high_res) {
218  clock_cat.warning()
219  << "No high resolution clock available." << endl;
220  }
221 }
222 
223 ////////////////////////////////////////////////////////////////////
224 // Function: TrueClock::correct_time, Win32 implementation
225 // Access: Protected
226 // Description: Ensures that the reported timestamp from the
227 // high-precision (or even the low-precision) clock is
228 // valid by verifying against the time-of-day clock.
229 //
230 // This attempts to detect sudden jumps in time that
231 // might be caused by a failure of the high-precision
232 // clock to roll over properly.
233 //
234 // It also corrects for long-term skew of the clock by
235 // measuring the timing discrepency against the wall
236 // clock and projecting that discrepency into the
237 // future. This also should defeat programs such as
238 // Speed Gear that work by munging the value returned by
239 // QueryPerformanceCounter() and GetTickCount(), but not
240 // the wall clock time.
241 //
242 // However, relying on wall clock time presents its own
243 // set of problems, since the time of day might be
244 // adjusted slightly forward or back from time to time
245 // in response to ntp messages, or it might even be
246 // suddenly reset at any time by the user. So we do the
247 // best we can.
248 ////////////////////////////////////////////////////////////////////
249 double TrueClock::
250 correct_time(double time) {
251  // First, get the current time of day measurement.
252  PN_uint64 int_tod;
253  GetSystemTimeAsFileTime((FILETIME *)&int_tod);
254  double tod = (double)(int_tod - _init_tod) * _00000001;
255 
256  nassertr(!_timestamps.empty(), time);
257 
258  // Make sure we didn't experience a sudden jump from the last
259  // measurement.
260  double time_delta = (time - _timestamps.back()._time) * _time_scale;
261  double tod_delta = (tod - _timestamps.back()._tod);
262 
263  if (time_delta < -0.0001 ||
264  fabs(time_delta - tod_delta) > paranoid_clock_jump_error) {
265  // A step backward in the high-precision clock, or more than a
266  // small jump on only one of the clocks, is cause for alarm. We
267  // allow a trivial step backward in the high-precision clock,
268  // since this does appear to happen in a threaded environment.
269 
270  clock_cat.debug()
271  << "Clock error detected; elapsed time " << time_delta
272  << "s on high-resolution counter, and " << tod_delta
273  << "s on time-of-day clock.\n";
274  ++_error_count;
275 
276  // If both are negative, we call it 0. If one is negative, we
277  // trust the other one (up to paranoid_clock_jump_error). If both
278  // are nonnegative, we trust the smaller of the two.
279  double time_adjust = 0.0;
280  double tod_adjust = 0.0;
281 
282  if (time_delta < 0.0 && tod < 0.0) {
283  // Trust neither.
284  time_adjust = -time_delta;
285  tod_adjust = -tod_delta;
286 
287  } else if (time_delta < 0.0 || (tod_delta >= 0.0 && tod_delta < time_delta)) {
288  // Trust tod, up to a point.
289  double new_tod_delta = min(tod_delta, paranoid_clock_jump_error);
290  time_adjust = new_tod_delta - time_delta;
291  tod_adjust = new_tod_delta - tod_delta;
292 
293  } else {
294  // Trust time, up to a point.
295  double new_time_delta = min(time_delta, paranoid_clock_jump_error);
296  time_adjust = new_time_delta - time_delta;
297  tod_adjust = new_time_delta - tod_delta;
298  }
299 
300  _time_offset += time_adjust;
301  time_delta += time_adjust;
302  _tod_offset += tod_adjust;
303  tod_delta += tod_adjust;
304 
305  // Apply the adjustments to the timestamp queue. We could just
306  // completely empty the timestamp queue, but that makes it hard to
307  // catch up if we are getting lots of these "momentary" errors in
308  // a row.
309  Timestamps::iterator ti;
310  for (ti = _timestamps.begin(); ti != _timestamps.end(); ++ti) {
311  (*ti)._time -= time_adjust / _time_scale;
312  (*ti)._tod -= tod_adjust;
313  }
314 
315  // And now we can record this timestamp, which is now consistent
316  // with the previous timestamps in the queue.
317  _timestamps.push_back(Timestamp(time, tod));
318 
319  // Detecting and filtering this kind of momentary error can help
320  // protect us from legitimate problems cause by OS or BIOS bugs
321  // (which might introduce errors into the high precision clock),
322  // or from sudden changes to the time-of-day by the user, but we
323  // have to be careful because if the user uses a Speed Gear-type
324  // program to speed up the clock by an extreme amount, it can look
325  // like a lot of such "momentary" errors in a row--and if we throw
326  // them all out, we won't compute _time_scale correctly. To avoid
327  // this, we hack _time_scale here if we seem to be getting out of
328  // sync.
329  double corrected_time = time * _time_scale + _time_offset;
330  double corrected_tod = tod + _tod_offset;
331  if (corrected_time - corrected_tod > paranoid_clock_jump_error_max_delta &&
332  _time_scale > 0.00001) {
333  clock_cat.info()
334  << "Force-adjusting time_scale to catch up to errors.\n";
335  set_time_scale(time, _time_scale * 0.5);
336  }
337 
338  } else if (tod_delta < 0.0) {
339  // A small backwards jump on the time-of-day clock is not a
340  // concern, since this is technically allowed with ntp enabled.
341  // We simply ignore the event.
342 
343  } else {
344  // Ok, we don't think there was a sudden jump, so carry on.
345 
346  // The timestamp queue here records the measured timestamps over
347  // the past _priority_interval seconds. Its main purpose is to
348  // keep a running observation of _time_scale, so we can detect
349  // runtime changes of the clock's scale, for instance if the user
350  // is using a program like Speed Gear and pulls the slider during
351  // runtime.
352 
353  // Consider the oldest timestamp in our queue.
354  Timestamp oldest = _timestamps.front();
355  double time_age = (time - oldest._time);
356  double tod_age = (tod - oldest._tod);
357 
358  double keep_interval = paranoid_clock_interval;
359 
360  if (tod_age > keep_interval / 2.0 && time_age > 0.0) {
361  // Adjust the _time_scale value to match the ratio between the
362  // elapsed time on the high-resolution clock, and the
363  // time-of-day clock.
364  double new_time_scale = tod_age / time_age;
365 
366  // When we adjust _time_scale, we have to be careful to adjust
367  // _time_offset at the same time, so we don't introduce a
368  // sudden jump in time.
369  set_time_scale(time, new_time_scale);
370 
371  // Check to see if the time scale has changed significantly
372  // since we last reported it.
373  double ratio = _time_scale / _last_reported_time_scale;
374  if (fabs(ratio - 1.0) > paranoid_clock_report_scale_factor) {
375  _time_scale_changed = true;
376  _last_reported_time_scale = _time_scale;
377  // Actually report it a little bit later, to give the time
378  // scale a chance to settle down.
379  _report_time_scale_time = tod + _tod_offset + keep_interval;
380  if (clock_cat.is_debug()) {
381  clock_cat.debug()
382  << "Will report time scale, now " << 100.0 / _time_scale
383  << "%, tod_age = " << tod_age << ", time_age = " << time_age
384  << ", ratio = " << ratio << "\n";
385  }
386  }
387  }
388 
389  // Clean out old entries in the timestamps queue.
390  if (tod_age > keep_interval) {
391  while (!_timestamps.empty() &&
392  tod - _timestamps.front()._tod > keep_interval) {
393  _timestamps.pop_front();
394  }
395  }
396 
397  // Record this timestamp.
398  _timestamps.push_back(Timestamp(time, tod));
399  }
400 
401  double corrected_time = time * _time_scale + _time_offset;
402  double corrected_tod = tod + _tod_offset;
403 
404  if (_time_scale_changed && corrected_tod >= _report_time_scale_time) {
405  double percent = 100.0 / _time_scale;
406  // Round percent to the nearest 5% to reduce confusion in the logs.
407  percent = floor(percent / 20.0 + 0.5) * 20.0;
408  clock_cat.info()
409  << "Clock appears to be running at " << percent << "% real time.\n";
410  _last_reported_time_scale = _time_scale;
411  _time_scale_changed = false;
412  }
413 
414  // By the time we get here, we have a corrected_time and a
415  // corrected_tod value, both of which should be advancing at about
416  // the same rate. However, there might be accumulated skew between
417  // them, since there is some lag in the above algorithm that
418  // corrects the _time_scale, and clock skew can accumulate while the
419  // algorithm is catching up.
420 
421  // Therefore, we have one more line of defense: we check at this
422  // point for skew, and correct for it by slowing the clock down or
423  // speeding it up a bit as needed, until we even out the clocks
424  // again. Rather than adjusting the clock speed with _time_scale
425  // here, we simply slide _time_offset forward and back as
426  // needed--that way we don't interfere with the above algorithm,
427  // which is trying to compute _time_scale accurately.
428 
429  switch (_chase_clock) {
430  case CC_slow_down:
431  if (corrected_time < corrected_tod) {
432  // We caught up.
433  _chase_clock = CC_keep_even;
434  if (clock_cat.is_debug()) {
435  clock_cat.debug()
436  << "Clock back down to real time.\n";
437  // Let's report the clock error now, so an app can resync now
438  // that we're at a good time.
439  ++_error_count;
440  }
441 
442  } else {
443  // Slow down the clock by sliding the offset a bit backward.
444  double fixup = 1.0 - (1.0 / (corrected_time - corrected_tod));
445  double correction = time_delta * max(fixup, paranoid_clock_chase_factor);
446  _time_offset -= correction;
447  corrected_time -= correction;
448  }
449  break;
450 
451  case CC_keep_even:
452  if ((corrected_tod - corrected_time) > paranoid_clock_chase_threshold) {
453  // Oops, we're dropping behind; need to speed up.
454  _chase_clock = CC_speed_up;
455 
456  if (clock_cat.is_debug()) {
457  clock_cat.debug()
458  << "Clock is behind by " << (corrected_tod - corrected_time)
459  << "s; speeding up to correct.\n";
460  }
461  } else if ((corrected_time - corrected_tod) > paranoid_clock_chase_threshold) {
462  // Oops, we're going too fast; need to slow down.
463  _chase_clock = CC_slow_down;
464 
465  if (clock_cat.is_debug()) {
466  clock_cat.debug()
467  << "Clock is ahead by " << (corrected_time - corrected_tod)
468  << "s; slowing down to correct.\n";
469  }
470  }
471  break;
472 
473  case CC_speed_up:
474  if (corrected_time > corrected_tod) {
475  // We caught up.
476  _chase_clock = CC_keep_even;
477  if (clock_cat.is_debug()) {
478  clock_cat.debug()
479  << "Clock back up to real time.\n";
480  // Let's report the clock error now, so an app can resync now
481  // that we're at a good time.
482  ++_error_count;
483  }
484 
485  } else {
486  // Speed up the clock by sliding the offset a bit forward.
487  double fixup = 1.0 - (1.0 / (corrected_tod - corrected_time));
488  double correction = time_delta * max(fixup, paranoid_clock_chase_factor);
489  _time_offset += correction;
490  corrected_time += correction;
491  }
492  break;
493  }
494 
495  if (clock_cat.is_spam()) {
496  clock_cat.spam()
497  << "time " << time << " tod " << corrected_tod
498  << " corrected time " << corrected_time << "\n";
499  }
500 
501  return corrected_time;
502 }
503 
504 ////////////////////////////////////////////////////////////////////
505 // Function: TrueClock::set_time_scale, Win32 implementation
506 // Access: Protected
507 // Description: Changes the _time_scale value, recomputing
508 // _time_offset at the same time so we don't introduce a
509 // sudden jump in time.
510 ////////////////////////////////////////////////////////////////////
511 void TrueClock::
512 set_time_scale(double time, double new_time_scale) {
513  nassertv(new_time_scale > 0.0);
514  _time_offset = time * _time_scale + _time_offset - (time * new_time_scale);
515  _time_scale = new_time_scale;
516 }
517 
518 #else // !WIN32_VC
519 
520 ////////////////////////////////////////////////////////////////////
521 //
522 // The Posix implementation.
523 //
524 ////////////////////////////////////////////////////////////////////
525 
526 #include <sys/time.h>
527 #include <stdio.h> // for perror
528 
529 static long _init_sec;
530 
531 ////////////////////////////////////////////////////////////////////
532 // Function: TrueClock::get_long_time, Posix implementation
533 // Access: Published
534 // Description:
535 ////////////////////////////////////////////////////////////////////
536 double TrueClock::
537 get_long_time() {
538  struct timeval tv;
539 
540  int result;
541 
542 #ifdef GETTIMEOFDAY_ONE_PARAM
543  result = gettimeofday(&tv);
544 #else
545  result = gettimeofday(&tv, (struct timezone *)NULL);
546 #endif
547 
548  if (result < 0) {
549  // Error in gettimeofday().
550  return 0.0;
551  }
552 
553  // We subtract out the time at which the clock was initialized,
554  // because we don't care about the number of seconds all the way
555  // back to 1970, and we want to leave the double with as much
556  // precision as it can get.
557  return (double)(tv.tv_sec - _init_sec) + (double)tv.tv_usec / 1000000.0;
558 }
559 
560 ////////////////////////////////////////////////////////////////////
561 // Function: TrueClock::get_short_raw_time, Posix implementation
562 // Access: Published
563 // Description:
564 ////////////////////////////////////////////////////////////////////
565 double TrueClock::
566 get_short_raw_time() {
567  struct timeval tv;
568 
569  int result;
570 
571 #ifdef GETTIMEOFDAY_ONE_PARAM
572  result = gettimeofday(&tv);
573 #else
574  result = gettimeofday(&tv, (struct timezone *)NULL);
575 #endif
576 
577  if (result < 0) {
578  // Error in gettimeofday().
579  return 0.0;
580  }
581 
582  // We subtract out the time at which the clock was initialized,
583  // because we don't care about the number of seconds all the way
584  // back to 1970, and we want to leave the double with as much
585  // precision as it can get.
586  return (double)(tv.tv_sec - _init_sec) + (double)tv.tv_usec / 1000000.0;
587 }
588 
589 ////////////////////////////////////////////////////////////////////
590 // Function: TrueClock::set_cpu_affinity, Posix implementation
591 // Access: Published
592 // Description:
593 ////////////////////////////////////////////////////////////////////
594 bool TrueClock::
595 set_cpu_affinity(PN_uint32 mask) const {
596  return false;
597 }
598 
599 ////////////////////////////////////////////////////////////////////
600 // Function: TrueClock::Constructor, Posix implementation
601 // Access: Protected
602 // Description:
603 ////////////////////////////////////////////////////////////////////
604 TrueClock::
605 TrueClock() {
606  _error_count = 0;
607  struct timeval tv;
608 
609  int result;
610 #ifdef GETTIMEOFDAY_ONE_PARAM
611  result = gettimeofday(&tv);
612 #else
613  result = gettimeofday(&tv, (struct timezone *)NULL);
614 #endif
615 
616  if (result < 0) {
617  perror("gettimeofday");
618  _init_sec = 0;
619  } else {
620  _init_sec = tv.tv_sec;
621  }
622 }
623 
624 #endif
This is a convenience class to specialize ConfigVariable as a boolean type.
An interface to whatever real-time clock we might have available in the current environment.
Definition: trueClock.h:38