panda3d/panda/src/putil/clockObject.cxx

703 lines
26 KiB
C++

// Filename: clockObject.cxx
// Created by: drose (17Feb00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#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;
TypeHandle ClockObject::_type_handle;
////////////////////////////////////////////////////////////////////
// Function: ClockObject::Constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
ClockObject::
ClockObject() : _ticks(get_class_type()) {
_true_clock = TrueClock::get_global_ptr();
// Each clock except for the application global clock is created in
// M_normal mode. The application global clock is later reset to
// respect clock_mode, which comes from the Config.prc file.
_mode = M_normal;
_start_short_time = _true_clock->get_short_time();
_start_long_time = _true_clock->get_long_time();
_actual_frame_time = 0.0;
ConfigVariableDouble max_dt
("max-dt", -1.0,
PRC_DESC("Sets a limit on the value returned by ClockObject::get_dt(). If "
"this value is less than zero, no limit is imposed; "
"otherwise, this is the maximum value that will ever "
"be returned by get_dt(), regardless of how much time "
"has actually elapsed between frames. See ClockObject::set_dt()."));
ConfigVariableDouble clock_frame_rate
("clock-frame-rate", 1.0,
PRC_DESC("In non-real-time clock 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. See ClockObject::set_frame_rate()."));
ConfigVariableDouble clock_degrade_factor
("clock-degrade-factor", 1.0,
PRC_DESC("In degrade clock mode, returns the ratio by which the "
"performance is degraded. A value of 2.0 causes the "
"clock to be slowed down by a factor of two (reducing "
"performance to 1/2 what would be otherwise). See ClockObject::set_degrade_factor()."));
ConfigVariableDouble average_frame_rate_interval
("average-frame-rate-interval", 1.0,
PRC_DESC("See ClockObject::set_average_frame_rate_interval()."));
_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();
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::Copy Constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
ClockObject::
ClockObject(const ClockObject &copy) :
_true_clock(copy._true_clock),
_mode(copy._mode),
_start_short_time(copy._start_short_time),
_start_long_time(copy._start_long_time),
_actual_frame_time(copy._actual_frame_time),
_max_dt(copy._max_dt),
_user_frame_rate(copy._user_frame_rate),
_degrade_factor(copy._degrade_factor),
_error_count(copy._error_count),
_average_frame_rate_interval(copy._average_frame_rate_interval),
_ticks(copy._ticks),
_cycler(copy._cycler)
{
}
////////////////////////////////////////////////////////////////////
// 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;
}
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::set_real_time
// Access: Published
// Description: Resets the clock to the indicated time. This
// changes only the real time of the clock as reported
// by get_real_time(), but does not immediately change
// the time reported by get_frame_time()--that will
// change after the next call to tick(). Also see
// reset(), set_frame_time(), and set_frame_count().
////////////////////////////////////////////////////////////////////
void ClockObject::
set_real_time(double time) {
#ifdef NOTIFY_DEBUG
// This is only a debug message, since it happens during normal
// development, particularly at startup, or whenever you break into
// the task loop.
if (util_cat.is_debug() && this == _global_clock) {
util_cat.debug()
<< "Adjusting global clock's real time by " << time - get_real_time()
<< " seconds.\n";
}
#endif // NOTIFY_DEBUG
_start_short_time = _true_clock->get_short_time() - time;
_start_long_time = _true_clock->get_long_time() - time;
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::set_frame_time
// Access: Published
// Description: Changes the time as reported for the current frame to
// the indicated time. Normally, the way to adjust the
// frame time is via tick(); this function is provided
// only for occasional special adjustments.
////////////////////////////////////////////////////////////////////
void ClockObject::
set_frame_time(double time, Thread *current_thread) {
nassertv(current_thread->get_pipeline_stage() == 0);
#ifdef NOTIFY_DEBUG
if (this == _global_clock && _mode != M_slave) {
util_cat.warning()
<< "Adjusting global clock's frame time by " << time - get_frame_time()
<< " seconds.\n";
}
#endif // NOTIFY_DEBUG
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;
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::set_frame_count
// Access: Published
// Description: Resets the number of frames counted to the indicated
// number. Also see reset(), set_real_time(), and
// set_frame_time().
////////////////////////////////////////////////////////////////////
void ClockObject::
set_frame_count(int frame_count, Thread *current_thread) {
nassertv(current_thread->get_pipeline_stage() == 0);
#ifdef NOTIFY_DEBUG
if (this == _global_clock && _mode != M_slave) {
util_cat.warning()
<< "Adjusting global clock's frame count by "
<< frame_count - get_frame_count() << " frames.\n";
}
#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_dt
// Access: Published
// 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.
//
// Also see set_frame_rate(), which is a different way
// to specify the same quantity.
////////////////////////////////////////////////////////////////////
void ClockObject::
set_dt(double dt) {
if (_mode == M_slave) {
// In M_slave mode, we can set any dt we like.
CDWriter cdata(_cycler, Thread::get_current_thread());
cdata->_dt = dt;
if (dt != 0.0) {
set_frame_rate(1.0 / dt);
}
} else {
// In any other mode, we can only set non-zero dt.
nassertv(dt != 0.0);
set_frame_rate(1.0 / dt);
}
}
////////////////////////////////////////////////////////////////////
// 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());
}
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::get_max_frame_duration
// Access: Published
// Description: Returns the maximum frame duration over the last
// get_average_frame_rate_interval() seconds.
////////////////////////////////////////////////////////////////////
double ClockObject::
get_max_frame_duration(Thread *current_thread) const {
CDStageReader cdata(_cycler, 0, current_thread);
double max_duration = 0.0;
double cur_duration = 0.0;
size_t i;
for (i = 0; i < _ticks.size() - 1; i++) {
cur_duration = _ticks[i + 1] - _ticks[i];
if (cur_duration > max_duration) {
max_duration = cur_duration;
}
}
return max_duration;
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::calc_frame_time_deviation
// Access: Published
// Description: Returns the standard deviation of the frame times of
// the frames rendered over the past
// get_average_frame_rate_interval() seconds. This
// number gives an estimate of the chugginess of the
// frame rate; if it is large, there is a large
// variation in the frame rate; if is small, all of the
// frames are consistent in length.
//
// A large value might also represent just a recent
// change in frame rate, for instance, because the
// camera has just rotated from looking at a simple
// scene to looking at a more complex scene.
////////////////////////////////////////////////////////////////////
double ClockObject::
calc_frame_rate_deviation(Thread *current_thread) const {
CDStageReader cdata(_cycler, 0, current_thread);
if (_ticks.size() <= 1) {
return 0.0;
} else {
double mean = (_ticks.back() - _ticks.front()) / (_ticks.size() - 1);
size_t i;
double sum_squares = 0.0;
for (i = 0; i < _ticks.size() - 1; ++i) {
double delta = _ticks[i + 1] - _ticks[i];
double diff = (delta - mean);
sum_squares += (diff * diff);
}
double deviation_2 = sum_squares / (_ticks.size() - 1);
return sqrt(deviation_2);
}
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::tick
// Access: Published
// Description: Instructs the clock that a new frame has just begun.
// In normal, real-time mode, get_frame_time() will
// henceforth report the time as of this instant as the
// current start-of-frame time. In non-real-time mode,
// get_frame_time() will be incremented by the value of
// dt.
////////////////////////////////////////////////////////////////////
void ClockObject::
tick(Thread *current_thread) {
nassertv(current_thread->get_pipeline_stage() == 0);
CDWriter cdata(_cycler, current_thread);
double old_reported_time = cdata->_reported_frame_time;
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:
// Time runs as it will; we simply report time elapsing.
cdata->_dt = _actual_frame_time - old_time;
cdata->_reported_frame_time = _actual_frame_time;
break;
case M_non_real_time:
// Ignore real time. We always report the same interval having
// elapsed each frame.
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.
{
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 + 1.0 / _user_frame_rate);
cdata->_reported_frame_time = cdata->_reported_frame_time_epoch +
cdata->_frame_count / _user_frame_rate;
break;
case M_degrade:
// Each frame, wait a certain fraction of the previous frame's
// time to degrade performance uniformly.
cdata->_dt = (_actual_frame_time - old_time) * _degrade_factor;
if (_degrade_factor < 1.0) {
// If the degrade_factor is less than one, we want to simulate a
// higher frame rate by incrementing the clock more slowly.
cdata->_reported_frame_time += cdata->_dt;
} else {
// Otherwise, we simulate a lower frame rate by waiting until
// the appropriate time has elapsed.
wait_until(old_time + cdata->_dt);
cdata->_reported_frame_time = _actual_frame_time;
}
break;
case M_slave:
// Handled above.
break;
}
}
if (_average_frame_rate_interval > 0.0) {
_ticks.push_back(old_reported_time);
while (_ticks.size() > 2 &&
cdata->_reported_frame_time - _ticks.front() > _average_frame_rate_interval) {
_ticks.pop_front();
}
}
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::sync_frame_time
// Access: Published
// Description: Resets the frame time to the current real time. This
// is similar to tick(), except that it does not advance
// the frame counter and does not affect dt. This is
// intended to be used in the middle of a particularly
// long frame to compensate for the time that has
// already elapsed.
//
// In non-real-time mode, this function has no effect
// (because in this mode all frames take the same length
// of time).
////////////////////////////////////////////////////////////////////
void ClockObject::
sync_frame_time(Thread *current_thread) {
if (_mode == M_normal) {
CDWriter cdata(_cycler, current_thread);
cdata->_reported_frame_time = get_real_time();
}
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::wait_until
// Access: Private
// Description: Waits at the end of a frame until the indicated time
// has arrived. This is used to implement M_forced and
// M_degrade.
////////////////////////////////////////////////////////////////////
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
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::make_global_clock
// Access: Private, Static
// Description: Called once per application to create the global
// clock object.
////////////////////////////////////////////////////////////////////
void ClockObject::
make_global_clock() {
nassertv(_global_clock == (ClockObject *)NULL);
ConfigVariableEnum<ClockObject::Mode> clock_mode
("clock-mode", ClockObject::M_normal,
PRC_DESC("Specifies the mode of the global clock. The default mode, normal, "
"is a real-time clock; other modes allow non-real-time special "
"effects like simulated reduced frame rate. See "
"ClockObject::set_mode()."));
_global_clock = new ClockObject;
_global_clock->set_mode(clock_mode);
_global_clock->ref();
}
////////////////////////////////////////////////////////////////////
// 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
// Description:
////////////////////////////////////////////////////////////////////
ClockObject::CData::
CData() {
_frame_count = 0;
_reported_frame_time = 0.0;
_reported_frame_time_epoch = 0.0;
_dt = 0.0;
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::CData::make_copy
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
CycleData *ClockObject::CData::
make_copy() const {
return new CData(*this);
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::Mode ostream operator
// Description:
////////////////////////////////////////////////////////////////////
ostream &
operator << (ostream &out, ClockObject::Mode mode) {
switch (mode) {
case ClockObject::M_normal:
return out << "normal";
case ClockObject::M_non_real_time:
return out << "non-real-time";
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";
case ClockObject::M_degrade:
return out << "degrade";
case ClockObject::M_slave:
return out << "slave";
};
return out << "**invalid ClockObject::Mode(" << (int)mode << ")**";
}
////////////////////////////////////////////////////////////////////
// Function: ClockObject::Mode istream operator
// Description:
////////////////////////////////////////////////////////////////////
istream &
operator >> (istream &in, ClockObject::Mode &mode) {
string word;
in >> word;
if (cmp_nocase_uh(word, "normal") == 0) {
mode = ClockObject::M_normal;
} else if (cmp_nocase_uh(word, "non-real-time") == 0) {
mode = ClockObject::M_non_real_time;
} else if (cmp_nocase_uh(word, "limited") == 0) {
mode = ClockObject::M_limited;
} 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 (cmp_nocase_uh(word, "degrade") == 0) {
mode = ClockObject::M_degrade;
} else if (cmp_nocase_uh(word, "slave") == 0) {
mode = ClockObject::M_slave;
} else {
util_cat->error()
<< "Invalid ClockObject::Mode: " << word << "\n";
mode = ClockObject::M_normal;
}
return in;
}