open_toontown_panda3d/panda/src/event/asyncTask.cxx

578 lines
21 KiB
C++

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