diff --git a/panda/src/pstatclient/pStatClient.cxx b/panda/src/pstatclient/pStatClient.cxx index d74e845e57..d5d32034eb 100644 --- a/panda/src/pstatclient/pStatClient.cxx +++ b/panda/src/pstatclient/pStatClient.cxx @@ -30,11 +30,14 @@ #include "config_pstats.h" #include "pStatProperties.h" #include "thread.h" +#include "clockObject.h" PStatCollector PStatClient::_total_size_pcollector("Memory usage"); PStatCollector PStatClient::_cpp_size_pcollector("Memory usage:C++"); PStatCollector PStatClient::_interpreter_size_pcollector("Memory usage:Interpreter"); PStatCollector PStatClient::_pstats_pcollector("*:PStats"); +PStatCollector PStatClient::_clock_wait_pcollector("Wait:Clock Wait:Sleep"); +PStatCollector PStatClient::_clock_busy_wait_pcollector("Wait:Clock Wait:Spin"); PStatClient *PStatClient::_global_pstats = NULL; @@ -317,6 +320,10 @@ PStatClient *PStatClient:: get_global_pstats() { if (_global_pstats == (PStatClient *)NULL) { _global_pstats = new PStatClient; + + ClockObject::_start_clock_wait = start_clock_wait; + ClockObject::_start_clock_busy_wait = start_clock_busy_wait; + ClockObject::_stop_clock_wait = stop_clock_wait; } return _global_pstats; } @@ -796,6 +803,58 @@ get_level(int collector_index, int thread_index) const { return collector->_per_thread[thread_index]._level / factor; } +//////////////////////////////////////////////////////////////////// +// Function: PStatClient::start_clock_wait +// Access: Private, Static +// Description: This function is added as a hook into ClockObject, so +// that we may time the delay for +// ClockObject::wait_until(), used for certain special +// clock modes. +// +// This callback is a hack around the fact that we can't +// let the ClockObject directly create a PStatCollector, +// because the pstatclient module depends on putil. +//////////////////////////////////////////////////////////////////// +void PStatClient:: +start_clock_wait() { + _clock_wait_pcollector.start(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClient::start_clock_busy_wait +// Access: Private, Static +// Description: This function is added as a hook into ClockObject, so +// that we may time the delay for +// ClockObject::wait_until(), used for certain special +// clock modes. +// +// This callback is a hack around the fact that we can't +// let the ClockObject directly create a PStatCollector, +// because the pstatclient module depends on putil. +//////////////////////////////////////////////////////////////////// +void PStatClient:: +start_clock_busy_wait() { + _clock_wait_pcollector.stop(); + _clock_busy_wait_pcollector.start(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClient::stop_clock_wait +// Access: Private, Static +// Description: This function is added as a hook into ClockObject, so +// that we may time the delay for +// ClockObject::wait_until(), used for certain special +// clock modes. +// +// This callback is a hack around the fact that we can't +// let the ClockObject directly create a PStatCollector, +// because the pstatclient module depends on putil. +//////////////////////////////////////////////////////////////////// +void PStatClient:: +stop_clock_wait() { + _clock_busy_wait_pcollector.stop(); +} + //////////////////////////////////////////////////////////////////// // Function: PStatClient::add_collector // Access: Private diff --git a/panda/src/pstatclient/pStatClient.h b/panda/src/pstatclient/pStatClient.h index 91549f9115..98ed88c0a8 100644 --- a/panda/src/pstatclient/pStatClient.h +++ b/panda/src/pstatclient/pStatClient.h @@ -128,6 +128,10 @@ private: void add_level(int collector_index, int thread_index, float increment); float get_level(int collector_index, int thread_index) const; + static void start_clock_wait(); + static void start_clock_busy_wait(); + static void stop_clock_wait(); + class Collector; class InternalThread; void add_collector(Collector *collector); @@ -220,6 +224,8 @@ private: static PStatCollector _cpp_size_pcollector; static PStatCollector _interpreter_size_pcollector; static PStatCollector _pstats_pcollector; + static PStatCollector _clock_wait_pcollector; + static PStatCollector _clock_busy_wait_pcollector; static PStatClient *_global_pstats; diff --git a/panda/src/pstatclient/pStatProperties.cxx b/panda/src/pstatclient/pStatProperties.cxx index f4a96f8cab..62e6a584ef 100644 --- a/panda/src/pstatclient/pStatProperties.cxx +++ b/panda/src/pstatclient/pStatProperties.cxx @@ -111,6 +111,10 @@ static TimeCollectorProperties time_properties[] = { { 1, "Wait", { 0.6, 0.6, 0.6 } }, { 0, "Wait:Mutex block", { 0.5, 0.0, 1.0 } }, { 1, "Wait:Thread sync", { 0.0, 1.0, 0.5 } }, + { 1, "Wait:Clock Wait", { 0.2, 0.8, 0.2 } }, + { 1, "Wait:Clock Wait:Sleep", { 0.9, 0.4, 0.8 } }, + { 1, "Wait:Clock Wait:Spin", { 0.2, 0.8, 1.0 } }, + { 0, "Wait:Mutex block", { 0.5, 0.0, 1.0 } }, { 1, "App", { 0.0, 0.4, 0.8 }, 1.0 / 30.0 }, { 1, "App:Collisions", { 1.0, 0.5, 0.0 } }, { 1, "App:Collisions:Reset", { 0.0, 0.0, 0.5 } }, diff --git a/panda/src/putil/clockObject.I b/panda/src/putil/clockObject.I index 005750d8e3..d7b2209b56 100644 --- a/panda/src/putil/clockObject.I +++ b/panda/src/putil/clockObject.I @@ -25,50 +25,6 @@ INLINE ClockObject:: ~ClockObject() { } -//////////////////////////////////////////////////////////////////// -// Function: ClockObject::set_mode -// Access: Published -// Description: Changes the mode of the clock. Normally, the clock -// is in mode M_normal. In this mode, each call to -// tick() will set the value returned by -// get_frame_time() to the current real time; thus, the -// clock simply reports time advancing. -// -// Other possible modes: -// -// M_non_real_time - the clock ignores real time -// completely; at each call to tick(), it pretends that -// exactly dt seconds have elapsed since the last call -// to tick(). You may set the value of dt with -// set_dt(). -// -// M_limited - the clock will run as fast as it can, as -// in M_normal, but will not run faster than the rate -// specified by set_dt(). If the application would run -// faster than this rate, the clock will slow down the -// application. -// -// M_forced - the clock forces the application to run at -// the rate specified by set_dt(). If the application -// would run faster than this rate, the clock will slow -// down the application; if the application would run -// slower than this rate, the clock slows down time so -// that the application believes it is running at the -// given rate. -// -// M_degrade - the clock runs at real time, but the -// application is slowed down by a set factor of its -// frame rate, specified by set_degrade_factor(). -// -// M_slave - the clock does not advance, but relies on -// the user to call set_frame_time() and/or -// set_frame_count() each frame. -//////////////////////////////////////////////////////////////////// -INLINE void ClockObject:: -set_mode(ClockObject::Mode mode) { - _mode = mode; -} - //////////////////////////////////////////////////////////////////// // Function: ClockObject::get_mode // Access: Published @@ -200,14 +156,15 @@ get_dt(Thread *current_thread) const { // Description: In non-real-time mode, sets the number of seconds // that should appear to elapse between frames. In // forced mode or limited mode, sets our target dt. In -// normal mode, this has no effect. +// normal mode, this has no effect. +// +// Also see set_frame_rate(), which is a different way +// to specify the same quantity. //////////////////////////////////////////////////////////////////// INLINE void ClockObject:: -set_dt(double dt, Thread *current_thread) { - nassertv(current_thread->get_pipeline_stage() == 0); - _set_dt = dt; - CDWriter cdata(_cycler, current_thread); - cdata->_dt = dt; +set_dt(double dt) { + nassertv(dt != 0.0); + set_frame_rate(1.0 / dt); } //////////////////////////////////////////////////////////////////// @@ -306,25 +263,6 @@ get_average_frame_rate_interval() const { return _average_frame_rate_interval; } -//////////////////////////////////////////////////////////////////// -// Function: ClockObject::get_average_frame_rate -// Access: Published -// Description: Returns the average frame rate in number of frames -// per second over the last -// get_average_frame_rate_interval() seconds. This -// measures the virtual frame rate if the clock is in -// M_non_real_time mode. -//////////////////////////////////////////////////////////////////// -INLINE double ClockObject:: -get_average_frame_rate(Thread *current_thread) const { - CDStageReader cdata(_cycler, 0, current_thread); - if (_ticks.size() <= 1) { - return 0.0; - } else { - return _ticks.size() / (cdata->_reported_frame_time - _ticks.front()); - } -} - //////////////////////////////////////////////////////////////////// // Function: ClockObject::check_errors // Access: Published diff --git a/panda/src/putil/clockObject.cxx b/panda/src/putil/clockObject.cxx index f6a101c957..2c5fbe0d63 100644 --- a/panda/src/putil/clockObject.cxx +++ b/panda/src/putil/clockObject.cxx @@ -19,8 +19,13 @@ #include "clockObject.h" #include "config_util.h" #include "configVariableEnum.h" +#include "string_utils.h" #include "thread.h" +void (*ClockObject::_start_clock_wait)() = ClockObject::dummy_clock_wait; +void (*ClockObject::_start_clock_busy_wait)() = ClockObject::dummy_clock_wait; +void (*ClockObject::_stop_clock_wait)() = ClockObject::dummy_clock_wait; + ClockObject *ClockObject::_global_clock = (ClockObject *)NULL; TypeHandle ClockObject::_type_handle; @@ -42,13 +47,83 @@ ClockObject() { _start_long_time = _true_clock->get_long_time(); _actual_frame_time = 0.0; _max_dt = max_dt; + _user_frame_rate = clock_frame_rate; _degrade_factor = clock_degrade_factor; _average_frame_rate_interval = average_frame_rate_interval; _error_count = _true_clock->get_error_count(); +} - CDReader cdata(_cycler); - _set_dt = cdata->_dt; +//////////////////////////////////////////////////////////////////// +// Function: ClockObject::set_mode +// Access: Published +// Description: Changes the mode of the clock. Normally, the clock +// is in mode M_normal. In this mode, each call to +// tick() will set the value returned by +// get_frame_time() to the current real time; thus, the +// clock simply reports time advancing. +// +// Other possible modes: +// +// M_non_real_time - the clock ignores real time +// completely; at each call to tick(), it pretends that +// exactly dt seconds have elapsed since the last call +// to tick(). You may set the value of dt with +// set_dt() or set_frame_rate(). +// +// M_limited - the clock will run as fast as it can, as +// in M_normal, but will not run faster than the rate +// specified by set_frame_rate(). If the application +// would run faster than this rate, the clock will slow +// down the application. +// +// M_integer - the clock will run as fast as it can, but +// the rate will be constrained to be an integer +// multiple or divisor of the rate specified by +// set_frame_rate(). The clock will slow down the +// application a bit to guarantee this. +// +// M_integer_limited - a combination of M_limited and +// M_integer; the clock will not run faster than +// set_frame_rate(), and if it runs slower, it will run +// at a integer divisor of that rate. +// +// M_forced - the clock forces the application to run at +// the rate specified by set_frame_rate(). If the +// application would run faster than this rate, the +// clock will slow down the application; if the +// application would run slower than this rate, the +// clock slows down time so that the application +// believes it is running at the given rate. +// +// M_degrade - the clock runs at real time, but the +// application is slowed down by a set factor of its +// frame rate, specified by set_degrade_factor(). +// +// M_slave - the clock does not advance, but relies on +// the user to call set_frame_time() and/or +// set_frame_count() each frame. +//////////////////////////////////////////////////////////////////// +void ClockObject:: +set_mode(ClockObject::Mode mode) { + Thread *current_thread = Thread::get_current_thread(); + nassertv(current_thread->get_pipeline_stage() == 0); + CDWriter cdata(_cycler, current_thread); + + _mode = mode; + + // In case we have set the clock to one of the modes that uses + // _reported_frame_time_epoch, recompute the epoch. + switch (_mode) { + case M_non_real_time: + case M_forced: + cdata->_reported_frame_time_epoch = cdata->_reported_frame_time - + cdata->_frame_count / _user_frame_rate; + cdata->_dt = 1.0 / _user_frame_rate; + + default: + break; + } } //////////////////////////////////////////////////////////////////// @@ -95,6 +170,10 @@ set_frame_time(double time, Thread *current_thread) { CDWriter cdata(_cycler, current_thread); _actual_frame_time = time; cdata->_reported_frame_time = time; + + // Recompute the epoch in case we are in a mode that relies on this. + cdata->_reported_frame_time_epoch = cdata->_reported_frame_time - + cdata->_frame_count / _user_frame_rate; } //////////////////////////////////////////////////////////////////// @@ -116,6 +195,62 @@ set_frame_count(int frame_count, Thread *current_thread) { #endif // NOTIFY_DEBUG CDWriter cdata(_cycler, current_thread); cdata->_frame_count = frame_count; + + // Recompute the epoch in case we are in a mode that relies on this. + cdata->_reported_frame_time_epoch = cdata->_reported_frame_time - + cdata->_frame_count / _user_frame_rate; +} + +//////////////////////////////////////////////////////////////////// +// Function: ClockObject::set_frame_rate +// Access: Published +// Description: In non-real-time mode, sets the number of frames per +// second that we should appear to be running. In forced +// mode or limited mode, sets our target frame rate. In +// normal mode, this has no effect. +// +// Also see set_dt(), which is a different way to +// specify the same quantity. +//////////////////////////////////////////////////////////////////// +void ClockObject:: +set_frame_rate(double frame_rate) { + nassertv(frame_rate != 0.0); + + Thread *current_thread = Thread::get_current_thread(); + nassertv(current_thread->get_pipeline_stage() == 0); + + CDWriter cdata(_cycler, current_thread); + _user_frame_rate = frame_rate; + + switch (_mode) { + case M_non_real_time: + case M_forced: + cdata->_reported_frame_time_epoch = cdata->_reported_frame_time - + cdata->_frame_count / _user_frame_rate; + cdata->_dt = 1.0 / _user_frame_rate; + + default: + break; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: ClockObject::get_average_frame_rate +// Access: Published +// Description: Returns the average frame rate in number of frames +// per second over the last +// get_average_frame_rate_interval() seconds. This +// measures the virtual frame rate if the clock is in +// M_non_real_time mode. +//////////////////////////////////////////////////////////////////// +double ClockObject:: +get_average_frame_rate(Thread *current_thread) const { + CDStageReader cdata(_cycler, 0, current_thread); + if (_ticks.size() <= 1) { + return 0.0; + } else { + return _ticks.size() / (cdata->_reported_frame_time - _ticks.front()); + } } //////////////////////////////////////////////////////////////////// @@ -137,6 +272,14 @@ tick(Thread *current_thread) { if (_mode != M_slave) { double old_time = _actual_frame_time; _actual_frame_time = get_real_time(); + + // In case someone munged the clock last frame and sent us + // backward in time, clamp the previous time to the current time + // to make sure we don't report anything strange (or wait + // interminably). + old_time = min(old_time, _actual_frame_time); + + ++cdata->_frame_count; switch (_mode) { case M_normal: @@ -148,22 +291,67 @@ tick(Thread *current_thread) { case M_non_real_time: // Ignore real time. We always report the same interval having // elapsed each frame. - cdata->_reported_frame_time += _set_dt; + cdata->_reported_frame_time = cdata->_reported_frame_time_epoch + + cdata->_frame_count / _user_frame_rate; break; case M_limited: // If we are running faster than the desired interval, slow down. - wait_until(old_time + _set_dt); - cdata->_dt = _actual_frame_time - old_time; - cdata->_reported_frame_time = _actual_frame_time; + { + double wait_until_time = old_time + 1.0 / _user_frame_rate; + wait_until(wait_until_time); + cdata->_dt = _actual_frame_time - old_time; + cdata->_reported_frame_time = max(_actual_frame_time, wait_until_time); + } + break; + + case M_integer: + { + double dt = _actual_frame_time - old_time; + double target_dt = 1.0 / _user_frame_rate; + if (dt < target_dt) { + // We're running faster than the desired interval, so slow + // down to the next integer multiple of the frame rate. + target_dt = target_dt / floor(target_dt / dt); + } else { + // We're running slower than the desired interval, so slow + // down to the next integer divisor of the frame rate. + target_dt = target_dt * ceil(dt / target_dt); + } + double wait_until_time = old_time + target_dt; + wait_until(wait_until_time); + cdata->_dt = target_dt; + cdata->_reported_frame_time = wait_until_time; + } + break; + + case M_integer_limited: + { + double dt = _actual_frame_time - old_time; + double target_dt = 1.0 / _user_frame_rate; + if (dt < target_dt) { + // We're running faster than the desired interval, so slow + // down to the target frame rate. + + } else { + // We're running slower than the desired interval, so slow + // down to the next integer divisor of the frame rate. + target_dt = target_dt * ceil(dt / target_dt); + } + double wait_until_time = old_time + target_dt; + wait_until(wait_until_time); + cdata->_dt = target_dt; + cdata->_reported_frame_time = wait_until_time; + } break; case M_forced: // If we are running faster than the desired interval, slow down. // If we are running slower than the desired interval, ignore that // and pretend we're running at the specified rate. - wait_until(old_time + _set_dt); - cdata->_reported_frame_time += _set_dt; + wait_until(old_time + 1.0 / _user_frame_rate); + cdata->_reported_frame_time = cdata->_reported_frame_time_epoch + + cdata->_frame_count / _user_frame_rate; break; case M_degrade: @@ -189,13 +377,11 @@ tick(Thread *current_thread) { // Handled above. break; } - - cdata->_frame_count++; } if (_average_frame_rate_interval > 0.0) { _ticks.push_back(old_reported_time); - while (!_ticks.empty() && + while (_ticks.size() > 2 && cdata->_reported_frame_time - _ticks.front() > _average_frame_rate_interval) { _ticks.pop_front(); } @@ -233,16 +419,32 @@ sync_frame_time(Thread *current_thread) { //////////////////////////////////////////////////////////////////// void ClockObject:: wait_until(double want_time) { + if (want_time <= _actual_frame_time) { + return; + } + +#ifdef DO_PSTATS + (*_start_clock_wait)(); +#endif + double wait_interval = (want_time - _actual_frame_time) - sleep_precision; if (wait_interval > 0.0) { Thread::sleep(wait_interval); } + +#ifdef DO_PSTATS + (*_start_clock_busy_wait)(); +#endif // Now busy-wait until the actual time elapses. while (_actual_frame_time < want_time) { _actual_frame_time = get_real_time(); } + +#ifdef DO_PSTATS + (*_stop_clock_wait)(); +#endif } //////////////////////////////////////////////////////////////////// @@ -266,6 +468,17 @@ make_global_clock() { _global_clock->set_mode(clock_mode); } +//////////////////////////////////////////////////////////////////// +// Function: ClockObject::dummy_clock_wait +// Access: Private, Static +// Description: This no-op function is assigned as the initial +// pointer for _start_clock_wait and _stop_clock_wait, +// until the PStatClient comes along and replaces it. +//////////////////////////////////////////////////////////////////// +void ClockObject:: +dummy_clock_wait() { +} + //////////////////////////////////////////////////////////////////// // Function: ClockObject::CData::Constructor // Access: Public @@ -275,7 +488,8 @@ ClockObject::CData:: CData() { _frame_count = 0; _reported_frame_time = 0.0; - _dt = 1.0 / clock_frame_rate; + _reported_frame_time_epoch = 0.0; + _dt = 0.0; } //////////////////////////////////////////////////////////////////// @@ -304,6 +518,12 @@ operator << (ostream &out, ClockObject::Mode mode) { case ClockObject::M_limited: return out << "limited"; + case ClockObject::M_integer: + return out << "integer"; + + case ClockObject::M_integer_limited: + return out << "integer_limited"; + case ClockObject::M_forced: return out << "forced"; @@ -326,17 +546,21 @@ operator >> (istream &in, ClockObject::Mode &mode) { string word; in >> word; - if (word == "normal") { + if (cmp_nocase_uh(word, "normal") == 0) { mode = ClockObject::M_normal; - } else if (word == "non-real-time") { + } else if (cmp_nocase_uh(word, "non-real-time") == 0) { mode = ClockObject::M_non_real_time; - } else if (word == "limited") { + } else if (cmp_nocase_uh(word, "limited") == 0) { mode = ClockObject::M_limited; - } else if (word == "forced") { + } else if (cmp_nocase_uh(word, "integer") == 0) { + mode = ClockObject::M_integer; + } else if (cmp_nocase_uh(word, "integer_limited") == 0) { + mode = ClockObject::M_integer_limited; + } else if (cmp_nocase_uh(word, "forced") == 0) { mode = ClockObject::M_forced; - } else if (word == "degrade") { + } else if (cmp_nocase_uh(word, "degrade") == 0) { mode = ClockObject::M_degrade; - } else if (word == "slave") { + } else if (cmp_nocase_uh(word, "slave") == 0) { mode = ClockObject::M_slave; } else { util_cat.error() diff --git a/panda/src/putil/clockObject.h b/panda/src/putil/clockObject.h index 6accc8844f..b8e70f3fe4 100644 --- a/panda/src/putil/clockObject.h +++ b/panda/src/putil/clockObject.h @@ -72,12 +72,14 @@ PUBLISHED: M_degrade, M_slave, M_limited, + M_integer, + M_integer_limited, }; ClockObject(); INLINE ~ClockObject(); - INLINE void set_mode(Mode mode); + void set_mode(Mode mode); INLINE Mode get_mode() const; INLINE double get_frame_time(Thread *current_thread = Thread::get_current_thread()) const; @@ -93,7 +95,8 @@ PUBLISHED: INLINE double get_net_frame_rate(Thread *current_thread = Thread::get_current_thread()) const; INLINE double get_dt(Thread *current_thread = Thread::get_current_thread()) const; - INLINE void set_dt(double dt, Thread *current_thread = Thread::get_current_thread()); + INLINE void set_dt(double dt); + void set_frame_rate(double frame_rate); INLINE double get_max_dt() const; INLINE void set_max_dt(double max_dt); @@ -103,7 +106,7 @@ PUBLISHED: INLINE void set_average_frame_rate_interval(double time); INLINE double get_average_frame_rate_interval() const; - INLINE double get_average_frame_rate(Thread *current_thread = Thread::get_current_thread()) const; + double get_average_frame_rate(Thread *current_thread = Thread::get_current_thread()) const; void tick(Thread *current_thread = Thread::get_current_thread()); void sync_frame_time(Thread *current_thread = Thread::get_current_thread()); @@ -112,9 +115,15 @@ PUBLISHED: INLINE static ClockObject *get_global_clock(); +public: + static void (*_start_clock_wait)(); + static void (*_start_clock_busy_wait)(); + static void (*_stop_clock_wait)(); + private: void wait_until(double want_time); static void make_global_clock(); + static void dummy_clock_wait(); TrueClock *_true_clock; Mode _mode; @@ -122,7 +131,7 @@ private: double _start_long_time; double _actual_frame_time; double _max_dt; - double _set_dt; + double _user_frame_rate; double _degrade_factor; int _error_count; @@ -145,6 +154,7 @@ private: int _frame_count; double _reported_frame_time; + double _reported_frame_time_epoch; double _dt; };