1211 lines
34 KiB
C++
1211 lines
34 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file cMetaInterval.cxx
|
|
* @author drose
|
|
* @date 2002-08-27
|
|
*/
|
|
|
|
#include "cMetaInterval.h"
|
|
#include "waitInterval.h"
|
|
#include "config_interval.h"
|
|
#include "indirectLess.h"
|
|
#include "indent.h"
|
|
|
|
#include <algorithm>
|
|
#include <math.h> // for log10()
|
|
#include <stdio.h> // for sprintf()
|
|
|
|
using std::string;
|
|
|
|
TypeHandle CMetaInterval::_type_handle;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
CMetaInterval::
|
|
CMetaInterval(const string &name) :
|
|
CInterval(name, 0.0, true)
|
|
{
|
|
_precision = interval_precision;
|
|
_current_nesting_level = 0;
|
|
_next_event_index = 0;
|
|
_processing_events = false;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
CMetaInterval::
|
|
~CMetaInterval() {
|
|
clear_intervals();
|
|
}
|
|
|
|
/**
|
|
* Resets the list of intervals and prepares for receiving a new list.
|
|
*/
|
|
void CMetaInterval::
|
|
clear_intervals() {
|
|
// Better not do this unless you have serviced all of the outstanding
|
|
// events!
|
|
bool lost_events = false;
|
|
if (!_event_queue.empty()) {
|
|
interval_cat.warning()
|
|
<< "Losing outstanding events for " << *this << "\n";
|
|
_event_queue.clear();
|
|
lost_events = true;
|
|
}
|
|
|
|
clear_events();
|
|
|
|
// Go through all of our nested intervals and remove ourselves as their
|
|
// parent.
|
|
Defs::iterator di;
|
|
for (di = _defs.begin(); di != _defs.end(); ++di) {
|
|
IntervalDef &def = (*di);
|
|
if (def._c_interval != nullptr) {
|
|
CInterval::Parents::iterator pi =
|
|
find(def._c_interval->_parents.begin(),
|
|
def._c_interval->_parents.end(),
|
|
this);
|
|
nassertv(pi != def._c_interval->_parents.end());
|
|
def._c_interval->_parents.erase(pi);
|
|
}
|
|
}
|
|
_defs.clear();
|
|
|
|
_current_nesting_level = 0;
|
|
_next_event_index = 0;
|
|
|
|
#ifndef NDEBUG
|
|
if (verify_intervals) {
|
|
nassertv(!lost_events);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Marks the beginning of a nested level of child intervals. Within the
|
|
* nested level, a RelativeStart time of RS_level_begin refers to the start of
|
|
* the level, and the first interval added within the level is always relative
|
|
* to the start of the level.
|
|
*
|
|
* The return value is the index of the def entry created by this push.
|
|
*/
|
|
int CMetaInterval::
|
|
push_level(const string &name, double rel_time, RelativeStart rel_to) {
|
|
nassertr(_event_queue.empty() && !_processing_events, -1);
|
|
|
|
_defs.push_back(IntervalDef());
|
|
IntervalDef &def = _defs.back();
|
|
def._type = DT_push_level;
|
|
def._ext_name = name;
|
|
def._rel_time = rel_time;
|
|
def._rel_to = rel_to;
|
|
_current_nesting_level++;
|
|
mark_dirty();
|
|
|
|
return (int)_defs.size() - 1;
|
|
}
|
|
|
|
/**
|
|
* Adds a new CInterval to the list. The interval will be played when the
|
|
* indicated time (relative to the given point) has been reached.
|
|
*
|
|
* The return value is the index of the def entry representing the new
|
|
* interval.
|
|
*/
|
|
int CMetaInterval::
|
|
add_c_interval(CInterval *c_interval,
|
|
double rel_time, RelativeStart rel_to) {
|
|
nassertr(_event_queue.empty() && !_processing_events, -1);
|
|
nassertr(c_interval != nullptr, -1);
|
|
|
|
c_interval->_parents.push_back(this);
|
|
c_interval->_ival_pcollector = PStatCollector(_ival_pcollector, c_interval->_pname);
|
|
_defs.push_back(IntervalDef());
|
|
IntervalDef &def = _defs.back();
|
|
def._type = DT_c_interval;
|
|
def._c_interval = c_interval;
|
|
def._rel_time = rel_time;
|
|
def._rel_to = rel_to;
|
|
mark_dirty();
|
|
|
|
return (int)_defs.size() - 1;
|
|
}
|
|
|
|
/**
|
|
* Adds a new external interval to the list. This represents some object in
|
|
* the external scripting language that has properties similar to a CInterval
|
|
* (for instance, a Python Interval object).
|
|
*
|
|
* The CMetaInterval object cannot play this external interval directly, but
|
|
* it records a placeholder for it and will ask the scripting language to play
|
|
* it when it is time, via is_event_ready() and related methods.
|
|
*
|
|
* The ext_index number itself is simply a handle that the scripting language
|
|
* makes up and associates with its interval object somehow. The
|
|
* CMetaInterval object does not attempt to interpret this value.
|
|
*
|
|
* The return value is the index of the def entry representing the new
|
|
* interval.
|
|
*/
|
|
int CMetaInterval::
|
|
add_ext_index(int ext_index, const string &name, double duration,
|
|
bool open_ended,
|
|
double rel_time, RelativeStart rel_to) {
|
|
nassertr(_event_queue.empty() && !_processing_events, -1);
|
|
|
|
_defs.push_back(IntervalDef());
|
|
IntervalDef &def = _defs.back();
|
|
def._type = DT_ext_index;
|
|
def._ext_index = ext_index;
|
|
def._ext_name = name;
|
|
def._ext_duration = duration;
|
|
def._ext_open_ended = open_ended;
|
|
def._rel_time = rel_time;
|
|
def._rel_to = rel_to;
|
|
mark_dirty();
|
|
|
|
return (int)_defs.size() - 1;
|
|
}
|
|
|
|
/**
|
|
* Finishes a level marked by a previous call to push_level(), and returns to
|
|
* the previous level.
|
|
*
|
|
* If the duration is not negative, it represents a phony duration to assign
|
|
* to the level, for the purposes of sequencing later intervals. Otherwise,
|
|
* the level's duration is computed based on the intervals within the level.
|
|
*/
|
|
int CMetaInterval::
|
|
pop_level(double duration) {
|
|
nassertr(_event_queue.empty() && !_processing_events, -1);
|
|
nassertr(_current_nesting_level > 0, -1);
|
|
|
|
_defs.push_back(IntervalDef());
|
|
IntervalDef &def = _defs.back();
|
|
def._type = DT_pop_level;
|
|
def._ext_duration = duration;
|
|
_current_nesting_level--;
|
|
mark_dirty();
|
|
|
|
return (int)_defs.size() - 1;
|
|
}
|
|
|
|
/**
|
|
* Adjusts the start time of the child interval with the given name, if found.
|
|
* This may be either a C++ interval added via add_c_interval(), or an
|
|
* external interval added via add_ext_index(); the name must match exactly.
|
|
*
|
|
* If the interval is found, its start time is adjusted, and all subsequent
|
|
* intervals are adjusting accordingly, and true is returned. If a matching
|
|
* interval is not found, nothing is changed and false is returned.
|
|
*/
|
|
bool CMetaInterval::
|
|
set_interval_start_time(const string &name, double rel_time,
|
|
CMetaInterval::RelativeStart rel_to) {
|
|
nassertr(_event_queue.empty() && !_processing_events, false);
|
|
Defs::iterator di;
|
|
for (di = _defs.begin(); di != _defs.end(); ++di) {
|
|
IntervalDef &def = (*di);
|
|
|
|
bool match = false;
|
|
switch (def._type) {
|
|
case DT_c_interval:
|
|
match = (def._c_interval->get_name() == name);
|
|
break;
|
|
|
|
case DT_ext_index:
|
|
match = (def._ext_name == name);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (match) {
|
|
// Here's the interval.
|
|
def._rel_time = rel_time;
|
|
def._rel_to = rel_to;
|
|
mark_dirty();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the actual start time, relative to the beginning of the interval,
|
|
* of the child interval with the given name, if found, or -1 if the interval
|
|
* is not found.
|
|
*/
|
|
double CMetaInterval::
|
|
get_interval_start_time(const string &name) const {
|
|
recompute();
|
|
Defs::const_iterator di;
|
|
for (di = _defs.begin(); di != _defs.end(); ++di) {
|
|
const IntervalDef &def = (*di);
|
|
|
|
bool match = false;
|
|
switch (def._type) {
|
|
case DT_c_interval:
|
|
match = (def._c_interval->get_name() == name);
|
|
break;
|
|
|
|
case DT_ext_index:
|
|
match = (def._ext_name == name);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (match) {
|
|
// Here's the interval.
|
|
return int_to_double_time(def._actual_begin_time);
|
|
}
|
|
}
|
|
|
|
return -1.0;
|
|
}
|
|
|
|
/**
|
|
* Returns the actual end time, relative to the beginning of the interval, of
|
|
* the child interval with the given name, if found, or -1 if the interval is
|
|
* not found.
|
|
*/
|
|
double CMetaInterval::
|
|
get_interval_end_time(const string &name) const {
|
|
recompute();
|
|
Defs::const_iterator di;
|
|
for (di = _defs.begin(); di != _defs.end(); ++di) {
|
|
const IntervalDef &def = (*di);
|
|
|
|
bool match = false;
|
|
double duration = 0.0;
|
|
switch (def._type) {
|
|
case DT_c_interval:
|
|
duration = def._c_interval->get_duration();
|
|
match = (def._c_interval->get_name() == name);
|
|
break;
|
|
|
|
case DT_ext_index:
|
|
duration = def._ext_duration;
|
|
match = (def._ext_name == name);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (match) {
|
|
// Here's the interval.
|
|
return int_to_double_time(def._actual_begin_time) + duration;
|
|
}
|
|
}
|
|
|
|
return -1.0;
|
|
}
|
|
|
|
/**
|
|
* This replaces the first call to priv_step(), and indicates that the
|
|
* interval has just begun. This may be overridden by derived classes that
|
|
* need to do some explicit initialization on the first call.
|
|
*/
|
|
void CMetaInterval::
|
|
priv_initialize(double t) {
|
|
if (_processing_events) {
|
|
enqueue_self_event(ET_initialize, t);
|
|
return;
|
|
}
|
|
|
|
check_stopped(get_class_type(), "priv_initialize");
|
|
// It may be tempting to flush the event_queue here, but don't do it. Those
|
|
// are events that must still be serviced from some previous interval
|
|
// operation. Throwing them away would be a mistake.
|
|
|
|
recompute();
|
|
_next_event_index = 0;
|
|
_active.clear();
|
|
|
|
int now = double_to_int_time(t);
|
|
|
|
/*
|
|
// One special case: if we step to t == 0.0, it really means to the very
|
|
// beginning of the interval, *before* any events that occurred at time 0.
|
|
// (Most of the time, stepping to a particular time means *after* any events
|
|
// that occurred at that time.)
|
|
if (t == 0.0) {
|
|
now = -1;
|
|
}
|
|
*/
|
|
|
|
// Now look for events from the beginning up to the current time.
|
|
_processing_events = true;
|
|
ActiveEvents new_active;
|
|
while (_next_event_index < _events.size() &&
|
|
_events[_next_event_index]->_time <= now) {
|
|
PlaybackEvent *event = _events[_next_event_index];
|
|
_next_event_index++;
|
|
|
|
// Do the indicated event.
|
|
do_event_forward(event, new_active, true);
|
|
}
|
|
finish_events_forward(now, new_active);
|
|
_processing_events = false;
|
|
|
|
_curr_t = t;
|
|
_state = S_started;
|
|
}
|
|
|
|
/**
|
|
* This is called in lieu of priv_initialize() .. priv_step() ..
|
|
* priv_finalize(), when everything is to happen within one frame. The
|
|
* interval should initialize itself, then leave itself in the final state.
|
|
*/
|
|
void CMetaInterval::
|
|
priv_instant() {
|
|
if (_processing_events) {
|
|
enqueue_self_event(ET_instant);
|
|
return;
|
|
}
|
|
|
|
check_stopped(get_class_type(), "priv_instant");
|
|
recompute();
|
|
_active.clear();
|
|
|
|
// Apply all of the events. This just means we invoke "instant" for any end
|
|
// or instant event, ignoring the begin events.
|
|
_processing_events = true;
|
|
PlaybackEvents::iterator ei;
|
|
for (ei = _events.begin(); ei != _events.end(); ++ei) {
|
|
PlaybackEvent *event = (*ei);
|
|
if (event->_type != PET_begin) {
|
|
enqueue_event(event->_n, ET_instant, true, 0);
|
|
}
|
|
}
|
|
_processing_events = false;
|
|
|
|
_next_event_index = _events.size();
|
|
_curr_t = get_duration();
|
|
_state = S_final;
|
|
|
|
if (_event_queue.empty()) {
|
|
interval_done();
|
|
} else {
|
|
enqueue_done_event();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Advances the time on the interval. The time may either increase (the
|
|
* normal case) or decrease (e.g. if the interval is being played by a
|
|
* slider).
|
|
*/
|
|
void CMetaInterval::
|
|
priv_step(double t) {
|
|
if (_processing_events) {
|
|
enqueue_self_event(ET_step, t);
|
|
return;
|
|
}
|
|
|
|
check_started(get_class_type(), "priv_step");
|
|
int now = double_to_int_time(t);
|
|
|
|
/*
|
|
// One special case: if we step to t == 0.0, it really means to the very
|
|
// beginning of the interval, *before* any events that occurred at time 0.
|
|
// (Most of the time, stepping to a particular time means *after* any events
|
|
// that occurred at that time.)
|
|
if (t == 0.0) {
|
|
now = -1;
|
|
}
|
|
*/
|
|
|
|
// Now look for events between the last time we ran and the current time.
|
|
|
|
_processing_events = true;
|
|
if (_next_event_index < _events.size() &&
|
|
_events[_next_event_index]->_time <= now) {
|
|
// The normal case: time is increasing.
|
|
ActiveEvents new_active;
|
|
while (_next_event_index < _events.size() &&
|
|
_events[_next_event_index]->_time <= now) {
|
|
PlaybackEvent *event = _events[_next_event_index];
|
|
_next_event_index++;
|
|
|
|
// Do the indicated event.
|
|
do_event_forward(event, new_active, false);
|
|
}
|
|
|
|
finish_events_forward(now, new_active);
|
|
|
|
} else {
|
|
// A less usual case: time is decreasing.
|
|
ActiveEvents new_active;
|
|
while (_next_event_index > 0 &&
|
|
_events[_next_event_index - 1]->_time > now) {
|
|
_next_event_index--;
|
|
PlaybackEvent *event = _events[_next_event_index];
|
|
|
|
do_event_reverse(event, new_active, false);
|
|
}
|
|
|
|
finish_events_reverse(now, new_active);
|
|
}
|
|
_processing_events = false;
|
|
|
|
_curr_t = t;
|
|
_state = S_started;
|
|
}
|
|
|
|
/**
|
|
* This is called when an interval is interrupted. It should advance the time
|
|
* as if priv_step() were called, and also perform whatever cleanup might be
|
|
* required.
|
|
*/
|
|
void CMetaInterval::
|
|
priv_finalize() {
|
|
if (_processing_events) {
|
|
enqueue_self_event(ET_finalize);
|
|
return;
|
|
}
|
|
|
|
double duration = get_duration();
|
|
if (_state == S_initial) {
|
|
priv_initialize(duration);
|
|
}
|
|
|
|
// Do all remaining events.
|
|
_processing_events = true;
|
|
ActiveEvents new_active;
|
|
while (_next_event_index < _events.size()) {
|
|
PlaybackEvent *event = _events[_next_event_index];
|
|
_next_event_index++;
|
|
|
|
// Do the indicated event.
|
|
do_event_forward(event, new_active, true);
|
|
}
|
|
finish_events_forward(double_to_int_time(duration), new_active);
|
|
_processing_events = false;
|
|
|
|
_curr_t = duration;
|
|
_state = S_final;
|
|
|
|
if (_event_queue.empty()) {
|
|
interval_done();
|
|
} else {
|
|
enqueue_done_event();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Similar to priv_initialize(), but this is called when the interval is being
|
|
* played backwards; it indicates that the interval should start at the
|
|
* finishing state and undo any intervening intervals.
|
|
*/
|
|
void CMetaInterval::
|
|
priv_reverse_initialize(double t) {
|
|
if (_processing_events) {
|
|
enqueue_self_event(ET_reverse_initialize, t);
|
|
return;
|
|
}
|
|
|
|
check_stopped(get_class_type(), "priv_reverse_initialize");
|
|
// It may be tempting to flush the event_queue here, but don't do it. Those
|
|
// are events that must still be serviced from some previous interval
|
|
// operation. Throwing them away would be a mistake.
|
|
|
|
recompute();
|
|
_next_event_index = _events.size();
|
|
_active.clear();
|
|
|
|
int now = double_to_int_time(t);
|
|
|
|
/*
|
|
// One special case: if we step to t == 0.0, it really means to the very
|
|
// beginning of the interval, *before* any events that occurred at time 0.
|
|
// (Most of the time, stepping to a particular time means *after* any events
|
|
// that occurred at that time.)
|
|
if (t == 0.0) {
|
|
now = -1;
|
|
}
|
|
*/
|
|
|
|
// Now look for events from the end down to the current time.
|
|
_processing_events = true;
|
|
ActiveEvents new_active;
|
|
while (_next_event_index > 0 &&
|
|
_events[_next_event_index - 1]->_time > now) {
|
|
_next_event_index--;
|
|
PlaybackEvent *event = _events[_next_event_index];
|
|
|
|
// Do the indicated event.
|
|
do_event_reverse(event, new_active, true);
|
|
}
|
|
finish_events_reverse(now, new_active);
|
|
_processing_events = false;
|
|
|
|
_curr_t = t;
|
|
_state = S_started;
|
|
}
|
|
|
|
/**
|
|
* This is called in lieu of priv_reverse_initialize() .. priv_step() ..
|
|
* priv_reverse_finalize(), when everything is to happen within one frame.
|
|
* The interval should initialize itself, then leave itself in the initial
|
|
* state.
|
|
*/
|
|
void CMetaInterval::
|
|
priv_reverse_instant() {
|
|
if (_processing_events) {
|
|
enqueue_self_event(ET_reverse_instant);
|
|
return;
|
|
}
|
|
|
|
check_stopped(get_class_type(), "priv_reverse_instant");
|
|
recompute();
|
|
_active.clear();
|
|
|
|
// Apply all of the events. This just means we invoke "instant" for any end
|
|
// or instant event, ignoring the begin events.
|
|
_processing_events = true;
|
|
PlaybackEvents::reverse_iterator ei;
|
|
for (ei = _events.rbegin(); ei != _events.rend(); ++ei) {
|
|
PlaybackEvent *event = (*ei);
|
|
if (event->_type != PET_begin) {
|
|
enqueue_event(event->_n, ET_reverse_instant, true, 0);
|
|
}
|
|
}
|
|
_processing_events = false;
|
|
|
|
_next_event_index = 0;
|
|
_curr_t = 0.0;
|
|
_state = S_initial;
|
|
}
|
|
|
|
/**
|
|
* Called generally following a priv_reverse_initialize(), this indicates the
|
|
* interval should set itself to the initial state.
|
|
*/
|
|
void CMetaInterval::
|
|
priv_reverse_finalize() {
|
|
if (_processing_events) {
|
|
enqueue_self_event(ET_reverse_finalize);
|
|
return;
|
|
}
|
|
|
|
if (_state == S_initial) {
|
|
priv_initialize(0.0);
|
|
}
|
|
|
|
// Do all remaining events at the beginning.
|
|
_processing_events = true;
|
|
ActiveEvents new_active;
|
|
|
|
while (_next_event_index > 0) {
|
|
_next_event_index--;
|
|
PlaybackEvent *event = _events[_next_event_index];
|
|
|
|
do_event_reverse(event, new_active, true);
|
|
}
|
|
finish_events_reverse(0, new_active);
|
|
_processing_events = false;
|
|
|
|
_curr_t = 0.0;
|
|
_state = S_initial;
|
|
}
|
|
|
|
/**
|
|
* This is called while the interval is playing to indicate that it is about
|
|
* to be interrupted; that is, priv_step() will not be called for a length of
|
|
* time. But the interval should remain in its current state in anticipation
|
|
* of being eventually restarted when the calls to priv_step() eventually
|
|
* resume.
|
|
*
|
|
* The purpose of this function is to allow self-running intervals like sound
|
|
* intervals to stop the actual sound playback during the pause.
|
|
*/
|
|
void CMetaInterval::
|
|
priv_interrupt() {
|
|
if (_processing_events) {
|
|
enqueue_self_event(ET_interrupt);
|
|
return;
|
|
}
|
|
|
|
_processing_events = true;
|
|
ActiveEvents::iterator ai;
|
|
for (ai = _active.begin(); ai != _active.end(); ++ai) {
|
|
PlaybackEvent *event = (*ai);
|
|
enqueue_event(event->_n, ET_interrupt, false);
|
|
}
|
|
_processing_events = false;
|
|
|
|
if (_state == S_started) {
|
|
_state = S_paused;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Acknowledges that the external interval on the top of the queue has been
|
|
* extracted, and is about to be serviced by the scripting language. This
|
|
* prepares the interval so the next call to is_event_ready() will return
|
|
* information about the next external interval on the queue, if any.
|
|
*/
|
|
void CMetaInterval::
|
|
pop_event() {
|
|
#ifndef NDEBUG
|
|
nassertv(!_event_queue.empty());
|
|
const EventQueueEntry &entry = _event_queue.front();
|
|
const IntervalDef &def = _defs[entry._n];
|
|
nassertv(def._type == DT_ext_index);
|
|
#endif
|
|
_event_queue.pop_front();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void CMetaInterval::
|
|
write(std::ostream &out, int indent_level) const {
|
|
recompute();
|
|
|
|
// How many digits of precision should we output for time?
|
|
int num_decimals = (int)ceil(log10(_precision));
|
|
int total_digits = num_decimals + 4;
|
|
static const int max_digits = 32; // totally arbitrary
|
|
nassertv(total_digits <= max_digits);
|
|
char format_str[16];
|
|
sprintf(format_str, "%%%d.%df", total_digits, num_decimals);
|
|
|
|
indent(out, indent_level) << get_name() << ":\n";
|
|
|
|
int extra_indent_level = 1;
|
|
Defs::const_iterator di;
|
|
for (di = _defs.begin(); di != _defs.end(); ++di) {
|
|
const IntervalDef &def = (*di);
|
|
char time_str[max_digits + 1];
|
|
sprintf(time_str, format_str, int_to_double_time(def._actual_begin_time));
|
|
indent(out, indent_level) << time_str;
|
|
|
|
write_event_desc(out, def, extra_indent_level);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Outputs a list of all events in the order in which they occur.
|
|
*/
|
|
void CMetaInterval::
|
|
timeline(std::ostream &out) const {
|
|
recompute();
|
|
|
|
// How many digits of precision should we output for time?
|
|
int num_decimals = (int)ceil(log10(_precision));
|
|
int total_digits = num_decimals + 4;
|
|
static const int max_digits = 32; // totally arbitrary
|
|
nassertv(total_digits <= max_digits);
|
|
char format_str[16];
|
|
sprintf(format_str, "%%%d.%df", total_digits, num_decimals);
|
|
|
|
int extra_indent_level = 0;
|
|
PlaybackEvents::const_iterator ei;
|
|
for (ei = _events.begin(); ei != _events.end(); ++ei) {
|
|
const PlaybackEvent *event = (*ei);
|
|
|
|
char time_str[max_digits + 1];
|
|
sprintf(time_str, format_str, int_to_double_time(event->_time));
|
|
out << time_str;
|
|
|
|
switch (event->_type) {
|
|
case PET_begin:
|
|
out << " begin ";
|
|
break;
|
|
case PET_end:
|
|
out << " end ";
|
|
break;
|
|
case PET_instant:
|
|
out << " instant ";
|
|
break;
|
|
}
|
|
|
|
int n = event->_n;
|
|
nassertv(n >= 0 && n < (int)_defs.size());
|
|
const IntervalDef &def = _defs[n];
|
|
|
|
write_event_desc(out, def, extra_indent_level);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recomputes all of the events (and the duration) according to the set of
|
|
* interval defs.
|
|
*/
|
|
void CMetaInterval::
|
|
do_recompute() {
|
|
_dirty = false;
|
|
clear_events();
|
|
|
|
int n = recompute_level(0, 0, _end_time);
|
|
|
|
if (n != (int)_defs.size()) {
|
|
interval_cat.warning()
|
|
<< "CMetaInterval pushes don't match pops.\n";
|
|
}
|
|
|
|
// We do a stable_sort() to guarantee ordering of events that have the same
|
|
// start time. These must be invoked in the order in which they appear.
|
|
std::stable_sort(_events.begin(), _events.end(), IndirectLess<PlaybackEvent>());
|
|
_duration = int_to_double_time(_end_time);
|
|
}
|
|
|
|
/**
|
|
* Removes all entries from the _events list.
|
|
*/
|
|
void CMetaInterval::
|
|
clear_events() {
|
|
PlaybackEvents::iterator ei;
|
|
for (ei = _events.begin(); ei != _events.end(); ++ei) {
|
|
PlaybackEvent *event = (*ei);
|
|
delete event;
|
|
}
|
|
_events.clear();
|
|
_active.clear();
|
|
}
|
|
|
|
/**
|
|
* Process a single event in the interval, moving forwards in time. If the
|
|
* event represents a new begin, adds it to the new_active list; if it is an
|
|
* end, finalizes it.
|
|
*
|
|
* If is_initial is true, it is as if we are in initialize or finalize:
|
|
* instant events will be invoked only if they are marked open_ended.
|
|
*/
|
|
void CMetaInterval::
|
|
do_event_forward(CMetaInterval::PlaybackEvent *event,
|
|
CMetaInterval::ActiveEvents &new_active, bool is_initial) {
|
|
switch (event->_type) {
|
|
case PET_begin:
|
|
nassertv(event->_begin_event == event);
|
|
new_active.push_back(event);
|
|
break;
|
|
|
|
case PET_end:
|
|
{
|
|
// Erase the event from either the new active or the current active
|
|
// lists.
|
|
ActiveEvents::iterator ai;
|
|
ai = find(new_active.begin(), new_active.end(), event->_begin_event);
|
|
if (ai != new_active.end()) {
|
|
new_active.erase(ai);
|
|
// This interval was new this frame; we must invoke it as an instant
|
|
// event.
|
|
enqueue_event(event->_n, ET_instant, is_initial);
|
|
|
|
} else {
|
|
ai = find(_active.begin(), _active.end(), event->_begin_event);
|
|
if (ai != _active.end()) {
|
|
_active.erase(ai);
|
|
enqueue_event(event->_n, ET_finalize, is_initial);
|
|
|
|
} else {
|
|
// Hmm, this event wasn't on either list. Maybe there was a start
|
|
// event on the list whose time was less than 0.
|
|
interval_cat.error()
|
|
<< "Event " << event->_begin_event->_n << " not on active list.\n";
|
|
nassertv(false);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PET_instant:
|
|
nassertv(event->_begin_event == event);
|
|
enqueue_event(event->_n, ET_instant, is_initial);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* After walking through the event list and adding a bunch of new events to
|
|
* new_active, finished up by calling priv_step() on all of the events still
|
|
* in _active and priv_initialize() on all the events in new_active, then
|
|
* copying the events from new_active to active.
|
|
*/
|
|
void CMetaInterval::
|
|
finish_events_forward(int now, CMetaInterval::ActiveEvents &new_active) {
|
|
// Do whatever's still active.
|
|
ActiveEvents::iterator ai;
|
|
for (ai = _active.begin(); ai != _active.end(); ++ai) {
|
|
PlaybackEvent *event = (*ai);
|
|
enqueue_event(event->_n, ET_step, false, now - event->_time);
|
|
}
|
|
|
|
// Initialize whatever new intervals we came across.
|
|
for (ai = new_active.begin(); ai != new_active.end(); ++ai) {
|
|
PlaybackEvent *event = (*ai);
|
|
enqueue_event(event->_n, ET_initialize, false, now - event->_time);
|
|
_active.push_back(event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a single event in the interval, moving backwards in time. This
|
|
* undoes the indicated event. If the event represents a new begin, adds it
|
|
* to the new_active list; if it is an end, finalizes it.
|
|
*
|
|
* If is_initial is true, it is as if we are in reverse_initialize or
|
|
* reverse_finalize: instant events will be invoked only if they are marked
|
|
* open_ended.
|
|
*/
|
|
void CMetaInterval::
|
|
do_event_reverse(CMetaInterval::PlaybackEvent *event,
|
|
CMetaInterval::ActiveEvents &new_active, bool is_initial) {
|
|
// Undo the indicated event.
|
|
switch (event->_type) {
|
|
case PET_begin:
|
|
{
|
|
nassertv(event->_begin_event == event);
|
|
// Erase the event from either the new active or the current active
|
|
// lists.
|
|
ActiveEvents::iterator ai;
|
|
ai = find(new_active.begin(), new_active.end(), event);
|
|
if (ai != new_active.end()) {
|
|
new_active.erase(ai);
|
|
// This interval was new this frame; we invoke it as an instant event.
|
|
enqueue_event(event->_n, ET_reverse_instant, is_initial);
|
|
|
|
} else {
|
|
ai = find(_active.begin(), _active.end(), event);
|
|
if (ai != _active.end()) {
|
|
_active.erase(ai);
|
|
enqueue_event(event->_n, ET_reverse_finalize, is_initial);
|
|
|
|
} else {
|
|
// Hmm, this event wasn't on either list. Maybe there was a stop
|
|
// event on the list whose time was greater than the total, somehow.
|
|
interval_cat.error()
|
|
<< "Event " << event->_n << " not on active list.\n";
|
|
nassertv(false);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PET_end:
|
|
new_active.push_front(event->_begin_event);
|
|
break;
|
|
|
|
case PET_instant:
|
|
nassertv(event->_begin_event == event);
|
|
enqueue_event(event->_n, ET_reverse_instant, is_initial);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* After walking through the event list and adding a bunch of new events to
|
|
* new_active, finishes up by calling priv_step() on all of the events still
|
|
* in _active and priv_reverse_initialize() on all the events in new_active,
|
|
* then copying the events from new_active to active.
|
|
*/
|
|
void CMetaInterval::
|
|
finish_events_reverse(int now, CMetaInterval::ActiveEvents &new_active) {
|
|
// Do whatever's still active.
|
|
ActiveEvents::iterator ai;
|
|
for (ai = _active.begin(); ai != _active.end(); ++ai) {
|
|
PlaybackEvent *event = (*ai);
|
|
enqueue_event(event->_n, ET_step, false, now - event->_time);
|
|
}
|
|
|
|
// Initialize whatever new intervals we came across.
|
|
for (ai = new_active.begin(); ai != new_active.end(); ++ai) {
|
|
PlaybackEvent *event = (*ai);
|
|
enqueue_event(event->_n, ET_reverse_initialize, false, now - event->_time);
|
|
_active.push_front(event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enqueues the indicated interval for invocation after we have finished
|
|
* scanning for events that need processing this frame.
|
|
*
|
|
* is_initial is only relevant for event types ET_instant or
|
|
* ET_reverse_instant, and indicates whether we are in the priv_initialize()
|
|
* (or priv_reverse_initialize()) call, and should therefore only invoke open-
|
|
* ended intervals.
|
|
*
|
|
* time is only relevant for ET_initialize, ET_reverse_initialize, and
|
|
* ET_step.
|
|
*/
|
|
void CMetaInterval::
|
|
enqueue_event(int n, CInterval::EventType event_type, bool is_initial, int time) {
|
|
nassertv(n >= 0 && n < (int)_defs.size());
|
|
const IntervalDef &def = _defs[n];
|
|
switch (def._type) {
|
|
case DT_c_interval:
|
|
if (is_initial &&
|
|
(event_type == ET_instant || event_type == ET_reverse_instant) &&
|
|
!def._c_interval->get_open_ended()) {
|
|
// Ignore a non-open-ended interval that we skipped completely past on
|
|
// priv_initialize().
|
|
return;
|
|
} else {
|
|
if (_event_queue.empty()) {
|
|
// if the event queue is empty, we can process this C++ interval
|
|
// immediately. We only need to defer it if there are external (e.g.
|
|
// Python) intervals in the queue that need to be processed first.
|
|
def._c_interval->priv_do_event(int_to_double_time(time), event_type);
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DT_ext_index:
|
|
if (is_initial &&
|
|
(event_type == ET_instant || event_type == ET_reverse_instant) &&
|
|
!def._ext_open_ended) {
|
|
// Ignore a non-open-ended interval that we skipped completely past on
|
|
// priv_initialize().
|
|
return;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
nassertv(false);
|
|
return;
|
|
}
|
|
|
|
_event_queue.push_back(EventQueueEntry(n, event_type, time));
|
|
}
|
|
|
|
/**
|
|
* Enqueues a reference to *this* interval. This is called only when the
|
|
* interval is recursively re-entered; the request will be serviced when the
|
|
* current request is done processing.
|
|
*
|
|
* time is only relevant for ET_initialize, ET_reverse_initialize, and
|
|
* ET_step.
|
|
*/
|
|
void CMetaInterval::
|
|
enqueue_self_event(CInterval::EventType event_type, double t) {
|
|
interval_cat.info()
|
|
<< "Recursive reentry detected into " << *this << "\n";
|
|
int time = double_to_int_time(t);
|
|
_event_queue.push_back(EventQueueEntry(-1, event_type, time));
|
|
}
|
|
|
|
/**
|
|
* Enqueues a special "event" that simply marks the end of processing of the
|
|
* interval; the interval's done event should be thrown now, if it is defined.
|
|
*/
|
|
void CMetaInterval::
|
|
enqueue_done_event() {
|
|
_event_queue.push_back(EventQueueEntry(-2, ET_finalize, 0));
|
|
}
|
|
|
|
/**
|
|
* Invokes whatever C++ intervals might be at the head of the queue, and
|
|
* prepares for passing an external interval to the scripting language.
|
|
*
|
|
* The return value is true if there remains at least one external event to be
|
|
* serviced, false if all events are handled.
|
|
*/
|
|
bool CMetaInterval::
|
|
service_event_queue() {
|
|
while (!_event_queue.empty()) {
|
|
nassertr(!_processing_events, true);
|
|
const EventQueueEntry &entry = _event_queue.front();
|
|
if (entry._n == -1) {
|
|
// Index -1 is a special code for *this* interval.
|
|
priv_do_event(int_to_double_time(entry._time), entry._event_type);
|
|
|
|
} else if (entry._n == -2) {
|
|
// Index -2 is a special code to indicate the interval is now done, and
|
|
// its done event should be thrown.
|
|
interval_done();
|
|
|
|
} else {
|
|
nassertr(entry._n >= 0 && entry._n < (int)_defs.size(), false);
|
|
const IntervalDef &def = _defs[entry._n];
|
|
switch (def._type) {
|
|
case DT_c_interval:
|
|
// Handle the C++ event.
|
|
def._c_interval->priv_do_event(int_to_double_time(entry._time), entry._event_type);
|
|
break;
|
|
|
|
case DT_ext_index:
|
|
// Here's an external event; leave it there and return.
|
|
return true;
|
|
|
|
default:
|
|
nassertr(false, false);
|
|
return false;
|
|
}
|
|
}
|
|
_event_queue.pop_front();
|
|
}
|
|
|
|
// No more events on the queue.
|
|
nassertr(!_processing_events, false);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Recursively recomputes a complete level (delimited by push/pop
|
|
* definitions).
|
|
*
|
|
* The value n on entry refers to the first entry after the push; the return
|
|
* value will reference the matching pop, or an index greater than the last
|
|
* element in the array if there was no matching pop.
|
|
*
|
|
* The level_begin value indicates the begin time of this level. On return,
|
|
* level_end is filled with the end time of this level.
|
|
*/
|
|
int CMetaInterval::
|
|
recompute_level(int n, int level_begin, int &level_end) {
|
|
level_end = level_begin;
|
|
int previous_begin = level_begin;
|
|
int previous_end = level_begin;
|
|
|
|
while (n < (int)_defs.size() && _defs[n]._type != DT_pop_level) {
|
|
IntervalDef &def = _defs[n];
|
|
int begin_time = previous_begin;
|
|
int end_time = previous_end;
|
|
switch (def._type) {
|
|
case DT_c_interval:
|
|
begin_time = get_begin_time(def, level_begin, previous_begin, previous_end);
|
|
def._actual_begin_time = begin_time;
|
|
end_time = begin_time + double_to_int_time(def._c_interval->get_duration());
|
|
|
|
if (def._c_interval->is_exact_type(WaitInterval::get_class_type())) {
|
|
// Don't bother enqueuing events for WaitIntervals; they're just there
|
|
// to fill up time.
|
|
|
|
} else {
|
|
if (begin_time == end_time) {
|
|
_events.push_back(new PlaybackEvent(begin_time, n, PET_instant));
|
|
} else {
|
|
PlaybackEvent *begin = new PlaybackEvent(begin_time, n, PET_begin);
|
|
PlaybackEvent *end = new PlaybackEvent(end_time, n, PET_end);
|
|
end->_begin_event = begin;
|
|
_events.push_back(begin);
|
|
_events.push_back(end);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DT_ext_index:
|
|
begin_time = get_begin_time(def, level_begin, previous_begin, previous_end);
|
|
def._actual_begin_time = begin_time;
|
|
end_time = begin_time + double_to_int_time(def._ext_duration);
|
|
if (begin_time == end_time) {
|
|
_events.push_back(new PlaybackEvent(begin_time, n, PET_instant));
|
|
} else {
|
|
PlaybackEvent *begin = new PlaybackEvent(begin_time, n, PET_begin);
|
|
PlaybackEvent *end = new PlaybackEvent(end_time, n, PET_end);
|
|
end->_begin_event = begin;
|
|
_events.push_back(begin);
|
|
_events.push_back(end);
|
|
}
|
|
break;
|
|
|
|
case DT_push_level:
|
|
begin_time = get_begin_time(def, level_begin, previous_begin, previous_end);
|
|
def._actual_begin_time = begin_time;
|
|
n = recompute_level(n + 1, begin_time, end_time);
|
|
break;
|
|
|
|
case DT_pop_level:
|
|
nassertr(false, _defs.size());
|
|
break;
|
|
}
|
|
|
|
previous_begin = begin_time;
|
|
previous_end = end_time;
|
|
level_end = std::max(level_end, end_time);
|
|
n++;
|
|
}
|
|
|
|
if (n < (int)_defs.size()) {
|
|
IntervalDef &def = _defs[n];
|
|
// If we have a pop record, check it for a phony duration.
|
|
if (def._ext_duration >= 0.0) {
|
|
level_end = level_begin + double_to_int_time(def._ext_duration);
|
|
}
|
|
|
|
// The final pop "begins" at the level end time, just for clarity on
|
|
// output.
|
|
def._actual_begin_time = level_end;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Returns the integer begin time indicated by the given IntervalDef, given
|
|
* the indicated level begin, previous begin, and previous end times.
|
|
*/
|
|
int CMetaInterval::
|
|
get_begin_time(const CMetaInterval::IntervalDef &def, int level_begin,
|
|
int previous_begin, int previous_end) {
|
|
switch (def._rel_to) {
|
|
case RS_previous_end:
|
|
return previous_end + double_to_int_time(def._rel_time);
|
|
|
|
case RS_previous_begin:
|
|
return previous_begin + double_to_int_time(def._rel_time);
|
|
|
|
case RS_level_begin:
|
|
return level_begin + double_to_int_time(def._rel_time);
|
|
}
|
|
|
|
nassertr(false, previous_end);
|
|
return previous_end;
|
|
}
|
|
|
|
/**
|
|
* Formats an event for output, for write() or timeline().
|
|
*/
|
|
void CMetaInterval::
|
|
write_event_desc(std::ostream &out, const CMetaInterval::IntervalDef &def,
|
|
int &extra_indent_level) const {
|
|
switch (def._type) {
|
|
case DT_c_interval:
|
|
indent(out, extra_indent_level)
|
|
<< *def._c_interval;
|
|
if (!def._c_interval->get_open_ended()) {
|
|
out << " (!oe)";
|
|
}
|
|
out << "\n";
|
|
break;
|
|
|
|
case DT_ext_index:
|
|
indent(out, extra_indent_level)
|
|
<< "*" << def._ext_name;
|
|
if (def._ext_duration != 0.0) {
|
|
out << " dur " << def._ext_duration;
|
|
}
|
|
if (!def._ext_open_ended) {
|
|
out << " (!oe)";
|
|
}
|
|
out<< "\n";
|
|
break;
|
|
|
|
case DT_push_level:
|
|
indent(out, extra_indent_level)
|
|
<< def._ext_name << " {\n";
|
|
extra_indent_level += 2;
|
|
break;
|
|
|
|
case DT_pop_level:
|
|
extra_indent_level -= 2;
|
|
indent(out, extra_indent_level)
|
|
<< "}\n";
|
|
break;
|
|
}
|
|
}
|