Panda3D
Loading...
Searching...
No Matches
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
21using std::string;
22
23AtomicAdjust::Integer AsyncTask::_next_task_id;
24PStatCollector AsyncTask::_show_code_pcollector("App:Show code");
25TypeHandle AsyncTask::_type_handle;
26
27/**
28 *
29 */
30AsyncTask::
31AsyncTask(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 */
62AsyncTask::
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 */
73remove() {
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 */
104get_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 */
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 */
147get_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 */
160get_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 */
169void AsyncTask::
170set_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 */
225get_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 */
249void AsyncTask::
250set_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 */
292void AsyncTask::
293set_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 */
337void AsyncTask::
338set_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 */
368void AsyncTask::
369output(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 */
380void AsyncTask::
381jump_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 */
397AsyncTask::DoneStatus AsyncTask::
398unlock_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 */
462bool AsyncTask::
463cancel() {
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 */
476bool AsyncTask::
477is_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 */
512AsyncTask::DoneStatus AsyncTask::
513do_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 */
523void AsyncTask::
524upon_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 */
543void AsyncTask::
544upon_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.
bool done() const
Returns true if the future is done or has been cancelled.
Definition asyncFuture.I:29
The AsyncTaskChain is a subset of the AsyncTaskManager.
A class to manage a loose queue of isolated tasks, which can be performed either synchronously (in th...
This class represents a concrete task performed by an AsyncManager.
Definition asyncTask.h:32
int get_elapsed_frames() const
Returns the number of frames that have elapsed since the task was started, according to the task mana...
set_task_chain
Specifies the AsyncTaskChain on which this task will be running.
Definition asyncTask.h:114
double get_elapsed_time() const
Returns the amount of time that has elapsed since the task was started, according to the task manager...
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...
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
set_priority
Specifies a priority value for this task.
Definition asyncTask.h:116
std::string get_name_prefix() const
Returns the initial part of the name, up to but not including any trailing digits following a hyphen ...
void recalc_wake_time()
If the task is currently sleeping on a task chain, this resets its wake time to the current time + ge...
set_sort
Specifies a sort value for this task.
Definition asyncTask.h:115
static Integer compare_and_exchange(Integer &mem, Integer old_value, Integer new_value)
Atomic compare and exchange.
static Pointer compare_and_exchange_ptr(Pointer &mem, Pointer old_value, Pointer new_value)
Atomic compare and exchange.
A ClockObject keeps track of elapsed real time and discrete time.
Definition clockObject.h:58
An optional parameter associated with an event.
A named event, possibly with parameters.
Definition event.h:33
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition mutexHolder.h:25
bool has_name() const
Returns true if the Namable has a nonempty name set, false if the name is empty.
Definition namable.I:44
A lightweight class that represents a single element that may be timed and/or counted via stats.
A thread; that is, a lightweight process.
Definition thread.h:46
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition thread.h:109
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
A base class for things which need to inherit from both TypedObject and from ReferenceCount.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.