// Filename: clockObject.cxx
// Created by:  drose (17Feb00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

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

////////////////////////////////////////////////////////////////////
//     Function: ClockObject::Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
ClockObject::
ClockObject() {
  _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::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
  if (this == _global_clock) {
    util_cat.warning()
      << "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_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::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);
}

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