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::
126 recalc_wake_time() {
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::
147 get_elapsed_time() const {
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::
160 get_elapsed_frames() const {
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::
225 get_name_prefix() const {
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
409 void *ptr = AtomicAdjust::compare_and_exchange_ptr
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
443 ptr = AtomicAdjust::compare_and_exchange_ptr
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 }
asyncTaskManager.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
asyncTask.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
AsyncFuture::done
bool done() const
Returns true if the future is done or has been cancelled.
Definition: asyncFuture.I:29
AsyncTaskChain
The AsyncTaskChain is a subset of the AsyncTaskManager.
Definition: asyncTaskChain.h:50
AsyncTaskManager
A class to manage a loose queue of isolated tasks, which can be performed either synchronously (in th...
Definition: asyncTaskManager.h:48
AsyncTask
This class represents a concrete task performed by an AsyncManager.
Definition: asyncTask.h:32
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
AsyncTask::set_task_chain
set_task_chain
Specifies the AsyncTaskChain on which this task will be running.
Definition: asyncTask.h:114
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
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
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
AsyncTask::set_priority
set_priority
Specifies a priority value for this task.
Definition: asyncTask.h:116
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::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
AsyncTask::set_sort
set_sort
Specifies a sort value for this task.
Definition: asyncTask.h:115
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
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
ClockObject
A ClockObject keeps track of elapsed real time and discrete time.
Definition: clockObject.h:58
EventParameter
An optional parameter associated with an event.
Definition: eventParameter.h:35
Event
A named event, possibly with parameters.
Definition: event.h:33
MutexHolder
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:25
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
PStatCollector
A lightweight class that represents a single element that may be timed and/or counted via stats.
Definition: pStatCollector.h:43
Thread
A thread; that is, a lightweight process.
Definition: thread.h:46
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
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.
eventParameter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pt_Event.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
throw_event.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.