Panda3D
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 
22 using std::ostream;
23 using std::string;
24 
25 PStatCollector CInterval::_root_pcollector("App:Show code:ivalLoop");
26 TypeHandle CInterval::_type_handle;
27 
28 static inline string
29 get_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  */
41 CInterval::
42 CInterval(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  */
76 CInterval::
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  */
89 void CInterval::
90 set_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:
97  priv_initialize(t);
98  if (is_playing()) {
99  setup_resume();
100  } else {
101  priv_interrupt();
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());
109  priv_interrupt();
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);
120  priv_interrupt();
121  break;
122 
123  case S_final:
125  if (is_playing()) {
126  setup_resume();
127  } else {
128  priv_interrupt();
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  */
140 void CInterval::
141 start(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  */
153 void CInterval::
154 loop(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  */
163 double CInterval::
164 pause() {
165  if (get_state() == S_started) {
166  priv_interrupt();
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  */
179 void CInterval::
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  */
189 void CInterval::
190 resume(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  */
201 void CInterval::
202 resume_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  */
210 void CInterval::
212  switch (get_state()) {
213  case S_initial:
214  priv_instant();
215  break;
216 
217  case S_final:
218  break;
219 
220  default:
221  priv_finalize();
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  */
236 void CInterval::
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  */
247 bool CInterval::
248 is_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  */
257 double CInterval::
258 get_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  */
268 void CInterval::
269 set_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  */
282 void CInterval::
283 priv_do_event(double t, EventType event) {
284  PStatTimer timer(_ival_pcollector);
285  switch (event) {
286  case ET_initialize:
287  priv_initialize(t);
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:
299  priv_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:
315  priv_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  */
328 void CInterval::
329 priv_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  */
341 void CInterval::
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  */
356 void CInterval::
357 priv_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  */
368 void CInterval::
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  */
382 void CInterval::
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  */
396 void CInterval::
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  */
409 void CInterval::
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  */
426 void CInterval::
428  check_started(get_class_type(), "priv_interrupt");
429  _state = S_paused;
430 }
431 
432 /**
433  *
434  */
435 void CInterval::
436 output(ostream &out) const {
437  out << get_name();
438  if (get_duration() != 0.0) {
439  out << " dur " << get_duration();
440  }
441 }
442 
443 /**
444  *
445  */
446 void CInterval::
447 write(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  */
462 void CInterval::
463 setup_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 
488  _clock_start = ClockObject::get_global_clock()->get_frame_time();
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  */
498 void CInterval::
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  */
515 void CInterval::
516 setup_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  */
537 bool CInterval::
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()) {
552  priv_initialize(t);
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 {
566  priv_finalize();
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) {
643  priv_interrupt();
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  */
653 void CInterval::
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  */
667 void CInterval::
668 interval_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  */
678 void CInterval::
679 do_recompute() {
680  _dirty = false;
681 }
682 
683 ostream &
684 operator << (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 }
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
virtual void priv_step(double t)
Advances the time on the interval.
Definition: cInterval.cxx:357
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.
Definition: cInterval.cxx:463
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void priv_instant()
This is called in lieu of priv_initialize() .
Definition: cInterval.cxx:342
double pause()
Stops the interval from playing but leaves it in its current state.
Definition: cInterval.cxx:164
is_playing
Returns true if the interval is currently playing, false otherwise.
Definition: cInterval.h:134
void resume_until(double end_t)
Restarts the interval from the current point after a previous call to pause() (or a previous play-to-...
Definition: cInterval.cxx:202
void setup_resume()
Called to prepare the interval for restarting at the current point within the interval after an inter...
Definition: cInterval.cxx:499
set_play_rate
Changes the play rate of the interval.
Definition: cInterval.h:133
get_state
Indicates the state the interval believes it is in: whether it has been started, is currently in the ...
Definition: cInterval.h:126
get_open_ended
Returns the state of the "open_ended" flag.
Definition: cInterval.h:125
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.
Definition: cInterval.cxx:154
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:30
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool step_play()
Should be called once per frame to execute the automatic timed playback begun with setup_play().
Definition: cInterval.cxx:538
virtual void priv_finalize()
This is called to stop an interval, forcing it to whatever state it would be after it played all the ...
Definition: cInterval.cxx:369
get_name
Returns the interval's name.
Definition: cInterval.h:123
virtual void priv_interrupt()
This is called while the interval is playing to indicate that it is about to be interrupted; that is,...
Definition: cInterval.cxx:427
static CIntervalManager * get_global_ptr()
Returns the pointer to the one global CIntervalManager object.
void finish()
Stops the interval from playing and sets it to its final state.
Definition: cInterval.cxx:211
void remove_c_interval(int index)
Removes the indicated interval from the queue immediately.
A lightweight class that represents a single element that may be timed and/or counted via stats.
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:127
get_t
Returns the current time of the interval: the last value of t passed to priv_initialize(),...
Definition: cInterval.h:129
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
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
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.
Definition: cInterval.cxx:141
virtual void priv_reverse_initialize(double t)
Similar to priv_initialize(), but this is called when the interval is being played backwards; it indi...
Definition: cInterval.cxx:383
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void priv_initialize(double t)
This replaces the first call to priv_step(), and indicates that the interval has just begun.
Definition: cInterval.cxx:329
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void setup_resume_until(double end_t)
Called to prepare the interval for restarting from the current point after a previous call to pause()...
Definition: cInterval.cxx:516
int find_c_interval(const std::string &name) const
Returns the index associated with the named interval, if there is such an interval,...
A named event, possibly with parameters.
Definition: event.h:33
void resume()
Restarts the interval from its current point after a previous call to pause().
Definition: cInterval.cxx:180
get_duration
Returns the duration of the interval in seconds.
Definition: cInterval.h:124
virtual void priv_reverse_instant()
This is called in lieu of priv_reverse_initialize() .
Definition: cInterval.cxx:397
set_t
Explicitly sets the time within the interval.
Definition: cInterval.h:129
void priv_do_event(double t, EventType event)
Calls the appropriate event function indicated by the EventType.
Definition: cInterval.cxx:283
void mark_dirty()
Called by a derived class to indicate the interval has been changed internally and must be recomputed...
Definition: cInterval.cxx:654
int add_c_interval(CInterval *interval, bool external)
Adds the interval to the manager, and returns a unique index for the interval.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
virtual void priv_reverse_finalize()
Called generally following a priv_reverse_initialize(), this indicates the interval should set itself...
Definition: cInterval.cxx:410
void clear_to_initial()
Pauses the interval, if it is playing, and resets its state to its initial state, abandoning any stat...
Definition: cInterval.cxx:237
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EventQueue * get_event_queue() const
Returns the custom event queue to be used for throwing done events from intervals as they finish.