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