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