add ClockObject::M_integer and M_integer_limited; pstats clock wait

This commit is contained in:
David Rose 2006-10-20 17:38:22 +00:00
parent 463cc7be4f
commit 70f9db5ec4
6 changed files with 332 additions and 91 deletions

View File

@ -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

View File

@ -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;

View File

@ -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 } },

View File

@ -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

View File

@ -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()

View File

@ -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;
};