// Filename: asyncTask.cxx
// Created by:  drose (23Aug06)
//
////////////////////////////////////////////////////////////////////
//
// 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 "asyncTask.h"
#include "asyncTaskManager.h"
#include "config_event.h"
#include "pt_Event.h"
#include "throw_event.h"
#include "eventParameter.h"

AtomicAdjust::Integer AsyncTask::_next_task_id;
PStatCollector AsyncTask::_show_code_pcollector("App:Show code");
TypeHandle AsyncTask::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
AsyncTask::
AsyncTask(const string &name) : 
  _chain_name("default"),
  _delay(0.0),
  _has_delay(false),
  _wake_time(0.0),
  _sort(0),
  _priority(0),
  _state(S_inactive),
  _servicing_thread(NULL),
  _manager(NULL),
  _chain(NULL),
  _start_time(0.0),
  _start_frame(0),
  _dt(0.0),
  _max_dt(0.0),
  _total_dt(0.0),
  _num_frames(0)
{
#ifdef HAVE_PYTHON
  _python_object = NULL;
#endif  // HAVE_PYTHON
  set_name(name);

  // Carefully copy _next_task_id and increment it so that we get a
  // unique ID.
  AtomicAdjust::Integer current_id = _next_task_id;
  while (AtomicAdjust::compare_and_exchange(_next_task_id, current_id, current_id + 1) != current_id) {
    current_id = _next_task_id;
  }

  _task_id = current_id;
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::Destructor
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
AsyncTask::
~AsyncTask() {
  nassertv(_state == S_inactive && _manager == NULL && _chain == NULL);
#ifdef HAVE_PYTHON
  set_python_object(NULL);
#endif
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::remove
//       Access: Published
//  Description: Removes the task from its active manager, if any, and
//               makes the state S_inactive (or possible
//               S_servicing_removed).  This is a no-op if the state
//               is already S_inactive.
////////////////////////////////////////////////////////////////////
void AsyncTask::
remove() {
  if (_manager != (AsyncTaskManager *)NULL) {
    _manager->remove(this);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::get_wake_time
//       Access: Published
//  Description: If this task has been added to an AsyncTaskManager
//               with a delay in effect, this returns the time at
//               which the task is expected to awaken.  It has no
//               meaning if the task has not yet been added to a
//               queue, or if there was no delay in effect at the time
//               the task was added.
//
//               If the task's status is not S_sleeping, this returns
//               0.0.
////////////////////////////////////////////////////////////////////
double AsyncTask::
get_wake_time() const {
  if (_manager != (AsyncTaskManager *)NULL) {
    MutexHolder holder(_manager->_lock);
    if (_state == S_sleeping) {
      return _wake_time;
    }
  }

  // If it's not on any manager, or it's not sleeping, the wake time
  // is 0.0.
  return 0.0;
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::recalc_wake_time
//       Access: Published
//  Description: If the task is currently sleeping on a task
//               chain, this resets its wake time to the current time
//               + get_delay().  It is as if the task had suddenly
//               returned DS_again.  The task will sleep for its
//               current delay seconds before running again.  This
//               method may therefore be used to make the task wake up
//               sooner or later than it would have otherwise.
//
//               If the task is not already sleeping, this method has
//               no effect.
////////////////////////////////////////////////////////////////////
void AsyncTask::
recalc_wake_time() {
  if (_manager != (AsyncTaskManager *)NULL) {
    MutexHolder holder(_manager->_lock);
    if (_state == S_sleeping) {
      double now = _manager->_clock->get_frame_time();
      _wake_time = now + _delay;
      _start_time = _wake_time;

      make_heap(_chain->_sleeping.begin(), _chain->_sleeping.end(), 
                AsyncTaskChain::AsyncTaskSortWakeTime());
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::get_elapsed_time
//       Access: Published
//  Description: Returns the amount of time that has elapsed since
//               the task was started, according to the task manager's
//               clock.
//
//               It is only valid to call this if the task's status is
//               not S_inactive.
////////////////////////////////////////////////////////////////////
double AsyncTask::
get_elapsed_time() const {
  nassertr(_state != S_inactive, 0.0);
  nassertr(_manager != (AsyncTaskManager *)NULL, 0.0);
  return _manager->_clock->get_frame_time() - _start_time;
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::get_elapsed_frames
//       Access: Published
//  Description: Returns the number of frames that have elapsed since
//               the task was started, according to the task manager's
//               clock.
//
//               It is only valid to call this if the task's status is
//               not S_inactive.
////////////////////////////////////////////////////////////////////
int AsyncTask::
get_elapsed_frames() const {
  nassertr(_state != S_inactive, 0);
  nassertr(_manager != (AsyncTaskManager *)NULL, 0);
  return _manager->_clock->get_frame_count() - _start_frame;
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::set_name
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
void AsyncTask::
set_name(const string &name) {
  if (_manager != (AsyncTaskManager *)NULL) {
    MutexHolder holder(_manager->_lock);
    if (Namable::get_name() != name) {
      // Changing an active task's name requires moving it around on
      // its name index.

      _manager->remove_task_by_name(this);
      Namable::set_name(name);
      _manager->add_task_by_name(this);
    }
  } else {
    // If it hasn't been started anywhere, we can just change the
    // name.
    Namable::set_name(name);
  }

#ifdef DO_PSTATS
  // Update the PStatCollector with the new name.  If the name
  // includes a colon, we stop the collector name there, and don't go
  // further.
  size_t end = name.size();
  size_t colon = name.find(':');
  if (colon != string::npos) {
    end = min(end, colon);
  }

  // If the name ends with a hyphen followed by a string of digits, we
  // strip all that off, for the parent collector, to group related
  // tasks together in the pstats graph.  We still create a child
  // collector that contains the full name, however.
  size_t trimmed = end;
  size_t p = trimmed;
  while (true) {
    while (p > 0 && isdigit(name[p - 1])) {
      --p;
    }
    if (p > 0 && (name[p - 1] == '-' || name[p - 1] == '_')) {
      --p;
      trimmed = p;
    } else {
      p = trimmed;
      break;
    }
  }
  PStatCollector parent(_show_code_pcollector, name.substr(0, trimmed));
  // prevent memory leak
  //_task_pcollector = PStatCollector(parent, name.substr(0, end));
  _task_pcollector = parent;
#endif  // DO_PSTATS
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::get_name_prefix
//       Access: Published
//  Description: Returns the initial part of the name, up to but not
//               including any trailing digits following a hyphen or
//               underscore.
////////////////////////////////////////////////////////////////////
string AsyncTask::
get_name_prefix() const {
  string name = get_name();
  size_t trimmed = name.size();
  size_t p = trimmed;
  while (true) {
    while (p > 0 && isdigit(name[p - 1])) {
      --p;
    }
    if (p > 0 && (name[p - 1] == '-' || name[p - 1] == '_')) {
      --p;
      trimmed = p;
    } else {
      p = trimmed;
      break;
    }
  }

  return name.substr(0, trimmed);
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::set_task_chain
//       Access: Published
//  Description: Specifies the AsyncTaskChain on which this task will
//               be running.  Each task chain runs tasks independently
//               of the others.
////////////////////////////////////////////////////////////////////
void AsyncTask::
set_task_chain(const string &chain_name) {
  if (chain_name != _chain_name) {
    if (_manager != (AsyncTaskManager *)NULL) {
      MutexHolder holder(_manager->_lock);
      if (_state == S_active) {
        // Changing chains on an "active" (i.e. enqueued) task means
        // removing it and re-inserting it into the queue.
        PT(AsyncTask) hold_task = this;
        PT(AsyncTaskManager) manager = _manager;

        AsyncTaskChain *chain_a = manager->do_find_task_chain(_chain_name);
        nassertv(chain_a != (AsyncTaskChain *)NULL);
        chain_a->do_remove(this);
        _chain_name = chain_name;

        jump_to_task_chain(manager);

      } else {
        // If it's sleeping, currently being serviced, or something
        // else, we can just change the chain_name value directly.
        _chain_name = chain_name;
      }
    } else {
      // If it hasn't been started anywhere, we can just change the
      // chain_name value.
      _chain_name = chain_name;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::set_sort
//       Access: Published
//  Description: Specifies a sort value for this task.  Within a given
//               AsyncTaskManager, all of the tasks with a given sort
//               value are guaranteed to be completed before any tasks
//               with a higher sort value are begun.
//
//               To put it another way, two tasks might execute in
//               parallel with each other only if they both have the
//               same sort value.  Tasks with a lower sort value are
//               executed first.
//
//               This is different from the priority, which makes no
//               such exclusion guarantees.
////////////////////////////////////////////////////////////////////
void AsyncTask::
set_sort(int sort) {
  if (sort != _sort) {
    if (_manager != (AsyncTaskManager *)NULL) {
      MutexHolder holder(_manager->_lock);
      if (_state == S_active && _sort >= _chain->_current_sort) {
        // Changing sort on an "active" (i.e. enqueued) task means
        // removing it and re-inserting it into the queue.
        PT(AsyncTask) hold_task = this;
        AsyncTaskChain *chain = _manager->do_find_task_chain(_chain_name);
        nassertv(chain != (AsyncTaskChain *)NULL);
        chain->do_remove(this);
        _sort = sort;
        chain->do_add(this);

      } else {
        // If it's sleeping, currently being serviced, or something
        // else, we can just change the sort value directly.
        _sort = sort;
      }
    } else {
      // If it hasn't been started anywhere, we can just change the
      // sort value.
      _sort = sort;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::set_priority
//       Access: Published
//  Description: Specifies a priority value for this task.  In
//               general, tasks with a higher priority value are
//               executed before tasks with a lower priority value
//               (but only for tasks with the same sort value).
//
//               Unlike the sort value, tasks with different
//               priorities may execute at the same time, if the
//               AsyncTaskManager has more than one thread servicing
//               tasks.
//
//               Also see AsyncTaskChain::set_timeslice_priority(),
//               which changes the meaning of this value.  In the
//               default mode, when the timeslice_priority flag is
//               false, all tasks always run once per epoch,
//               regardless of their priority values (that is, the
//               priority controls the order of the task execution
//               only, not the number of times it runs).  On the other
//               hand, if you set the timeslice_priority flag to true,
//               then changing a task's priority has an effect on the
//               number of times it runs.
////////////////////////////////////////////////////////////////////
void AsyncTask::
set_priority(int priority) {
  if (priority != _priority) {
    if (_manager != (AsyncTaskManager *)NULL) {
      MutexHolder holder(_manager->_lock);
      if (_state == S_active && _sort >= _chain->_current_sort) {
        // Changing priority on an "active" (i.e. enqueued) task means
        // removing it and re-inserting it into the queue.
        PT(AsyncTask) hold_task = this;
        AsyncTaskChain *chain = _manager->do_find_task_chain(_chain_name);
        nassertv(chain != (AsyncTaskChain *)NULL);
        chain->do_remove(this);
        _priority = priority;
        chain->do_add(this);

      } else {
        // If it's sleeping, currently being serviced, or something
        // else, we can just change the priority value directly.
        _priority = priority;
      }
    } else {
      // If it hasn't been started anywhere, we can just change the
      // priority value.
      _priority = priority;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::output
//       Access: Published, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void AsyncTask::
output(ostream &out) const {
  out << get_type();
  if (has_name()) {
    out << " " << get_name();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::jump_to_task_chain
//       Access: Protected
//  Description: Switches the AsyncTask to its new task chain, named
//               by _chain_name.  Called internally only.
////////////////////////////////////////////////////////////////////
void AsyncTask::
jump_to_task_chain(AsyncTaskManager *manager) {
  AsyncTaskChain *chain_b = manager->do_find_task_chain(_chain_name);
  if (chain_b == (AsyncTaskChain *)NULL) {
    task_cat.warning()
      << "Creating implicit AsyncTaskChain " << _chain_name
      << " for " << manager->get_type() << " "
      << manager->get_name() << "\n";
    chain_b = manager->do_make_task_chain(_chain_name);
  }
  chain_b->do_add(this);
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::unlock_and_do_task
//       Access: Protected
//  Description: Called by the AsyncTaskManager to actually run the
//               task.  Assumes the lock is held.  See do_task().
////////////////////////////////////////////////////////////////////
AsyncTask::DoneStatus AsyncTask::
unlock_and_do_task() {
  nassertr(_manager != (AsyncTaskManager *)NULL, DS_done);
  PT(ClockObject) clock = _manager->get_clock();

  // It's important to release the lock while the task is being
  // serviced.
  _manager->_lock.release();
  
  double start = clock->get_real_time();
  _task_pcollector.start();
  DoneStatus status = do_task();
  _task_pcollector.stop();
  double end = clock->get_real_time();

  // Now reacquire the lock (so we can return with the lock held).
  _manager->_lock.acquire();

  _dt = end - start;
  _max_dt = max(_dt, _max_dt);
  _total_dt += _dt;

  _chain->_time_in_frame += _dt;

  return status;
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::is_runnable
//       Access: Protected, Virtual
//  Description: Override this function to return true if the task can
//               be successfully executed, false if it cannot.  Mainly
//               intended as a sanity check when attempting to add the
//               task to a task manager.
//
//               This function is called with the lock held.
////////////////////////////////////////////////////////////////////
bool AsyncTask::
is_runnable() {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::do_task
//       Access: Protected, Virtual
//  Description: Override this function to do something useful for the
//               task.  The return value should be one of:
//
//               DS_done: the task is finished, remove from active and
//               throw the done event.
//
//               DS_cont: the task has more work to do, keep it active
//               and call this function again in the next epoch.
//
//               DS_again: like DS_cont, but next time call the
//               function from the beginning, almost as if it were
//               freshly added to the task manager.  The task's
//               get_start_time() will be reset to now, and its
//               get_elapsed_time() will be reset to 0.  If the task
//               has a set_delay(), it will wait again for that amount
//               of time to elapse before restarting.  Timing
//               accounting, however, is not reset.
//
//               DS_pickup: like DS_cont, but if the task chain has a
//               frame budget and that budget has not yet been met,
//               re-run the task again without waiting for the next
//               frame.  Otherwise, run it next epoch as usual.
//
//               DS_exit: stop the task, and stop the enclosing
//               sequence too.  Outside of a sequence, this is the
//               same as DS_done.
//
//               DS_pause: delay the task for set_delay() seconds,
//               then stop it.  This is only useful within a sequence.
//
//               DS_interrupt: Interrupt the whole AsyncTaskManager.
//               The task will continue again next epoch, as if it had
//               returned DS_cont.
//
//               This function is called with the lock *not* held.
////////////////////////////////////////////////////////////////////
AsyncTask::DoneStatus AsyncTask::
do_task() {
  return DS_done;
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::upon_birth
//       Access: Protected, Virtual
//  Description: Override this function to do something useful when the
//               task has been added to the active queue.
//
//               This function is called with the lock *not* held.
////////////////////////////////////////////////////////////////////
void AsyncTask::
upon_birth(AsyncTaskManager *manager) {
  // Throw a generic add event for the manager.
  string add_name = manager->get_name() + "-addTask";
  PT_Event event = new Event(add_name);
  event->add_parameter(EventParameter(this));
  throw_event(event);
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTask::upon_death
//       Access: Protected, Virtual
//  Description: Override this function to do something useful when the
//               task has been removed from the active queue.  The
//               parameter clean_exit is true if the task has been
//               removed because it exited normally (returning
//               DS_done), or false if it was removed for some other
//               reason (e.g. AsyncTaskManager::remove()).  By the
//               time this method is called, _manager has been
//               cleared, so the parameter manager indicates the
//               original AsyncTaskManager that owned this task.
//
//               The normal behavior is to throw the done_event only
//               if clean_exit is true.
//
//               This function is called with the lock *not* held.
////////////////////////////////////////////////////////////////////
void AsyncTask::
upon_death(AsyncTaskManager *manager, bool clean_exit) {
  if (clean_exit && !_done_event.empty()) {
    PT_Event event = new Event(_done_event);
    event->add_parameter(EventParameter(this));
    throw_event(event);
  }

  // Also throw a generic remove event for the manager.
  if (manager != (AsyncTaskManager *)NULL) {
    string remove_name = manager->get_name() + "-removeTask";
    PT_Event event = new Event(remove_name);
    event->add_parameter(EventParameter(this));
    throw_event(event);
  }
}
