Panda3D
Loading...
Searching...
No Matches
cInterval.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 cInterval.cxx
10 * @author drose
11 * @date 2002-08-27
12 */
13
14#include "cInterval.h"
15#include "cIntervalManager.h"
16#include "indent.h"
17#include "clockObject.h"
18#include "event.h"
19#include "eventQueue.h"
20#include "pStatTimer.h"
21
22using std::ostream;
23using std::string;
24
25PStatCollector CInterval::_root_pcollector("App:Show code:ivalLoop");
26TypeHandle CInterval::_type_handle;
27
28static inline string
29get_pstats_name(const string &name) {
30 string pname = name;
31 size_t hyphen = pname.find('-');
32 if (hyphen != string::npos) {
33 pname = pname.substr(0, hyphen);
34 }
35 return pname;
36}
37
38/**
39 *
40 */
41CInterval::
42CInterval(const string &name, double duration, bool open_ended) :
43 _state(S_initial),
44 _curr_t(0.0),
45 _name(name),
46 _pname(get_pstats_name(name)),
47 _duration(std::max(duration, 0.0)),
48 _open_ended(open_ended),
49 _dirty(false),
50 _ival_pcollector(_root_pcollector, _pname)
51{
52 _auto_pause = false;
53 _auto_finish = false;
54 _wants_t_callback = false;
55 _last_t_callback = -1.0;
57 _clock_start = 0.0;
58 _start_t = 0.0;
59 _end_t = _duration;
60 _start_t_at_start = true;
61 _end_t_at_end = true;
62 _play_rate = 1.0;
63 _do_loop = false;
64 _loop_count = 0;
65
66 if (interval_cat.is_spam()) {
67 interval_cat.spam()
68 << "Constructing interval " << (void *)this << ", duration = "
69 << _duration << "\n";
70 }
71}
72
73/**
74 *
75 */
76CInterval::
77~CInterval() {
78 if (interval_cat.is_spam()) {
79 interval_cat.spam()
80 << "Destructing interval " << (void *)this << "\n";
81 }
82}
83
84/**
85 * Explicitly sets the time within the interval. Normally, you would use
86 * start() .. finish() to let the time play normally, but this may be used to
87 * set the time to some particular value.
88 */
89void CInterval::
90set_t(double t) {
91 // There doesn't seem to be any reason to clamp this, and it breaks looping
92 // intervals. The interval code should properly handle t values outside the
93 // proper range. t = min(max(t, 0.0), get_duration());
94
95 switch (get_state()) {
96 case S_initial:
98 if (is_playing()) {
100 } else {
102 }
103 break;
104
105 case S_started:
106 // Support modifying t while the interval is playing. We assume
107 // is_playing() will be true in this state.
108 nassertv(is_playing());
110 priv_step(t);
111 setup_resume();
112 break;
113
114 case S_paused:
115 // Support modifying t while the interval is paused. In this case, we
116 // simply step to the new value of t; but this will change the state to
117 // S_started, so we must then change it back to S_paused by hand (because
118 // we're still paused).
119 priv_step(t);
121 break;
122
123 case S_final:
125 if (is_playing()) {
126 setup_resume();
127 } else {
129 }
130 break;
131 }
132}
133
134/**
135 * Starts the interval playing by registering it with the current
136 * CIntervalManager. The interval will play to the end and stop.
137 *
138 * If end_t is less than zero, it indicates the end of the interval.
139 */
141start(double start_t, double end_t, double play_rate) {
142 setup_play(start_t, end_t, play_rate, false);
143 _manager->add_c_interval(this, false);
144}
145
146/**
147 * Starts the interval playing by registering it with the current
148 * CIntervalManager. The interval will play until it is interrupted with
149 * finish() or pause(), looping back to start_t when it reaches end_t.
150 *
151 * If end_t is less than zero, it indicates the end of the interval.
152 */
154loop(double start_t, double end_t, double play_rate) {
155 setup_play(start_t, end_t, play_rate, true);
156 _manager->add_c_interval(this, false);
157}
158
159/**
160 * Stops the interval from playing but leaves it in its current state. It may
161 * later be resumed from this point by calling resume().
162 */
164pause() {
165 if (get_state() == S_started) {
167 }
168 int index = _manager->find_c_interval(this->get_name());
169 if (index >= 0) {
170 _manager->remove_c_interval(index);
171 }
172 return get_t();
173}
174
175/**
176 * Restarts the interval from its current point after a previous call to
177 * pause().
178 */
180resume() {
181 setup_resume();
182 _manager->add_c_interval(this, false);
183}
184
185/**
186 * Restarts the interval from the indicated point after a previous call to
187 * pause().
188 */
190resume(double start_t) {
191 set_t(start_t);
192 setup_resume();
193 _manager->add_c_interval(this, false);
194}
195
196/**
197 * Restarts the interval from the current point after a previous call to
198 * pause() (or a previous play-to-point-and-stop), to play until the indicated
199 * point and then stop.
200 */
202resume_until(double end_t) {
203 setup_resume_until(end_t);
204 _manager->add_c_interval(this, false);
205}
206
207/**
208 * Stops the interval from playing and sets it to its final state.
209 */
211finish() {
212 switch (get_state()) {
213 case S_initial:
214 priv_instant();
215 break;
216
217 case S_final:
218 break;
219
220 default:
222 }
223
224 int index = _manager->find_c_interval(this->get_name());
225 if (index >= 0) {
226 _manager->remove_c_interval(index);
227 }
228}
229
230/**
231 * Pauses the interval, if it is playing, and resets its state to its initial
232 * state, abandoning any state changes already in progress in the middle of
233 * the interval. Calling this is like pausing the interval and discarding it,
234 * creating a new one in its place.
235 */
238 pause();
239
240 _state = S_initial;
241 _curr_t = 0.0;
242}
243
244/**
245 * Returns true if the interval is currently playing, false otherwise.
246 */
247bool CInterval::
248is_playing() const {
249 int index = _manager->find_c_interval(this->get_name());
250 return (index >= 0);
251}
252
253/**
254 * Returns the play rate as set by the last call to start(), loop(), or
255 * set_play_rate().
256 */
257double CInterval::
258get_play_rate() const {
259 return _play_rate;
260}
261
262/**
263 * Changes the play rate of the interval. If the interval is already started,
264 * this changes its speed on-the-fly. Note that since play_rate is a
265 * parameter to start() and loop(), the next call to start() or loop() will
266 * reset this parameter.
267 */
268void CInterval::
269set_play_rate(double play_rate) {
270 if (is_playing()) {
271 pause();
272 _play_rate = play_rate;
273 resume();
274 } else {
275 _play_rate = play_rate;
276 }
277}
278
279/**
280 * Calls the appropriate event function indicated by the EventType.
281 */
283priv_do_event(double t, EventType event) {
284 PStatTimer timer(_ival_pcollector);
285 switch (event) {
286 case ET_initialize:
288 return;
289
290 case ET_instant:
291 priv_instant();
292 return;
293
294 case ET_step:
295 priv_step(t);
296 return;
297
298 case ET_finalize:
300 return;
301
302 case ET_reverse_initialize:
304 return;
305
306 case ET_reverse_instant:
308 return;
309
310 case ET_reverse_finalize:
312 return;
313
314 case ET_interrupt:
316 return;
317 }
318
319 interval_cat.warning()
320 << "Invalid event type: " << (int)event << "\n";
321}
322
323/**
324 * This replaces the first call to priv_step(), and indicates that the
325 * interval has just begun. This may be overridden by derived classes that
326 * need to do some explicit initialization on the first call.
327 */
329priv_initialize(double t) {
330 check_stopped(get_class_type(), "priv_initialize");
331 recompute();
332 _state = S_started;
333 priv_step(t);
334}
335
336/**
337 * This is called in lieu of priv_initialize() .. priv_step() ..
338 * priv_finalize(), when everything is to happen within one frame. The
339 * interval should initialize itself, then leave itself in the final state.
340 */
342priv_instant() {
343 check_stopped(get_class_type(), "priv_instant");
344 recompute();
345 _state = S_started;
347 _state = S_final;
348 interval_done();
349}
350
351/**
352 * Advances the time on the interval. The time may either increase (the
353 * normal case) or decrease (e.g. if the interval is being played by a
354 * slider).
355 */
357priv_step(double t) {
358 check_started(get_class_type(), "priv_step");
359 _state = S_started;
360 _curr_t = t;
361}
362
363/**
364 * This is called to stop an interval, forcing it to whatever state it would
365 * be after it played all the way through. It's generally invoked by
366 * set_final_t().
367 */
370 check_started(get_class_type(), "priv_finalize");
371 double duration = get_duration();
372 priv_step(duration);
373 _state = S_final;
374 interval_done();
375}
376
377/**
378 * Similar to priv_initialize(), but this is called when the interval is being
379 * played backwards; it indicates that the interval should start at the
380 * finishing state and undo any intervening intervals.
381 */
383priv_reverse_initialize(double t) {
384 check_stopped(get_class_type(), "priv_reverse_initialize");
385 recompute();
386 _state = S_started;
387 priv_step(t);
388}
389
390/**
391 * This is called in lieu of priv_reverse_initialize() .. priv_step() ..
392 * priv_reverse_finalize(), when everything is to happen within one frame.
393 * The interval should initialize itself, then leave itself in the initial
394 * state.
395 */
398 check_stopped(get_class_type(), "priv_reverse_instant");
399 recompute();
400 _state = S_started;
401 priv_step(0.0);
402 _state = S_initial;
403}
404
405/**
406 * Called generally following a priv_reverse_initialize(), this indicates the
407 * interval should set itself to the initial state.
408 */
411 check_started(get_class_type(), "priv_reverse_finalize");
412 priv_step(0.0);
413 _state = S_initial;
414}
415
416/**
417 * This is called while the interval is playing to indicate that it is about
418 * to be interrupted; that is, priv_step() will not be called for a length of
419 * time. But the interval should remain in its current state in anticipation
420 * of being eventually restarted when the calls to priv_step() eventually
421 * resume.
422 *
423 * The purpose of this function is to allow self-running intervals like sound
424 * intervals to stop the actual sound playback during the pause.
425 */
428 check_started(get_class_type(), "priv_interrupt");
429 _state = S_paused;
430}
431
432/**
433 *
434 */
435void CInterval::
436output(ostream &out) const {
437 out << get_name();
438 if (get_duration() != 0.0) {
439 out << " dur " << get_duration();
440 }
441}
442
443/**
444 *
445 */
446void CInterval::
447write(ostream &out, int indent_level) const {
448 indent(out, indent_level) << *this << "\n";
449}
450
451/**
452 * Called to prepare the interval for automatic timed playback, e.g. via a
453 * Python task. The interval will be played from start_t to end_t, at a time
454 * factor specified by play_rate. start_t must always be less than end_t
455 * (except for the exception for end_t == -1, below), but if play_rate is
456 * negative the interval will be played backwards.
457 *
458 * Specify end_t of -1 to play the entire interval from start_t.
459 *
460 * Call step_play() repeatedly to execute the interval.
461 */
463setup_play(double start_t, double end_t, double play_rate, bool do_loop) {
464 nassertv(start_t < end_t || end_t < 0.0);
465 nassertv(play_rate != 0.0);
466 PStatTimer timer(_ival_pcollector);
467
468 double duration = get_duration();
469
470 if (start_t <= 0.0) {
471 _start_t = 0.0;
472 _start_t_at_start = true;
473 } else if (start_t > duration) {
474 _start_t = duration;
475 _start_t_at_start = false;
476 } else {
477 _start_t = start_t;
478 _start_t_at_start = false;
479 }
480 if (end_t < 0.0 || end_t >= duration) {
481 _end_t = duration;
482 _end_t_at_end = true;
483 } else {
484 _end_t = end_t;
485 _end_t_at_end = false;
486 }
487
489 _play_rate = play_rate;
490 _do_loop = do_loop;
491 _loop_count = 0;
492}
493
494/**
495 * Called to prepare the interval for restarting at the current point within
496 * the interval after an interruption.
497 */
499setup_resume() {
501 if (_play_rate > 0.0) {
502 _clock_start = now - ((get_t() - _start_t) / _play_rate);
503
504 } else if (_play_rate < 0.0) {
505 _clock_start = now - ((get_t() - _end_t) / _play_rate);
506 }
507 _loop_count = 0;
508}
509
510/**
511 * Called to prepare the interval for restarting from the current point after
512 * a previous call to pause() (or a previous play-to-point-and-stop), to play
513 * until the indicated point and then stop.
514 */
516setup_resume_until(double end_t) {
517 double duration = get_duration();
518
519 if (end_t < 0.0 || end_t >= duration) {
520 _end_t = duration;
521 _end_t_at_end = true;
522 } else {
523 _end_t = end_t;
524 _end_t_at_end = false;
525 }
526
527 setup_resume();
528}
529
530/**
531 * Should be called once per frame to execute the automatic timed playback
532 * begun with setup_play().
533 *
534 * Returns true if the interval should continue, false if it is done and
535 * should stop.
536 */
538step_play() {
539 PStatTimer timer(_ival_pcollector);
541
542 if (_play_rate >= 0.0) {
543 double t = (now - _clock_start) * _play_rate + _start_t;
544
545 if (_end_t_at_end) {
546 _end_t = get_duration();
547 }
548
549 if (t < _end_t) {
550 // In the middle of the interval, not a problem.
551 if (is_stopped()) {
553 } else {
554 priv_step(t);
555 }
556
557 } else {
558 // Past the ending point; time to finalize.
559 if (_end_t_at_end) {
560 // Only finalize if the playback cycle includes the whole interval.
561 if (is_stopped()) {
562 if (get_open_ended() || _loop_count != 0) {
563 priv_instant();
564 }
565 } else {
567 }
568 } else {
569 if (is_stopped()) {
570 priv_initialize(_end_t);
571 } else {
572 priv_step(_end_t);
573 }
574 }
575
576 // Advance the clock for the next loop cycle. We might have to advance
577 // multiple times if we skipped several cycles in the past frame.
578
579 if (_end_t == _start_t) {
580 // If the interval has no length, we loop exactly once each time.
581 _loop_count++;
582
583 } else {
584 // Otherwise, figure out how many loops we need to skip.
585 double time_per_loop = (_end_t - _start_t) / _play_rate;
586 double num_loops = floor((now - _clock_start) / time_per_loop);
587 _loop_count += (int)num_loops;
588 _clock_start += num_loops * time_per_loop;
589 }
590 }
591
592 } else {
593 // Playing backwards.
594 double t = (now - _clock_start) * _play_rate + _end_t;
595
596 if (t >= _start_t) {
597 // In the middle of the interval, not a problem.
598 if (is_stopped()) {
600 } else {
601 priv_step(t);
602 }
603
604 } else {
605 // Past the ending point; time to finalize.
606 if (_start_t_at_start) {
607 // Only finalize if the playback cycle includes the whole interval.
608 if (is_stopped()) {
609 if (get_open_ended() || _loop_count != 0) {
611 }
612 } else {
614 }
615 } else {
616 if (is_stopped()) {
617 priv_reverse_initialize(_start_t);
618 } else {
619 priv_step(_start_t);
620 }
621 }
622
623 // Advance the clock for the next loop cycle. We might have to advance
624 // multiple times if we skipped several cycles in the past frame.
625
626 if (_end_t == _start_t) {
627 // If the interval has no length, we loop exactly once each time.
628 _loop_count++;
629
630 } else {
631 // Otherwise, figure out how many loops we need to skip.
632 double time_per_loop = (_end_t - _start_t) / -_play_rate;
633 double num_loops = floor((now - _clock_start) / time_per_loop);
634 _loop_count += (int)num_loops;
635 _clock_start += num_loops * time_per_loop;
636 }
637 }
638 }
639
640 bool should_continue = (_loop_count == 0 || _do_loop);
641
642 if (!should_continue && _state == S_started) {
644 }
645
646 return should_continue;
647}
648
649/**
650 * Called by a derived class to indicate the interval has been changed
651 * internally and must be recomputed before its duration may be returned.
652 */
654mark_dirty() {
655 if (!_dirty) {
656 _dirty = true;
657 Parents::iterator pi;
658 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
659 (*pi)->mark_dirty();
660 }
661 }
662}
663
664/**
665 * Called internally whenever the interval reaches its final state.
666 */
667void CInterval::
668interval_done() {
669 if (!_done_event.empty()) {
670 _manager->get_event_queue()->queue_event(new Event(_done_event));
671 }
672}
673
674/**
675 * Does whatever processing is necessary to recompute the interval after a
676 * call to mark_dirty() has indicated a recomputation is necessary.
677 */
678void CInterval::
679do_recompute() {
680 _dirty = false;
681}
682
683ostream &
684operator << (ostream &out, CInterval::State state) {
685 switch (state) {
686 case CInterval::S_initial:
687 return out << "initial";
688
689 case CInterval::S_started:
690 return out << "started";
691
692 case CInterval::S_paused:
693 return out << "paused";
694
695 case CInterval::S_final:
696 return out << "final";
697 }
698
699 return out << "**invalid state(" << (int)state << ")**";
700}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int find_c_interval(const std::string &name) const
Returns the index associated with the named interval, if there is such an interval,...
void remove_c_interval(int index)
Removes the indicated interval from the queue immediately.
static CIntervalManager * get_global_ptr()
Returns the pointer to the one global CIntervalManager object.
EventQueue * get_event_queue() const
Returns the custom event queue to be used for throwing done events from intervals as they finish.
int add_c_interval(CInterval *interval, bool external)
Adds the interval to the manager, and returns a unique index for the interval.
is_playing
Returns true if the interval is currently playing, false otherwise.
Definition cInterval.h:137
set_play_rate
Changes the play rate of the interval.
Definition cInterval.h:136
virtual void priv_reverse_finalize()
Called generally following a priv_reverse_initialize(), this indicates the interval should set itself...
void resume()
Restarts the interval from its current point after a previous call to pause().
bool step_play()
Should be called once per frame to execute the automatic timed playback begun with setup_play().
virtual void priv_step(double t)
Advances the time on the interval.
void setup_play(double start_time, double end_time, double play_rate, bool do_loop)
Called to prepare the interval for automatic timed playback, e.g.
void loop(double start_t=0.0, double end_t=-1.0, double play_rate=1.0)
Starts the interval playing by registering it with the current CIntervalManager.
get_name
Returns the interval's name.
Definition cInterval.h:126
set_t
Explicitly sets the time within the interval.
Definition cInterval.h:132
get_duration
Returns the duration of the interval in seconds.
Definition cInterval.h:127
is_stopped
Returns true if the interval is in either its initial or final states (but not in a running or paused...
Definition cInterval.h:130
get_open_ended
Returns the state of the "open_ended" flag.
Definition cInterval.h:128
get_state
Indicates the state the interval believes it is in: whether it has been started, is currently in the ...
Definition cInterval.h:129
virtual void priv_initialize(double t)
This replaces the first call to priv_step(), and indicates that the interval has just begun.
void mark_dirty()
Called by a derived class to indicate the interval has been changed internally and must be recomputed...
virtual void priv_reverse_initialize(double t)
Similar to priv_initialize(), but this is called when the interval is being played backwards; it indi...
get_play_rate
Returns the play rate as set by the last call to start(), loop(), or set_play_rate().
Definition cInterval.h:136
void resume_until(double end_t)
Restarts the interval from the current point after a previous call to pause() (or a previous play-to-...
void priv_do_event(double t, EventType event)
Calls the appropriate event function indicated by the EventType.
virtual void priv_finalize()
This is called to stop an interval, forcing it to whatever state it would be after it played all the ...
get_t
Returns the current time of the interval: the last value of t passed to priv_initialize(),...
Definition cInterval.h:132
void clear_to_initial()
Pauses the interval, if it is playing, and resets its state to its initial state, abandoning any stat...
virtual void priv_reverse_instant()
This is called in lieu of priv_reverse_initialize() .
virtual void priv_interrupt()
This is called while the interval is playing to indicate that it is about to be interrupted; that is,...
void setup_resume()
Called to prepare the interval for restarting at the current point within the interval after an inter...
virtual void priv_instant()
This is called in lieu of priv_initialize() .
void setup_resume_until(double end_t)
Called to prepare the interval for restarting from the current point after a previous call to pause()...
void start(double start_t=0.0, double end_t=-1.0, double play_rate=1.0)
Starts the interval playing by registering it with the current CIntervalManager.
void finish()
Stops the interval from playing and sets it to its final state.
double pause()
Stops the interval from playing but leaves it in its current state.
get_frame_time
Returns the time in seconds as of the last time tick() was called (typically, this will be as of the ...
Definition clockObject.h:91
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
A named event, possibly with parameters.
Definition event.h:33
A lightweight class that represents a single element that may be timed and/or counted via stats.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition pStatTimer.h:30
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
STL namespace.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.