open_toontown_panda3d/direct/src/interval/cMetaInterval.cxx

1328 lines
44 KiB
C++

// Filename: cMetaInterval.cxx
// Created by: drose (27Aug02)
//
////////////////////////////////////////////////////////////////////
//
// 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."
//
////////////////////////////////////////////////////////////////////
#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()
TypeHandle CMetaInterval::_type_handle;
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::Constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
CMetaInterval::
CMetaInterval(const string &name) :
CInterval(name, 0.0, true)
{
_precision = interval_precision;
_current_nesting_level = 0;
_next_event_index = 0;
_processing_events = false;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::Destructor
// Access: Published, Virtual
// Description:
////////////////////////////////////////////////////////////////////
CMetaInterval::
~CMetaInterval() {
clear_intervals();
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::clear_intervals
// Access: Published
// Description: 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 != (CInterval *)NULL) {
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
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::push_level
// Access: Published
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::add_c_interval
// Access: Published
// Description: 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 != (CInterval *)NULL, -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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::add_ext_index
// Access: Published
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::pop_level
// Access: Published
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::set_interval_start_time
// Access: Published
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::get_interval_start_time
// Access: Published
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::get_interval_end_time
// Access: Published
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::initialize
// Access: Published, Virtual
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::instant
// Access: Published, Virtual
// Description: 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();
}
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::step
// Access: Published, Virtual
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::finalize
// Access: Published, Virtual
// Description: 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();
}
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::reverse_initialize
// Access: Published, Virtual
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::reverse_instant
// Access: Published, Virtual
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::reverse_finalize
// Access: Published, Virtual
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::interrupt
// Access: Published, Virtual
// Description: 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;
}
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::pop_event
// Access: Published
// Description: 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();
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::write
// Access: Published, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void CMetaInterval::
write(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[12];
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);
}
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::timeline
// Access: Published
// Description: Outputs a list of all events in the order in which
// they occur.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
timeline(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[12];
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);
}
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::do_recompute
// Access: Protected, Virtual
// Description: 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.
stable_sort(_events.begin(), _events.end(), IndirectLess<PlaybackEvent>());
_duration = int_to_double_time(_end_time);
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::clear_events
// Access: Private
// Description: 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();
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::do_event_forward
// Access: Private
// Description: 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;
}
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::finish_events_forward
// Access: Private
// Description: 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);
}
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::do_event_reverse
// Access: Private
// Description: 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;
}
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::finish_events_reverse
// Access: Private
// Description: 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);
}
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::enqueue_event
// Access: Private
// Description: 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));
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::enqueue_self_event
// Access: Private
// Description: 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));
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::enqueue_done_event
// Access: Private
// Description: 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));
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::service_event_queue
// Access: Private
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::recompute_level
// Access: Private
// Description: 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 = 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::get_begin_time
// Access: Private
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// Function: CMetaInterval::write_event_desc
// Access: Private
// Description: Formats an event for output, for write() or
// timeline().
////////////////////////////////////////////////////////////////////
void CMetaInterval::
write_event_desc(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;
}
}