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