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 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An optional parameter associated with an event.
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
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
A class to manage a loose queue of isolated tasks, which can be performed either synchronously (in th...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
set_sort
Specifies a sort value for this task.
Definition: asyncTask.h:115
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
set_priority
Specifies a priority value for this task.
Definition: asyncTask.h:116
A base class for things which need to inherit from both TypedObject and from ReferenceCount.
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:25
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
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
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
A lightweight class that represents a single element that may be timed and/or counted via stats.
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition: thread.h:109
bool done() const
Returns true if the future is done or has been cancelled.
Definition: asyncFuture.I:29
set_task_chain
Specifies the AsyncTaskChain on which this task will be running.
Definition: asyncTask.h:114
A ClockObject keeps track of elapsed real time and discrete time.
Definition: clockObject.h:58
The AsyncTaskChain is a subset of the AsyncTaskManager.
This class represents a concrete task performed by an AsyncManager.
Definition: asyncTask.h:32
A thread; that is, a lightweight process.
Definition: thread.h:46
A named event, possibly with parameters.
Definition: event.h:33
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool has_name() const
Returns true if the Namable has a nonempty name set, false if the name is empty.
Definition: namable.I:44
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
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static Pointer compare_and_exchange_ptr(Pointer &mem, Pointer old_value, Pointer new_value)
Atomic compare and exchange.
static Integer compare_and_exchange(Integer &mem, Integer old_value, Integer new_value)
Atomic compare and exchange.