Panda3D
asyncTask.cxx
1 // Filename: asyncTask.cxx
2 // Created by: drose (23Aug06)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "asyncTask.h"
16 #include "asyncTaskManager.h"
17 #include "config_event.h"
18 #include "pt_Event.h"
19 #include "throw_event.h"
20 #include "eventParameter.h"
21 
22 AtomicAdjust::Integer AsyncTask::_next_task_id;
23 PStatCollector AsyncTask::_show_code_pcollector("App:Show code");
24 TypeHandle AsyncTask::_type_handle;
25 
26 ////////////////////////////////////////////////////////////////////
27 // Function: AsyncTask::Constructor
28 // Access: Public
29 // Description:
30 ////////////////////////////////////////////////////////////////////
31 AsyncTask::
32 AsyncTask(const string &name) :
33  _chain_name("default"),
34  _delay(0.0),
35  _has_delay(false),
36  _wake_time(0.0),
37  _sort(0),
38  _priority(0),
39  _state(S_inactive),
40  _servicing_thread(NULL),
41  _manager(NULL),
42  _chain(NULL),
43  _start_time(0.0),
44  _start_frame(0),
45  _dt(0.0),
46  _max_dt(0.0),
47  _total_dt(0.0),
48  _num_frames(0)
49 {
50 #ifdef HAVE_PYTHON
51  _python_object = NULL;
52 #endif // HAVE_PYTHON
53  set_name(name);
54 
55  // Carefully copy _next_task_id and increment it so that we get a
56  // unique ID.
57  AtomicAdjust::Integer current_id = _next_task_id;
58  while (AtomicAdjust::compare_and_exchange(_next_task_id, current_id, current_id + 1) != current_id) {
59  current_id = _next_task_id;
60  }
61 
62  _task_id = current_id;
63 }
64 
65 ////////////////////////////////////////////////////////////////////
66 // Function: AsyncTask::Destructor
67 // Access: Public, Virtual
68 // Description:
69 ////////////////////////////////////////////////////////////////////
70 AsyncTask::
71 ~AsyncTask() {
72  nassertv(_state == S_inactive && _manager == NULL && _chain == NULL);
73 #ifdef HAVE_PYTHON
74  set_python_object(NULL);
75 #endif
76 }
77 
78 ////////////////////////////////////////////////////////////////////
79 // Function: AsyncTask::remove
80 // Access: Published
81 // Description: Removes the task from its active manager, if any, and
82 // makes the state S_inactive (or possible
83 // S_servicing_removed). This is a no-op if the state
84 // is already S_inactive.
85 ////////////////////////////////////////////////////////////////////
86 void AsyncTask::
87 remove() {
88  if (_manager != (AsyncTaskManager *)NULL) {
89  _manager->remove(this);
90  }
91 }
92 
93 ////////////////////////////////////////////////////////////////////
94 // Function: AsyncTask::get_wake_time
95 // Access: Published
96 // Description: If this task has been added to an AsyncTaskManager
97 // with a delay in effect, this returns the time at
98 // which the task is expected to awaken. It has no
99 // meaning if the task has not yet been added to a
100 // queue, or if there was no delay in effect at the time
101 // the task was added.
102 //
103 // If the task's status is not S_sleeping, this returns
104 // 0.0.
105 ////////////////////////////////////////////////////////////////////
106 double AsyncTask::
107 get_wake_time() const {
108  if (_manager != (AsyncTaskManager *)NULL) {
109  MutexHolder holder(_manager->_lock);
110  if (_state == S_sleeping) {
111  return _wake_time;
112  }
113  }
114 
115  // If it's not on any manager, or it's not sleeping, the wake time
116  // is 0.0.
117  return 0.0;
118 }
119 
120 ////////////////////////////////////////////////////////////////////
121 // Function: AsyncTask::recalc_wake_time
122 // Access: Published
123 // Description: If the task is currently sleeping on a task
124 // chain, this resets its wake time to the current time
125 // + get_delay(). It is as if the task had suddenly
126 // returned DS_again. The task will sleep for its
127 // current delay seconds before running again. This
128 // method may therefore be used to make the task wake up
129 // sooner or later than it would have otherwise.
130 //
131 // If the task is not already sleeping, this method has
132 // no effect.
133 ////////////////////////////////////////////////////////////////////
134 void AsyncTask::
136  if (_manager != (AsyncTaskManager *)NULL) {
137  MutexHolder holder(_manager->_lock);
138  if (_state == S_sleeping) {
139  double now = _manager->_clock->get_frame_time();
140  _wake_time = now + _delay;
141  _start_time = _wake_time;
142 
143  make_heap(_chain->_sleeping.begin(), _chain->_sleeping.end(),
144  AsyncTaskChain::AsyncTaskSortWakeTime());
145  }
146  }
147 }
148 
149 ////////////////////////////////////////////////////////////////////
150 // Function: AsyncTask::get_elapsed_time
151 // Access: Published
152 // Description: Returns the amount of time that has elapsed since
153 // the task was started, according to the task manager's
154 // clock.
155 //
156 // It is only valid to call this if the task's status is
157 // not S_inactive.
158 ////////////////////////////////////////////////////////////////////
159 double AsyncTask::
161  nassertr(_state != S_inactive, 0.0);
162  nassertr(_manager != (AsyncTaskManager *)NULL, 0.0);
163  return _manager->_clock->get_frame_time() - _start_time;
164 }
165 
166 ////////////////////////////////////////////////////////////////////
167 // Function: AsyncTask::get_elapsed_frames
168 // Access: Published
169 // Description: Returns the number of frames that have elapsed since
170 // the task was started, according to the task manager's
171 // clock.
172 //
173 // It is only valid to call this if the task's status is
174 // not S_inactive.
175 ////////////////////////////////////////////////////////////////////
176 int AsyncTask::
178  nassertr(_state != S_inactive, 0);
179  nassertr(_manager != (AsyncTaskManager *)NULL, 0);
180  return _manager->_clock->get_frame_count() - _start_frame;
181 }
182 
183 ////////////////////////////////////////////////////////////////////
184 // Function: AsyncTask::set_name
185 // Access: Published
186 // Description:
187 ////////////////////////////////////////////////////////////////////
188 void AsyncTask::
189 set_name(const string &name) {
190  if (_manager != (AsyncTaskManager *)NULL) {
191  MutexHolder holder(_manager->_lock);
192  if (Namable::get_name() != name) {
193  // Changing an active task's name requires moving it around on
194  // its name index.
195 
196  _manager->remove_task_by_name(this);
197  Namable::set_name(name);
198  _manager->add_task_by_name(this);
199  }
200  } else {
201  // If it hasn't been started anywhere, we can just change the
202  // name.
203  Namable::set_name(name);
204  }
205 
206 #ifdef DO_PSTATS
207  // Update the PStatCollector with the new name. If the name
208  // includes a colon, we stop the collector name there, and don't go
209  // further.
210  size_t end = name.size();
211  size_t colon = name.find(':');
212  if (colon != string::npos) {
213  end = min(end, colon);
214  }
215 
216  // If the name ends with a hyphen followed by a string of digits, we
217  // strip all that off, for the parent collector, to group related
218  // tasks together in the pstats graph. We still create a child
219  // collector that contains the full name, however.
220  size_t trimmed = end;
221  size_t p = trimmed;
222  while (true) {
223  while (p > 0 && isdigit(name[p - 1])) {
224  --p;
225  }
226  if (p > 0 && (name[p - 1] == '-' || name[p - 1] == '_')) {
227  --p;
228  trimmed = p;
229  } else {
230  p = trimmed;
231  break;
232  }
233  }
234  PStatCollector parent(_show_code_pcollector, name.substr(0, trimmed));
235  // prevent memory leak
236  //_task_pcollector = PStatCollector(parent, name.substr(0, end));
237  _task_pcollector = parent;
238 #endif // DO_PSTATS
239 }
240 
241 ////////////////////////////////////////////////////////////////////
242 // Function: AsyncTask::get_name_prefix
243 // Access: Published
244 // Description: Returns the initial part of the name, up to but not
245 // including any trailing digits following a hyphen or
246 // underscore.
247 ////////////////////////////////////////////////////////////////////
248 string AsyncTask::
250  string name = get_name();
251  size_t trimmed = name.size();
252  size_t p = trimmed;
253  while (true) {
254  while (p > 0 && isdigit(name[p - 1])) {
255  --p;
256  }
257  if (p > 0 && (name[p - 1] == '-' || name[p - 1] == '_')) {
258  --p;
259  trimmed = p;
260  } else {
261  p = trimmed;
262  break;
263  }
264  }
265 
266  return name.substr(0, trimmed);
267 }
268 
269 ////////////////////////////////////////////////////////////////////
270 // Function: AsyncTask::set_task_chain
271 // Access: Published
272 // Description: Specifies the AsyncTaskChain on which this task will
273 // be running. Each task chain runs tasks independently
274 // of the others.
275 ////////////////////////////////////////////////////////////////////
276 void AsyncTask::
277 set_task_chain(const string &chain_name) {
278  if (chain_name != _chain_name) {
279  if (_manager != (AsyncTaskManager *)NULL) {
280  MutexHolder holder(_manager->_lock);
281  if (_state == S_active) {
282  // Changing chains on an "active" (i.e. enqueued) task means
283  // removing it and re-inserting it into the queue.
284  PT(AsyncTask) hold_task = this;
285  PT(AsyncTaskManager) manager = _manager;
286 
287  AsyncTaskChain *chain_a = manager->do_find_task_chain(_chain_name);
288  nassertv(chain_a != (AsyncTaskChain *)NULL);
289  chain_a->do_remove(this);
290  _chain_name = chain_name;
291 
292  jump_to_task_chain(manager);
293 
294  } else {
295  // If it's sleeping, currently being serviced, or something
296  // else, we can just change the chain_name value directly.
297  _chain_name = chain_name;
298  }
299  } else {
300  // If it hasn't been started anywhere, we can just change the
301  // chain_name value.
302  _chain_name = chain_name;
303  }
304  }
305 }
306 
307 ////////////////////////////////////////////////////////////////////
308 // Function: AsyncTask::set_sort
309 // Access: Published
310 // Description: Specifies a sort value for this task. Within a given
311 // AsyncTaskManager, all of the tasks with a given sort
312 // value are guaranteed to be completed before any tasks
313 // with a higher sort value are begun.
314 //
315 // To put it another way, two tasks might execute in
316 // parallel with each other only if they both have the
317 // same sort value. Tasks with a lower sort value are
318 // executed first.
319 //
320 // This is different from the priority, which makes no
321 // such exclusion guarantees.
322 ////////////////////////////////////////////////////////////////////
323 void AsyncTask::
324 set_sort(int sort) {
325  if (sort != _sort) {
326  if (_manager != (AsyncTaskManager *)NULL) {
327  MutexHolder holder(_manager->_lock);
328  if (_state == S_active && _sort >= _chain->_current_sort) {
329  // Changing sort on an "active" (i.e. enqueued) task means
330  // removing it and re-inserting it into the queue.
331  PT(AsyncTask) hold_task = this;
332  AsyncTaskChain *chain = _manager->do_find_task_chain(_chain_name);
333  nassertv(chain != (AsyncTaskChain *)NULL);
334  chain->do_remove(this);
335  _sort = sort;
336  chain->do_add(this);
337 
338  } else {
339  // If it's sleeping, currently being serviced, or something
340  // else, we can just change the sort value directly.
341  _sort = sort;
342  }
343  } else {
344  // If it hasn't been started anywhere, we can just change the
345  // sort value.
346  _sort = sort;
347  }
348  }
349 }
350 
351 ////////////////////////////////////////////////////////////////////
352 // Function: AsyncTask::set_priority
353 // Access: Published
354 // Description: Specifies a priority value for this task. In
355 // general, tasks with a higher priority value are
356 // executed before tasks with a lower priority value
357 // (but only for tasks with the same sort value).
358 //
359 // Unlike the sort value, tasks with different
360 // priorities may execute at the same time, if the
361 // AsyncTaskManager has more than one thread servicing
362 // tasks.
363 //
364 // Also see AsyncTaskChain::set_timeslice_priority(),
365 // which changes the meaning of this value. In the
366 // default mode, when the timeslice_priority flag is
367 // false, all tasks always run once per epoch,
368 // regardless of their priority values (that is, the
369 // priority controls the order of the task execution
370 // only, not the number of times it runs). On the other
371 // hand, if you set the timeslice_priority flag to true,
372 // then changing a task's priority has an effect on the
373 // number of times it runs.
374 ////////////////////////////////////////////////////////////////////
375 void AsyncTask::
376 set_priority(int priority) {
377  if (priority != _priority) {
378  if (_manager != (AsyncTaskManager *)NULL) {
379  MutexHolder holder(_manager->_lock);
380  if (_state == S_active && _sort >= _chain->_current_sort) {
381  // Changing priority on an "active" (i.e. enqueued) task means
382  // removing it and re-inserting it into the queue.
383  PT(AsyncTask) hold_task = this;
384  AsyncTaskChain *chain = _manager->do_find_task_chain(_chain_name);
385  nassertv(chain != (AsyncTaskChain *)NULL);
386  chain->do_remove(this);
387  _priority = priority;
388  chain->do_add(this);
389 
390  } else {
391  // If it's sleeping, currently being serviced, or something
392  // else, we can just change the priority value directly.
393  _priority = priority;
394  }
395  } else {
396  // If it hasn't been started anywhere, we can just change the
397  // priority value.
398  _priority = priority;
399  }
400  }
401 }
402 
403 ////////////////////////////////////////////////////////////////////
404 // Function: AsyncTask::output
405 // Access: Published, Virtual
406 // Description:
407 ////////////////////////////////////////////////////////////////////
408 void AsyncTask::
409 output(ostream &out) const {
410  out << get_type();
411  if (has_name()) {
412  out << " " << get_name();
413  }
414 }
415 
416 ////////////////////////////////////////////////////////////////////
417 // Function: AsyncTask::jump_to_task_chain
418 // Access: Protected
419 // Description: Switches the AsyncTask to its new task chain, named
420 // by _chain_name. Called internally only.
421 ////////////////////////////////////////////////////////////////////
422 void AsyncTask::
423 jump_to_task_chain(AsyncTaskManager *manager) {
424  AsyncTaskChain *chain_b = manager->do_find_task_chain(_chain_name);
425  if (chain_b == (AsyncTaskChain *)NULL) {
426  task_cat.warning()
427  << "Creating implicit AsyncTaskChain " << _chain_name
428  << " for " << manager->get_type() << " "
429  << manager->get_name() << "\n";
430  chain_b = manager->do_make_task_chain(_chain_name);
431  }
432  chain_b->do_add(this);
433 }
434 
435 ////////////////////////////////////////////////////////////////////
436 // Function: AsyncTask::unlock_and_do_task
437 // Access: Protected
438 // Description: Called by the AsyncTaskManager to actually run the
439 // task. Assumes the lock is held. See do_task().
440 ////////////////////////////////////////////////////////////////////
441 AsyncTask::DoneStatus AsyncTask::
442 unlock_and_do_task() {
443  nassertr(_manager != (AsyncTaskManager *)NULL, DS_done);
444  PT(ClockObject) clock = _manager->get_clock();
445 
446  Thread *current_thread = Thread::get_current_thread();
447  record_task(current_thread);
448 
449  // It's important to release the lock while the task is being
450  // serviced.
451  _manager->_lock.release();
452 
453  double start = clock->get_real_time();
454  _task_pcollector.start();
455  DoneStatus status = do_task();
456  _task_pcollector.stop();
457  double end = clock->get_real_time();
458 
459  // Now reacquire the lock (so we can return with the lock held).
460  _manager->_lock.acquire();
461 
462  _dt = end - start;
463  _max_dt = max(_dt, _max_dt);
464  _total_dt += _dt;
465 
466  _chain->_time_in_frame += _dt;
467 
468  clear_task(current_thread);
469 
470  return status;
471 }
472 
473 ////////////////////////////////////////////////////////////////////
474 // Function: AsyncTask::is_runnable
475 // Access: Protected, Virtual
476 // Description: Override this function to return true if the task can
477 // be successfully executed, false if it cannot. Mainly
478 // intended as a sanity check when attempting to add the
479 // task to a task manager.
480 //
481 // This function is called with the lock held.
482 ////////////////////////////////////////////////////////////////////
483 bool AsyncTask::
484 is_runnable() {
485  return true;
486 }
487 
488 ////////////////////////////////////////////////////////////////////
489 // Function: AsyncTask::do_task
490 // Access: Protected, Virtual
491 // Description: Override this function to do something useful for the
492 // task. The return value should be one of:
493 //
494 // DS_done: the task is finished, remove from active and
495 // throw the done event.
496 //
497 // DS_cont: the task has more work to do, keep it active
498 // and call this function again in the next epoch.
499 //
500 // DS_again: like DS_cont, but next time call the
501 // function from the beginning, almost as if it were
502 // freshly added to the task manager. The task's
503 // get_start_time() will be reset to now, and its
504 // get_elapsed_time() will be reset to 0. If the task
505 // has a set_delay(), it will wait again for that amount
506 // of time to elapse before restarting. Timing
507 // accounting, however, is not reset.
508 //
509 // DS_pickup: like DS_cont, but if the task chain has a
510 // frame budget and that budget has not yet been met,
511 // re-run the task again without waiting for the next
512 // frame. Otherwise, run it next epoch as usual.
513 //
514 // DS_exit: stop the task, and stop the enclosing
515 // sequence too. Outside of a sequence, this is the
516 // same as DS_done.
517 //
518 // DS_pause: delay the task for set_delay() seconds,
519 // then stop it. This is only useful within a sequence.
520 //
521 // DS_interrupt: Interrupt the whole AsyncTaskManager.
522 // The task will continue again next epoch, as if it had
523 // returned DS_cont.
524 //
525 // This function is called with the lock *not* held.
526 ////////////////////////////////////////////////////////////////////
527 AsyncTask::DoneStatus AsyncTask::
528 do_task() {
529  return DS_done;
530 }
531 
532 ////////////////////////////////////////////////////////////////////
533 // Function: AsyncTask::upon_birth
534 // Access: Protected, Virtual
535 // Description: Override this function to do something useful when the
536 // task has been added to the active queue.
537 //
538 // This function is called with the lock *not* held.
539 ////////////////////////////////////////////////////////////////////
540 void AsyncTask::
541 upon_birth(AsyncTaskManager *manager) {
542  // Throw a generic add event for the manager.
543  string add_name = manager->get_name() + "-addTask";
544  PT_Event event = new Event(add_name);
545  event->add_parameter(EventParameter(this));
546  throw_event(event);
547 }
548 
549 ////////////////////////////////////////////////////////////////////
550 // Function: AsyncTask::upon_death
551 // Access: Protected, Virtual
552 // Description: Override this function to do something useful when the
553 // task has been removed from the active queue. The
554 // parameter clean_exit is true if the task has been
555 // removed because it exited normally (returning
556 // DS_done), or false if it was removed for some other
557 // reason (e.g. AsyncTaskManager::remove()). By the
558 // time this method is called, _manager has been
559 // cleared, so the parameter manager indicates the
560 // original AsyncTaskManager that owned this task.
561 //
562 // The normal behavior is to throw the done_event only
563 // if clean_exit is true.
564 //
565 // This function is called with the lock *not* held.
566 ////////////////////////////////////////////////////////////////////
567 void AsyncTask::
568 upon_death(AsyncTaskManager *manager, bool clean_exit) {
569  if (clean_exit && !_done_event.empty()) {
570  PT_Event event = new Event(_done_event);
571  event->add_parameter(EventParameter(this));
572  throw_event(event);
573  }
574 
575  // Also throw a generic remove event for the manager.
576  if (manager != (AsyncTaskManager *)NULL) {
577  string remove_name = manager->get_name() + "-removeTask";
578  PT_Event event = new Event(remove_name);
579  event->add_parameter(EventParameter(this));
580  throw_event(event);
581  }
582 }
bool remove(AsyncTask *task)
Removes the indicated task from the active queue.
An optional parameter associated with an event.
double get_wake_time() const
If this task has been added to an AsyncTaskManager with a delay in effect, this returns the time at w...
Definition: asyncTask.cxx:107
A class to manage a loose queue of isolated tasks, which can be performed either synchronously (in th...
void set_sort(int sort)
Specifies a sort value for this task.
Definition: asyncTask.cxx:324
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:29
static Thread * get_current_thread()
Returns a pointer to the currently-executing Thread object.
Definition: thread.I:145
int get_elapsed_frames() const
Returns the number of frames that have elapsed since the task was started, according to the task mana...
Definition: asyncTask.cxx:177
string get_name_prefix() const
Returns the initial part of the name, up to but not including any trailing digits following a hyphen ...
Definition: asyncTask.cxx:249
double get_elapsed_time() const
Returns the amount of time that has elapsed since the task was started, according to the task manager...
Definition: asyncTask.cxx:160
A lightweight class that represents a single element that may be timed and/or counted via stats...
void remove()
Removes the task from its active manager, if any, and makes the state S_inactive (or possible S_servi...
Definition: asyncTask.cxx:87
void acquire() const
Grabs the mutex if it is available.
Definition: mutexDirect.I:70
A ClockObject keeps track of elapsed real time and discrete time.
Definition: clockObject.h:66
The AsyncTaskChain is a subset of the AsyncTaskManager.
void set_task_chain(const string &chain_name)
Specifies the AsyncTaskChain on which this task will be running.
Definition: asyncTask.cxx:277
This class represents a concrete task performed by an AsyncManager.
Definition: asyncTask.h:43
void set_priority(int priority)
Specifies a priority value for this task.
Definition: asyncTask.cxx:376
A thread; that is, a lightweight process.
Definition: thread.h:51
A named event, possibly with parameters.
Definition: event.h:36
ClockObject * get_clock()
Returns the clock pointer used within the AsyncTaskManager.
bool has_name() const
Returns true if the Namable has a nonempty name set, false if the name is empty.
Definition: namable.I:75
void recalc_wake_time()
If the task is currently sleeping on a task chain, this resets its wake time to the current time + ge...
Definition: asyncTask.cxx:135
void release() const
Releases the mutex.
Definition: mutexDirect.I:99
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85
static Integer compare_and_exchange(Integer &mem, Integer old_value, Integer new_value)
Atomic compare and exchange.