Panda3D
clockObject.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 clockObject.cxx
10  * @author drose
11  * @date 2000-02-17
12  */
13 
14 #include "clockObject.h"
15 #include "config_putil.h"
16 #include "configVariableEnum.h"
17 #include "string_utils.h"
18 #include "thread.h"
19 
20 using std::istream;
21 using std::ostream;
22 using std::string;
23 
24 void (*ClockObject::_start_clock_wait)() = ClockObject::dummy_clock_wait;
25 void (*ClockObject::_start_clock_busy_wait)() = ClockObject::dummy_clock_wait;
26 void (*ClockObject::_stop_clock_wait)() = ClockObject::dummy_clock_wait;
27 
28 AtomicAdjust::Pointer ClockObject::_global_clock = nullptr;
29 TypeHandle ClockObject::_type_handle;
30 
31 /**
32  *
33  */
34 ClockObject::
35 ClockObject(Mode mode) : _ticks(get_class_type()), _mode(mode) {
36  _true_clock = TrueClock::get_global_ptr();
37 
38  _start_short_time = _true_clock->get_short_time();
39  _start_long_time = _true_clock->get_long_time();
40  _actual_frame_time = 0.0;
41 
43  ("max-dt", -1.0,
44  PRC_DESC("Sets a limit on the value returned by ClockObject::get_dt(). If "
45  "this value is less than zero, no limit is imposed; "
46  "otherwise, this is the maximum value that will ever "
47  "be returned by get_dt(), regardless of how much time "
48  "has actually elapsed between frames. See ClockObject::set_dt()."));
49  ConfigVariableDouble clock_frame_rate
50  ("clock-frame-rate", 1.0,
51  PRC_DESC("In non-real-time clock mode, sets the number of frames per "
52  "second that we should appear to be running. In forced "
53  "mode or limited mode, sets our target frame rate. In "
54  "normal mode, this has no effect. See ClockObject::set_frame_rate()."));
55  ConfigVariableDouble clock_degrade_factor
56  ("clock-degrade-factor", 1.0,
57  PRC_DESC("In degrade clock mode, returns the ratio by which the "
58  "performance is degraded. A value of 2.0 causes the "
59  "clock to be slowed down by a factor of two (reducing "
60  "performance to 1/2 what would be otherwise). See ClockObject::set_degrade_factor()."));
61  ConfigVariableDouble average_frame_rate_interval
62  ("average-frame-rate-interval", 1.0,
63  PRC_DESC("See ClockObject::set_average_frame_rate_interval()."));
64 
65  _max_dt = max_dt;
66  _user_frame_rate = clock_frame_rate;
67  _degrade_factor = clock_degrade_factor;
68  _average_frame_rate_interval = average_frame_rate_interval;
69 
70  _error_count = _true_clock->get_error_count();
71 }
72 
73 /**
74  *
75  */
76 ClockObject::
77 ClockObject(const ClockObject &copy) :
78  _true_clock(copy._true_clock),
79  _mode(copy._mode),
80  _start_short_time(copy._start_short_time),
81  _start_long_time(copy._start_long_time),
82  _actual_frame_time(copy._actual_frame_time),
83  _max_dt(copy._max_dt),
84  _user_frame_rate(copy._user_frame_rate),
85  _degrade_factor(copy._degrade_factor),
86  _error_count(copy._error_count),
87  _average_frame_rate_interval(copy._average_frame_rate_interval),
88  _ticks(copy._ticks),
89  _cycler(copy._cycler)
90 {
91 }
92 
93 /**
94  * Changes the mode of the clock. Normally, the clock is in mode M_normal.
95  * In this mode, each call to tick() will set the value returned by
96  * get_frame_time() to the current real time; thus, the clock simply reports
97  * time advancing.
98  *
99  * Other possible modes:
100  *
101  * M_non_real_time - the clock ignores real time completely; at each call to
102  * tick(), it pretends that exactly dt seconds have elapsed since the last
103  * call to tick(). You may set the value of dt with set_dt() or
104  * set_frame_rate().
105  *
106  * M_limited - the clock will run as fast as it can, as in M_normal, but will
107  * not run faster than the rate specified by set_frame_rate(). If the
108  * application would run faster than this rate, the clock will slow down the
109  * application.
110  *
111  * M_integer - the clock will run as fast as it can, but the rate will be
112  * constrained to be an integer multiple or divisor of the rate specified by
113  * set_frame_rate(). The clock will slow down the application a bit to
114  * guarantee this.
115  *
116  * M_integer_limited - a combination of M_limited and M_integer; the clock
117  * will not run faster than set_frame_rate(), and if it runs slower, it will
118  * run at a integer divisor of that rate.
119  *
120  * M_forced - the clock forces the application to run at the rate specified by
121  * set_frame_rate(). If the application would run faster than this rate, the
122  * clock will slow down the application; if the application would run slower
123  * than this rate, the clock slows down time so that the application believes
124  * it is running at the given rate.
125  *
126  * M_degrade - the clock runs at real time, but the application is slowed down
127  * by a set factor of its frame rate, specified by set_degrade_factor().
128  *
129  * M_slave - the clock does not advance, but relies on the user to call
130  * set_frame_time() and/or set_frame_count() each frame.
131  */
132 void ClockObject::
133 set_mode(ClockObject::Mode mode) {
134  Thread *current_thread = Thread::get_current_thread();
135  nassertv(current_thread->get_pipeline_stage() == 0);
136  CDWriter cdata(_cycler, current_thread);
137 
138  _mode = mode;
139 
140  // In case we have set the clock to one of the modes that uses
141  // _reported_frame_time_epoch, recompute the epoch.
142  switch (_mode) {
143  case M_non_real_time:
144  case M_forced:
145  cdata->_reported_frame_time_epoch = cdata->_reported_frame_time -
146  cdata->_frame_count / _user_frame_rate;
147  cdata->_dt = 1.0 / _user_frame_rate;
148 
149  default:
150  break;
151  }
152 }
153 
154 /**
155  * Resets the clock to the indicated time. This changes only the real time of
156  * the clock as reported by get_real_time(), but does not immediately change
157  * the time reported by get_frame_time()--that will change after the next call
158  * to tick(). Also see reset(), set_frame_time(), and set_frame_count().
159  */
160 void ClockObject::
161 set_real_time(double time) {
162 #ifdef NOTIFY_DEBUG
163  // This is only a debug message, since it happens during normal development,
164  // particularly at startup, or whenever you break into the task loop.
165  if (util_cat.is_debug() && this == _global_clock) {
166  util_cat.debug()
167  << "Adjusting global clock's real time by " << time - get_real_time()
168  << " seconds.\n";
169  }
170 #endif // NOTIFY_DEBUG
171  _start_short_time = _true_clock->get_short_time() - time;
172  _start_long_time = _true_clock->get_long_time() - time;
173 }
174 
175 /**
176  * Changes the time as reported for the current frame to the indicated time.
177  * Normally, the way to adjust the frame time is via tick(); this function is
178  * provided only for occasional special adjustments.
179  */
180 void ClockObject::
181 set_frame_time(double time, Thread *current_thread) {
182  nassertv(current_thread->get_pipeline_stage() == 0);
183 #ifdef NOTIFY_DEBUG
184  if (this == _global_clock && _mode != M_slave) {
185  util_cat.warning()
186  << "Adjusting global clock's frame time by " << time - get_frame_time()
187  << " seconds.\n";
188  }
189 #endif // NOTIFY_DEBUG
190  CDWriter cdata(_cycler, current_thread);
191  _actual_frame_time = time;
192  cdata->_reported_frame_time = time;
193 
194  // Recompute the epoch in case we are in a mode that relies on this.
195  cdata->_reported_frame_time_epoch = cdata->_reported_frame_time -
196  cdata->_frame_count / _user_frame_rate;
197 }
198 
199 /**
200  * Resets the number of frames counted to the indicated number. Also see
201  * reset(), set_real_time(), and set_frame_time().
202  */
203 void ClockObject::
204 set_frame_count(int frame_count, Thread *current_thread) {
205  nassertv(current_thread->get_pipeline_stage() == 0);
206 #ifdef NOTIFY_DEBUG
207  if (this == _global_clock && _mode != M_slave) {
208  util_cat.warning()
209  << "Adjusting global clock's frame count by "
210  << frame_count - get_frame_count() << " frames.\n";
211  }
212 #endif // NOTIFY_DEBUG
213  CDWriter cdata(_cycler, current_thread);
214  cdata->_frame_count = frame_count;
215 
216  // Recompute the epoch in case we are in a mode that relies on this.
217  cdata->_reported_frame_time_epoch = cdata->_reported_frame_time -
218  cdata->_frame_count / _user_frame_rate;
219 }
220 
221 /**
222  * In non-real-time mode, sets the number of seconds that should appear to
223  * elapse between frames. In forced mode or limited mode, sets our target dt.
224  * In normal mode, this has no effect.
225  *
226  * Also see set_frame_rate(), which is a different way to specify the same
227  * quantity.
228  */
229 void ClockObject::
230 set_dt(double dt) {
231  if (_mode == M_slave) {
232  // In M_slave mode, we can set any dt we like.
233  CDWriter cdata(_cycler, Thread::get_current_thread());
234  cdata->_dt = dt;
235  if (dt != 0.0) {
236  set_frame_rate(1.0 / dt);
237  }
238 
239  } else {
240  // In any other mode, we can only set non-zero dt.
241  nassertv(dt != 0.0);
242  set_frame_rate(1.0 / dt);
243  }
244 }
245 
246 /**
247  * In non-real-time mode, sets the number of frames per second that we should
248  * appear to be running. In forced mode or limited mode, sets our target
249  * frame rate. In normal mode, this has no effect.
250  *
251  * Also see set_dt(), which is a different way to specify the same quantity.
252  */
254 set_frame_rate(double frame_rate) {
255  nassertv(frame_rate != 0.0);
256 
257  Thread *current_thread = Thread::get_current_thread();
258  nassertv(current_thread->get_pipeline_stage() == 0);
259 
260  CDWriter cdata(_cycler, current_thread);
261  _user_frame_rate = frame_rate;
262 
263  switch (_mode) {
264  case M_non_real_time:
265  case M_forced:
266  cdata->_reported_frame_time_epoch = cdata->_reported_frame_time -
267  cdata->_frame_count / _user_frame_rate;
268  cdata->_dt = 1.0 / _user_frame_rate;
269 
270  default:
271  break;
272  }
273 }
274 
275 /**
276  * Returns the average frame rate in number of frames per second over the last
277  * get_average_frame_rate_interval() seconds. This measures the virtual frame
278  * rate if the clock is in M_non_real_time mode.
279  */
280 double ClockObject::
281 get_average_frame_rate(Thread *current_thread) const {
282  CDStageReader cdata(_cycler, 0, current_thread);
283  if (_ticks.size() <= 1) {
284  return 0.0;
285  } else {
286  return _ticks.size() / (cdata->_reported_frame_time - _ticks.front());
287  }
288 }
289 
290 /**
291  * Returns the maximum frame duration over the last
292  * get_average_frame_rate_interval() seconds.
293  */
294 double ClockObject::
295 get_max_frame_duration(Thread *current_thread) const {
296  CDStageReader cdata(_cycler, 0, current_thread);
297  double max_duration = 0.0;
298  double cur_duration = 0.0;
299  size_t i;
300  for (i = 0; i < _ticks.size() - 1; i++) {
301  cur_duration = _ticks[i + 1] - _ticks[i];
302  if (cur_duration > max_duration) {
303  max_duration = cur_duration;
304  }
305  }
306  return max_duration;
307 }
308 
309 /**
310  * Returns the standard deviation of the frame times of the frames rendered
311  * over the past get_average_frame_rate_interval() seconds. This number gives
312  * an estimate of the chugginess of the frame rate; if it is large, there is a
313  * large variation in the frame rate; if is small, all of the frames are
314  * consistent in length.
315  *
316  * A large value might also represent just a recent change in frame rate, for
317  * instance, because the camera has just rotated from looking at a simple
318  * scene to looking at a more complex scene.
319  */
321 calc_frame_rate_deviation(Thread *current_thread) const {
322  CDStageReader cdata(_cycler, 0, current_thread);
323  if (_ticks.size() <= 1) {
324  return 0.0;
325  } else {
326  double mean = (_ticks.back() - _ticks.front()) / (_ticks.size() - 1);
327  size_t i;
328  double sum_squares = 0.0;
329  for (i = 0; i < _ticks.size() - 1; ++i) {
330  double delta = _ticks[i + 1] - _ticks[i];
331  double diff = (delta - mean);
332  sum_squares += (diff * diff);
333  }
334  double deviation_2 = sum_squares / (_ticks.size() - 1);
335  return sqrt(deviation_2);
336  }
337 }
338 
339 /**
340  * Instructs the clock that a new frame has just begun. In normal, real-time
341  * mode, get_frame_time() will henceforth report the time as of this instant
342  * as the current start-of-frame time. In non-real-time mode,
343  * get_frame_time() will be incremented by the value of dt.
344  */
346 tick(Thread *current_thread) {
347  nassertv(current_thread->get_pipeline_stage() == 0);
348  CDWriter cdata(_cycler, current_thread);
349  double old_reported_time = cdata->_reported_frame_time;
350 
351  if (_mode != M_slave) {
352  double old_time = _actual_frame_time;
353  _actual_frame_time = get_real_time();
354 
355  // In case someone munged the clock last frame and sent us backward in
356  // time, clamp the previous time to the current time to make sure we don't
357  // report anything strange (or wait interminably).
358  old_time = std::min(old_time, _actual_frame_time);
359 
360  ++cdata->_frame_count;
361 
362  switch (_mode) {
363  case M_normal:
364  // Time runs as it will; we simply report time elapsing.
365  cdata->_dt = _actual_frame_time - old_time;
366  cdata->_reported_frame_time = _actual_frame_time;
367  break;
368 
369  case M_non_real_time:
370  // Ignore real time. We always report the same interval having elapsed
371  // each frame.
372  cdata->_reported_frame_time = cdata->_reported_frame_time_epoch +
373  cdata->_frame_count / _user_frame_rate;
374  break;
375 
376  case M_limited:
377  // If we are running faster than the desired interval, slow down.
378  {
379  double wait_until_time = old_time + 1.0 / _user_frame_rate;
380  wait_until(wait_until_time);
381  cdata->_dt = _actual_frame_time - old_time;
382  cdata->_reported_frame_time = std::max(_actual_frame_time, wait_until_time);
383  }
384  break;
385 
386  case M_integer:
387  {
388  double dt = _actual_frame_time - old_time;
389  double target_dt = 1.0 / _user_frame_rate;
390  if (dt < target_dt) {
391  // We're running faster than the desired interval, so slow down to
392  // the next integer multiple of the frame rate.
393  target_dt = target_dt / floor(target_dt / dt);
394  } else {
395  // We're running slower than the desired interval, so slow down to
396  // the next integer divisor of the frame rate.
397  target_dt = target_dt * ceil(dt / target_dt);
398  }
399  double wait_until_time = old_time + target_dt;
400  wait_until(wait_until_time);
401  cdata->_dt = target_dt;
402  cdata->_reported_frame_time = wait_until_time;
403  }
404  break;
405 
406  case M_integer_limited:
407  {
408  double dt = _actual_frame_time - old_time;
409  double target_dt = 1.0 / _user_frame_rate;
410  if (dt < target_dt) {
411  // We're running faster than the desired interval, so slow down to
412  // the target frame rate.
413 
414  } else {
415  // We're running slower than the desired interval, so slow down to
416  // the next integer divisor of the frame rate.
417  target_dt = target_dt * ceil(dt / target_dt);
418  }
419  double wait_until_time = old_time + target_dt;
420  wait_until(wait_until_time);
421  cdata->_dt = target_dt;
422  cdata->_reported_frame_time = wait_until_time;
423  }
424  break;
425 
426  case M_forced:
427  // If we are running faster than the desired interval, slow down. If we
428  // are running slower than the desired interval, ignore that and pretend
429  // we're running at the specified rate.
430  wait_until(old_time + 1.0 / _user_frame_rate);
431  cdata->_reported_frame_time = cdata->_reported_frame_time_epoch +
432  cdata->_frame_count / _user_frame_rate;
433  break;
434 
435  case M_degrade:
436  // Each frame, wait a certain fraction of the previous frame's time to
437  // degrade performance uniformly.
438  cdata->_dt = (_actual_frame_time - old_time) * _degrade_factor;
439 
440  if (_degrade_factor < 1.0) {
441  // If the degrade_factor is less than one, we want to simulate a
442  // higher frame rate by incrementing the clock more slowly.
443  cdata->_reported_frame_time += cdata->_dt;
444 
445  } else {
446  // Otherwise, we simulate a lower frame rate by waiting until the
447  // appropriate time has elapsed.
448  wait_until(old_time + cdata->_dt);
449  cdata->_reported_frame_time = _actual_frame_time;
450  }
451 
452  break;
453 
454  case M_slave:
455  // Handled above.
456  break;
457  }
458  }
459 
460  if (_average_frame_rate_interval > 0.0) {
461  _ticks.push_back(old_reported_time);
462  while (_ticks.size() > 2 &&
463  cdata->_reported_frame_time - _ticks.front() > _average_frame_rate_interval) {
464  _ticks.pop_front();
465  }
466  }
467 }
468 
469 /**
470  * Resets the frame time to the current real time. This is similar to tick(),
471  * except that it does not advance the frame counter and does not affect dt.
472  * This is intended to be used in the middle of a particularly long frame to
473  * compensate for the time that has already elapsed.
474  *
475  * In non-real-time mode, this function has no effect (because in this mode
476  * all frames take the same length of time).
477  */
479 sync_frame_time(Thread *current_thread) {
480  if (_mode == M_normal) {
481  CDWriter cdata(_cycler, current_thread);
482  cdata->_reported_frame_time = get_real_time();
483  }
484 }
485 
486 /**
487  * Waits at the end of a frame until the indicated time has arrived. This is
488  * used to implement M_forced and M_degrade.
489  */
490 void ClockObject::
491 wait_until(double want_time) {
492  if (want_time <= _actual_frame_time) {
493  return;
494  }
495 
496 #ifdef DO_PSTATS
497  (*_start_clock_wait)();
498 #endif
499 
500  double wait_interval = (want_time - _actual_frame_time) - sleep_precision;
501 
502  if (wait_interval > 0.0) {
503  Thread::sleep(wait_interval);
504  }
505 
506 #ifdef DO_PSTATS
507  (*_start_clock_busy_wait)();
508 #endif
509 
510  // Now busy-wait until the actual time elapses.
511  while (_actual_frame_time < want_time) {
512  _actual_frame_time = get_real_time();
513  }
514 
515 #ifdef DO_PSTATS
516  (*_stop_clock_wait)();
517 #endif
518 }
519 
520 /**
521  * Called once per application to create the global clock object.
522  */
523 void ClockObject::
524 make_global_clock() {
525  nassertv(_global_clock == nullptr);
526 
528  ("clock-mode", ClockObject::M_normal,
529  PRC_DESC("Specifies the mode of the global clock. The default mode, normal, "
530  "is a real-time clock; other modes allow non-real-time special "
531  "effects like simulated reduced frame rate. See "
532  "ClockObject::set_mode()."));
533 
534  ClockObject *clock = new ClockObject(clock_mode);
535  clock->local_object();
536 
537  if (AtomicAdjust::compare_and_exchange_ptr(_global_clock, nullptr, clock) != nullptr) {
538  // Another thread beat us to it.
539  delete clock;
540  }
541 }
542 
543 /**
544  * This no-op function is assigned as the initial pointer for
545  * _start_clock_wait and _stop_clock_wait, until the PStatClient comes along
546  * and replaces it.
547  */
548 void ClockObject::
549 dummy_clock_wait() {
550 }
551 
552 /**
553  *
554  */
555 ClockObject::CData::
556 CData() {
557  _frame_count = 0;
558  _reported_frame_time = 0.0;
559  _reported_frame_time_epoch = 0.0;
560  _dt = 0.0;
561 }
562 
563 /**
564  *
565  */
566 CycleData *ClockObject::CData::
567 make_copy() const {
568  return new CData(*this);
569 }
570 
571 /**
572  *
573  */
574 ostream &
575 operator << (ostream &out, ClockObject::Mode mode) {
576  switch (mode) {
577  case ClockObject::M_normal:
578  return out << "normal";
579 
580  case ClockObject::M_non_real_time:
581  return out << "non-real-time";
582 
583  case ClockObject::M_limited:
584  return out << "limited";
585 
586  case ClockObject::M_integer:
587  return out << "integer";
588 
589  case ClockObject::M_integer_limited:
590  return out << "integer_limited";
591 
592  case ClockObject::M_forced:
593  return out << "forced";
594 
595  case ClockObject::M_degrade:
596  return out << "degrade";
597 
598  case ClockObject::M_slave:
599  return out << "slave";
600  };
601 
602  return out << "**invalid ClockObject::Mode(" << (int)mode << ")**";
603 }
604 
605 /**
606  *
607  */
608 istream &
609 operator >> (istream &in, ClockObject::Mode &mode) {
610  string word;
611  in >> word;
612 
613  if (cmp_nocase_uh(word, "normal") == 0) {
614  mode = ClockObject::M_normal;
615  } else if (cmp_nocase_uh(word, "non-real-time") == 0) {
616  mode = ClockObject::M_non_real_time;
617  } else if (cmp_nocase_uh(word, "limited") == 0) {
618  mode = ClockObject::M_limited;
619  } else if (cmp_nocase_uh(word, "integer") == 0) {
620  mode = ClockObject::M_integer;
621  } else if (cmp_nocase_uh(word, "integer_limited") == 0) {
622  mode = ClockObject::M_integer_limited;
623  } else if (cmp_nocase_uh(word, "forced") == 0) {
624  mode = ClockObject::M_forced;
625  } else if (cmp_nocase_uh(word, "degrade") == 0) {
626  mode = ClockObject::M_degrade;
627  } else if (cmp_nocase_uh(word, "slave") == 0) {
628  mode = ClockObject::M_slave;
629  } else {
630  util_cat->error()
631  << "Invalid ClockObject::Mode: " << word << "\n";
632  mode = ClockObject::M_normal;
633  }
634 
635  return in;
636 }
static Pointer compare_and_exchange_ptr(Pointer &mem, Pointer old_value, Pointer new_value)
Atomic compare and exchange.
A ClockObject keeps track of elapsed real time and discrete time.
Definition: clockObject.h:58
get_frame_time
Returns the time in seconds as of the last time tick() was called (typically, this will be as of the ...
Definition: clockObject.h:91
void set_frame_rate(double frame_rate)
In non-real-time mode, sets the number of frames per second that we should appear to be running.
void tick(Thread *current_thread=Thread::get_current_thread())
Instructs the clock that a new frame has just begun.
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
Definition: clockObject.h:94
get_real_time
Returns the actual number of seconds elapsed since the ClockObject was created, or since it was last ...
Definition: clockObject.h:92
set_frame_time
Changes the time as reported for the current frame to the indicated time.
Definition: clockObject.h:91
set_frame_count
Resets the number of frames counted to the indicated number.
Definition: clockObject.h:94
set_real_time
Resets the clock to the indicated time.
Definition: clockObject.h:92
void sync_frame_time(Thread *current_thread=Thread::get_current_thread())
Resets the frame time to the current real time.
set_mode
Changes the mode of the clock.
Definition: clockObject.h:77
double calc_frame_rate_deviation(Thread *current_thread=Thread::get_current_thread()) const
Returns the standard deviation of the frame times of the frames rendered over the past get_average_fr...
get_average_frame_rate
Returns the average frame rate in number of frames per second over the last get_average_frame_rate_in...
Definition: clockObject.h:118
set_dt
In non-real-time mode, sets the number of seconds that should appear to elapse between frames.
Definition: clockObject.h:99
get_max_frame_duration
Returns the maximum frame duration over the last get_average_frame_rate_interval() seconds.
Definition: clockObject.h:119
This is a convenience class to specialize ConfigVariable as a floating- point type.
This class specializes ConfigVariable as an enumerated type.
This class is similar to CycleDataReader, except it allows reading from a particular stage of the pip...
This template class calls PipelineCycler::write() in the constructor and PipelineCycler::release_writ...
A single page of data maintained by a PipelineCycler.
Definition: cycleData.h:50
void local_object()
This function should be called, once, immediately after creating a new instance of some ReferenceCoun...
A thread; that is, a lightweight process.
Definition: thread.h:46
static void sleep(double seconds)
Suspends the current thread for at least the indicated amount of time.
Definition: thread.I:192
get_pipeline_stage
Returns the Pipeline stage number associated with this thread.
Definition: thread.h:105
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition: thread.h:109
static TrueClock * get_global_ptr()
Returns a pointer to the one TrueClock object in the world.
Definition: trueClock.I:68
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.