// Filename: cMetaInterval.cxx // Created by: drose (27Aug02) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved // // All use of this software is subject to the terms of the Panda 3d // Software license. You should have received a copy of this license // along with this source code; you will also find a current copy of // the license at http://etc.cmu.edu/panda3d/docs/license/ . // // To contact the maintainers of this program write to // panda3d-general@lists.sourceforge.net . // //////////////////////////////////////////////////////////////////// #include "cMetaInterval.h" #include "waitInterval.h" #include "config_interval.h" #include "indirectLess.h" #include "indent.h" #include #include // for log10() #include // 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()); _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; } }