// Filename: bamReader.cxx
// Created by:  jason (12Jun00)
//
////////////////////////////////////////////////////////////////////
//
// 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 "pandabase.h"
#include "pnotify.h"

#include "bam.h"
#include "bamReader.h"
#include "datagramIterator.h"
#include "config_util.h"
#include "pipelineCyclerBase.h"

TypeHandle BamReaderAuxData::_type_handle;

WritableFactory *BamReader::_factory = (WritableFactory*)0L;
BamReader *const BamReader::Null = (BamReader*)0L;
WritableFactory *const BamReader::NullFactory = (WritableFactory*)0L;

BamReader::NewTypes BamReader::_new_types;

const int BamReader::_cur_major = _bam_major_ver;
const int BamReader::_cur_minor = _bam_minor_ver;


////////////////////////////////////////////////////////////////////
//     Function: BamReader::Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
BamReader::
BamReader(DatagramGenerator *source)
  : _source(source)
{
  _needs_init = true;
  _num_extra_objects = 0;
  _nesting_level = 0;
  _now_creating = _created_objs.end();
  _reading_cycler = (PipelineCyclerBase *)NULL;
  _pta_id = -1;
  _long_object_id = false;
  _long_pta_id = false;
}


////////////////////////////////////////////////////////////////////
//     Function: BamReader::Destructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
BamReader::
~BamReader() {
  nassertv(_num_extra_objects == 0);
  nassertv(_nesting_level == 0);
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::set_source
//       Access: Published
//  Description: Changes the source of future datagrams for this
//               BamReader.  This also implicitly calls init() if it
//               has not already been called.
////////////////////////////////////////////////////////////////////
void BamReader::
set_source(DatagramGenerator *source) {
  _source = source;
  if (_needs_init && _source != NULL) {
    bool success = init();
    nassertv(success);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::init
//       Access: Published
//  Description: Initializes the BamReader prior to reading any
//               objects from its source.  This includes reading the
//               Bam header.
//
//               This returns true if the BamReader successfully
//               initialized, false otherwise.
////////////////////////////////////////////////////////////////////
bool BamReader::
init() {
  nassertr(_source != NULL, false);
  nassertr(_needs_init, false);
  _needs_init = false;
  Datagram header;

  if (_source->is_error()) {
    return false;
  }

  if (!get_datagram(header)) {
    bam_cat.error()
      << "Unable to read Bam header.\n";
    return false;
  }

  DatagramIterator scan(header);

  _file_major = scan.get_uint16();
  _file_minor = scan.get_uint16();

  // If the major version is different, or the minor version is
  // *newer*, we can't safely load the file.
  if (_file_major != _bam_major_ver || 
      _file_minor < _bam_first_minor_ver ||
      _file_minor > _bam_minor_ver) {
    bam_cat.error()
      << "Bam file is version " << _file_major << "." << _file_minor
      << ".\n";

    if (_bam_minor_ver == _bam_first_minor_ver) {
      bam_cat.error()
        << "This program can only load version "
        << _bam_major_ver << "." << _bam_first_minor_ver << " bams.\n";
    } else {
      bam_cat.error()
        << "This program can only load version "
        << _bam_major_ver << "." << _bam_first_minor_ver << " through "
        << _bam_major_ver << "." << _bam_minor_ver << " bams.\n";
    }

    return false;
  }

  _file_endian = (BamEndian)scan.get_uint8();

  _file_stdfloat_double = false;
  if (_file_minor >= 27) {
    _file_stdfloat_double = scan.get_bool();
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::set_aux_data
//       Access: Published
//  Description: Associates an arbitrary block of data with the
//               indicated object (or NULL), and the indicated name.
//
//               This is intended to provide a place for temporary
//               storage for objects reading themselves from the bam
//               file.  To use it, inherit from BamReader::AuxData and
//               store whatever data you like there.  Then associate
//               your AuxData with the object as it is being read with
//               set_aux_data().  You may later set the aux data to
//               NULL to remove it; or it will automatically be
//               removed (and deleted) after finalize() is called for
//               the object in question.
//
//               If the TypedWritable pointer is NULL, the the aux
//               data is stored globally for the BamReader in general.
//               This pointer is available to any bam objects, and
//               will not be automatically removed until the BamReader
//               itself destructs.
//
//               In either case, the name is just an arbitrary
//               user-defined key.  If there is already a data pointer
//               stored for the obj/name pair, that data pointer will
//               be replaced (and deleted).
////////////////////////////////////////////////////////////////////
void BamReader::
set_aux_data(TypedWritable *obj, const string &name, BamReader::AuxData *data) {
  if (data == (void *)NULL) {
    AuxDataTable::iterator ti = _aux_data.find(obj);
    if (ti != _aux_data.end()) {
      AuxDataNames &names = (*ti).second;
      names.erase(name);
      if (names.empty()) {
        _aux_data.erase(ti);
      }
    }

  } else {
    _aux_data[obj][name] = data;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::get_aux_data
//       Access: Published
//  Description: Returns the pointer previously associated with the
//               bam reader by a previous call to set_aux_data(), or
//               NULL if data with the indicated key has not been set.
////////////////////////////////////////////////////////////////////
BamReader::AuxData *BamReader::
get_aux_data(TypedWritable *obj, const string &name) const {
  AuxDataTable::const_iterator ti = _aux_data.find(obj);
  if (ti != _aux_data.end()) {
    const AuxDataNames &names = (*ti).second;
    AuxDataNames::const_iterator ni = names.find(name);
    if (ni != names.end()) {
      return (*ni).second;
    }
  }

  return NULL;
}


////////////////////////////////////////////////////////////////////
//     Function: BamReader::read_object
//       Access: Published
//  Description: Reads a single object from the Bam file.  If the
//               object type is known, a new object of the appropriate
//               type is created and returned; otherwise, NULL is
//               returned.  NULL is also returned when the end of the
//               file is reached.  is_eof() may be called to
//               differentiate between these two cases.
//
//               This may be called repeatedly to extract out all the
//               objects in the Bam file, but typically (especially
//               for scene graph files, indicated with the .bam
//               extension), only one object is retrieved directly
//               from the Bam file: the root of the scene graph.  The
//               remaining objects will all be retrieved recursively
//               by the first object.
//
//               Note that the object returned may not yet be
//               complete.  In particular, some of its pointers may
//               not be filled in; you must call resolve() to fill in
//               all the available pointers before you can safely use
//               any objects returned by read_object().
//
//               This flavor of read_object() requires the caller to
//               know what type of object it has received in order to
//               properly manage the reference counts.
////////////////////////////////////////////////////////////////////
TypedWritable *BamReader::
read_object() {
  TypedWritable *ptr;
  ReferenceCount *ref_ptr;

  if (!read_object(ptr, ref_ptr)) {
    return NULL;
  }

  return ptr;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::read_object
//       Access: Published
//  Description: Reads a single object from the Bam file.  
//
//               This flavor of read_object() returns both a
//               TypedWritable and a ReferenceCount pointer to the
//               same object, so the reference count may be tracked
//               reliably, without having to know precisely what type
//               of object we have.  It returns true on success, or
//               false on failure.
////////////////////////////////////////////////////////////////////
bool BamReader::
read_object(TypedWritable *&ptr, ReferenceCount *&ref_ptr) {
  ptr = NULL;
  ref_ptr = NULL;
  nassertr(_num_extra_objects == 0, false);

  int start_level = _nesting_level;

  // First, read the base object.
  int object_id = p_read_object();

  // Now that object might have included some pointers to other
  // objects, which may still need to be read.  And those objects
  // might in turn require reading additional objects.  Read all the
  // remaining objects.

  // Prior to 6.21, we kept track of _num_extra_objects to know when
  // we're done.
  while (_num_extra_objects > 0) {
    p_read_object();
    _num_extra_objects--;
  }

  // Beginning with 6.21, we use explicit nesting commands to know
  // when we're done.
  while (_nesting_level > start_level) {
    p_read_object();
  }

  // Now look up the pointer of the object we read first.  It should
  // be available now.
  if (object_id == 0) {
    if (bam_cat.is_spam()) {
      bam_cat.spam()
        << "Returning false\n";
    }
    return false;
  }

  CreatedObjs::iterator oi = _created_objs.find(object_id);

  if (oi == _created_objs.end()) {
    bam_cat.error()
      << "Undefined object encountered!\n";
    return false;

  } else {
    CreatedObj &created_obj = (*oi).second;
    ptr = created_obj._ptr;
    ref_ptr = created_obj._ref_ptr;

    if (bam_cat.is_spam()) {
      if (ptr != (TypedWritable *)NULL) {
        bam_cat.spam()
          << "Returning object of type " << ptr->get_type() << "\n";
      }
    }
    if (created_obj._change_this != NULL ||
        created_obj._change_this_ref != NULL) {
      bam_cat.warning()
        << "Returning pointer to " << ptr->get_type()
        << " that might change.\n";
    }

    return true;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::resolve
//       Access: Published
//  Description: This may be called at any time during processing of
//               the Bam file to resolve all the known pointers so
//               far.  It is usually called at the end of the
//               processing, after all objects have been read, which
//               is generally the best time to call it.
//
//               This must be called at least once after reading a
//               particular object via get_object() in order to
//               validate that object.
//
//               The return value is true if all objects have been
//               resolved, or false if some objects are still
//               outstanding (in which case you will need to call
//               resolve() again later).
////////////////////////////////////////////////////////////////////
bool BamReader::
resolve() {
  bool all_completed;
  bool any_completed_this_pass;

  do {
    if (bam_cat.is_spam()) {
      bam_cat.spam()
        << "resolve pass begin\n";
    }
    all_completed = true;
    any_completed_this_pass = false;
    
    ObjectPointers::iterator oi;
    oi = _object_pointers.begin();
    while (oi != _object_pointers.end()) {
      int object_id = (*oi).first;
      PointerReference &pref = (*oi).second;

      CreatedObjs::iterator ci = _created_objs.find(object_id);
      nassertr(ci != _created_objs.end(), false);

      CreatedObj &created_obj = (*ci).second;
      
      TypedWritable *object_ptr = created_obj._ptr;

      // Update _now_creating, so a call to get_int_tag() from within
      // complete_pointers() will come to the right place.
      CreatedObjs::iterator was_creating = _now_creating;
      _now_creating = ci;
      
      if (resolve_object_pointers(object_ptr, pref)) {
        // Now remove this object from the list of things that need
        // completion.  We have to be a bit careful when deleting things
        // from the STL container while we are traversing it.
        ObjectPointers::iterator old = oi;
        ++oi;
        _object_pointers.erase(old);
        any_completed_this_pass = true;
        
        // Does the pointer need to change?
        if (created_obj._change_this_ref != NULL) {
          // Reference-counting variant.
          TypedWritableReferenceCount *object_ref_ptr = (TypedWritableReferenceCount *)object_ptr;
          nassertr(created_obj._ref_ptr == NULL || created_obj._ref_ptr == object_ref_ptr, false);
          PT(TypedWritableReferenceCount) new_ptr = created_obj._change_this_ref(object_ref_ptr, this);
          if (new_ptr != object_ref_ptr) {
            // Also update the reverse
            vector_int &old_refs = _created_objs_by_pointer[object_ptr];
            vector_int &new_refs = _created_objs_by_pointer[new_ptr];
            for (vector_int::const_iterator oi = old_refs.begin();
                 oi != old_refs.end();
                 ++oi) {
              new_refs.push_back(*oi);
            }
            _created_objs_by_pointer.erase(object_ptr);

            // Remove the pointer from the finalize list (the new
            // pointer presumably doesn't require finalizing).
            _finalize_list.erase(object_ptr);
          }
          created_obj.set_ptr(new_ptr, new_ptr);
          created_obj._change_this = NULL;
          created_obj._change_this_ref = NULL;

        } else if (created_obj._change_this != NULL) {
          // Non-reference-counting variant.
          TypedWritable *new_ptr = created_obj._change_this(object_ptr, this);
          if (new_ptr != object_ptr) {
            // Also update the reverse
            vector_int &old_refs = _created_objs_by_pointer[object_ptr];
            vector_int &new_refs = _created_objs_by_pointer[new_ptr];
            for (vector_int::const_iterator oi = old_refs.begin();
                 oi != old_refs.end();
                 ++oi) {
              new_refs.push_back(*oi);
            }
            _created_objs_by_pointer.erase(object_ptr);

            // Remove the pointer from the finalize list (the new
            // pointer presumably doesn't require finalizing).
            _finalize_list.erase(object_ptr);
          }
          created_obj.set_ptr(new_ptr, new_ptr->as_reference_count());
          created_obj._change_this = NULL;
          created_obj._change_this_ref = NULL;
        }
        
      } else {
        // Couldn't complete this object yet; it'll wait for next time.
        ++oi;
        all_completed = false;
      }

      _now_creating = was_creating;
    }

    if (bam_cat.is_spam()) {
      bam_cat.spam()
        << "resolve pass end: all_completed = " << all_completed
        << " any_completed_this_pass = " << any_completed_this_pass
        << "\n";
    }
  } while (!all_completed && any_completed_this_pass);

  if (all_completed) {
    finalize();
  } else {
    // Report all the uncompleted objects for no good reason.  This
    // will probably have to come out later when we have cases in
    // which some objects might legitimately be uncompleted after
    // calling resolve(), but for now we expect resolve() to always
    // succeed.
    ObjectPointers::const_iterator oi;
    for (oi = _object_pointers.begin(); 
         oi != _object_pointers.end();
         ++oi) {
      int object_id = (*oi).first;
      CreatedObjs::iterator ci = _created_objs.find(object_id);
      nassertr(ci != _created_objs.end(), false);
      CreatedObj &created_obj = (*ci).second;
      TypedWritable *object_ptr = created_obj._ptr;

      bam_cat.warning()
        << "Unable to complete " << object_ptr->get_type() << "\n";
    }
  }

  return all_completed;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::change_pointer
//       Access: Published
//  Description: Indicates that an object recently read from the bam
//               stream should be replaced with a new object.  Any
//               future occurrences of the original object in the
//               stream will henceforth return the new object instead.
//
//               The return value is true if the replacement was
//               successfully made, or false if the object was not
//               read from the stream (or if change_pointer had
//               already been called on it).
////////////////////////////////////////////////////////////////////
bool BamReader::
change_pointer(const TypedWritable *orig_pointer, const TypedWritable *new_pointer) {
  if (orig_pointer == new_pointer) {
    return false;
  }
  CreatedObjsByPointer::iterator ci = _created_objs_by_pointer.find(orig_pointer);
  if (ci == _created_objs_by_pointer.end()) {
    // No record of this object.
    return false;
  }

  if (bam_cat.is_spam()) {
    bam_cat.spam()
      << "change_pointer(" << (void *)orig_pointer << ", " 
      << (void *)new_pointer << ") (" << new_pointer->get_type() << ")\n";
  }

  const vector_int &old_refs = (*ci).second;
  vector_int &new_refs = _created_objs_by_pointer[new_pointer];

  for (vector_int::const_iterator oi = old_refs.begin(); 
       oi != old_refs.end();
       ++oi) {
    int object_id = (*oi);

    CreatedObjs::iterator ci = _created_objs.find(object_id);
    nassertr(ci != _created_objs.end(), false);
    nassertr((*ci).second._ptr == orig_pointer, false);

    TypedWritable *ptr = (TypedWritable *)new_pointer;
    (*ci).second.set_ptr(ptr, ptr->as_reference_count());
    new_refs.push_back(object_id);
  }

  _created_objs_by_pointer.erase(ci);

  // Also change the pointer on the finalize_list.
  Finalize::iterator fi = _finalize_list.find((TypedWritable *)orig_pointer);
  if (fi != _finalize_list.end()) {
    _finalize_list.insert((TypedWritable *)new_pointer);
    _finalize_list.erase(fi);
  }

  return true;
}


////////////////////////////////////////////////////////////////////
//     Function: BamReader::read_handle
//       Access: Published
//  Description: Reads a TypeHandle out of the Datagram.
////////////////////////////////////////////////////////////////////
TypeHandle BamReader::
read_handle(DatagramIterator &scan) {
  // We encode TypeHandles within the Bam file by writing a unique
  // index number for each one to the file.  When we write a
  // particular TypeHandle for the first time, we assign it a new
  // index number and then immediately follow it by its definition;
  // when we write the same TypeHandle on subsequent times we only
  // write the index number.

  // Thus, to read a TypeHandle, we first read the index number.  If
  // it is a number we have not yet encountered, we must then read the
  // definition.

  // Here's the index number.
  int id = scan.get_uint16();

  if (id == 0) {
    // Index number 0 is always, by convention, TypeHandle::none().
    return TypeHandle::none();
  }

  IndexMap::const_iterator mi = _index_map.find(id);
  if (mi != _index_map.end()) {
    // We've encountered this index number before, so there should be
    // no type definition following the id.  Simply return the
    // TypeHandle we previously associated with the id.
    TypeHandle type = (*mi).second;
    return type;
  }

  // We haven't encountered this index number before.  This means it
  // will be immediately followed by the type definition.  This
  // consists of the string name, followed by the list of parent
  // TypeHandles for this type.

  string name = scan.get_string();
  bool new_type = false;

  TypeHandle type = TypeRegistry::ptr()->find_type(name);
  if (type == TypeHandle::none()) {
    // We've never heard of this type before!  This is really an error
    // condition, but we'll do the best we can and declare it
    // on-the-fly.

    type = TypeRegistry::ptr()->register_dynamic_type(name);
    bam_cat.warning()
      << "Bam file '" << get_filename() << "' contains objects of unknown type: " 
      << type << "\n";
    new_type = true;
    _new_types.insert(type);
  }

  // Now pick up the derivation information.
  int num_parent_classes = scan.get_uint8();
  for (int i = 0; i < num_parent_classes; i++) {
    TypeHandle parent_type = read_handle(scan);
    if (new_type) {
      TypeRegistry::ptr()->record_derivation(type, parent_type);
    } else {
      if (type.get_parent_towards(parent_type) != parent_type) {
        if (bam_cat.is_debug()) {
          bam_cat.debug()
            << "Bam file indicates a derivation of " << type
            << " from " << parent_type << " which is no longer true.\n";
        }
      }
    }
  }

  bool inserted = _index_map.insert(IndexMap::value_type(id, type)).second;
  nassertr(inserted, type);

  if (bam_cat.is_spam()) {
    bam_cat.spam()
      << "Read TypeHandle for " << type << ".\n";
  }

  return type;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::read_pointer
//       Access: Public
//  Description: The interface for reading a pointer to another object
//               from a Bam file.  Objects reading themselves from a
//               Bam file should call this when they expect to read a
//               pointer to some other object.  This function reads
//               whatever is stored in the bam file to represent the
//               pointer, and advances the datagram iterator
//               accordingly.
//
//               Rather than returning a pointer immediately, this
//               function reads the internal pointer information from
//               the datagram and queues up the request.  The pointer
//               itself may not be available until later (it may be a
//               pointer to an object that appears later in the Bam
//               file).  Later, when all pointers are available, the
//               complete_pointers() callback function will be called
//               with an array of actual pointers, one for each time
//               read_pointer() was called.  It is then the calling
//               object's responsibility to store these pointers in the
//               object properly.
////////////////////////////////////////////////////////////////////
void BamReader::
read_pointer(DatagramIterator &scan) {
  Thread::consider_yield();

  nassertv(_now_creating != _created_objs.end());
  int requestor_id = (*_now_creating).first;

  // Read the object ID, and associate it with the requesting object.
  int object_id = read_object_id(scan);

  PointerReference &pref = _object_pointers[requestor_id];
  if (_reading_cycler == (PipelineCyclerBase *)NULL) {
    // This is not being read within a read_cdata() call.
    pref._objects.push_back(object_id);
  } else {
    // This *is* being read within a read_cdata() call.
    pref._cycler_pointers[_reading_cycler].push_back(object_id);
  }

  // If the object ID is zero (which indicates a NULL pointer), we
  // don't have to do anything else.
  if (object_id != 0) {
    /*
    CreatedObj new_created_obj;
    _created_objs.insert(CreatedObjs::value_type(object_id, new_created_obj)).second;
    */

    if (get_file_minor_ver() < 21) {
      // Prior to bam version 6.21, we expect to read an adjunct
      // object for each non-NULL pointer we read.
      _num_extra_objects++;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::read_pointers
//       Access: Public
//  Description: A convenience function to read a contiguous list of
//               pointers.  This is equivalent to calling
//               read_pointer() count times.
////////////////////////////////////////////////////////////////////
void BamReader::
read_pointers(DatagramIterator &scan, int count) {
  for (int i = 0; i < count; i++) {
    read_pointer(scan);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::skip_pointer
//       Access: Public
//  Description: Reads and discards a pointer value from the Bam file.
//               This pointer will not be counted among the pointers
//               read for a given object, and will not be in the list
//               of pointers passed to complete_pointers().
////////////////////////////////////////////////////////////////////
void BamReader::
skip_pointer(DatagramIterator &scan) {
  read_object_id(scan);
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::read_file_data
//       Access: Public
//  Description: Reads a block of auxiliary file data from the Bam
//               file.  This can be a block of arbitrary size, and it
//               is assumed it may be quite large.  Rather than
//               reading the entire block into memory, a file
//               reference is returned to locate the block on disk.
//               The data must have been written by a matching call to
//               write_file_data().
////////////////////////////////////////////////////////////////////
void BamReader::
read_file_data(SubfileInfo &info) {
  // write_file_data() actually writes the blocks in datagrams prior
  // to this particular datagram.  Assume we get the calls to
  // read_file_data() in the same order as the corresponding calls to
  // write_file_data(), and just pop the first one off the
  // queue. There's no actual data written to the stream at this
  // point.
  nassertv(!_file_data_records.empty());
  info = _file_data_records.front();
  _file_data_records.pop_front();
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::read_cdata
//       Access: Public
//  Description: Reads in the indicated CycleData object.  This should
//               be used by classes that store some or all of their
//               data within a CycleData subclass, in support of
//               pipelining.  This will call the virtual
//               CycleData::fillin() method to do the actual reading.
////////////////////////////////////////////////////////////////////
void BamReader::
read_cdata(DatagramIterator &scan, PipelineCyclerBase &cycler) {
  PipelineCyclerBase *old_cycler = _reading_cycler;
  _reading_cycler = &cycler;
  CycleData *cdata = cycler.write(Thread::get_current_thread());
  cdata->fillin(scan, this);
  cycler.release_write(cdata);
  _reading_cycler = old_cycler;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::read_cdata
//       Access: Public
//  Description: This flavor of read_cdata allows passing an
//               additional parameter to cdata->fillin().
////////////////////////////////////////////////////////////////////
void BamReader::
read_cdata(DatagramIterator &scan, PipelineCyclerBase &cycler,
           void *extra_data) {
  PipelineCyclerBase *old_cycler = _reading_cycler;
  _reading_cycler = &cycler;
  CycleData *cdata = cycler.write(Thread::get_current_thread());
  cdata->fillin(scan, this, extra_data);
  cycler.release_write(cdata);
  _reading_cycler = old_cycler;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::set_int_tag
//       Access: Public
//  Description: Allows the creating object to store a temporary data
//               value on the BamReader.  This method may be called
//               during an object's fillin() method; it will associate
//               an integer value with an arbitrary string key (which
//               is in turn associated with the calling object only).
//               Later, in the complete_pointers() method, the same
//               object may query this data again via get_int_tag().
//
//               The tag string need not be unique between different
//               objects, but it should be unique between an object
//               and its CData object(s).
////////////////////////////////////////////////////////////////////
void BamReader::
set_int_tag(const string &tag, int value) {
  nassertv(_now_creating != _created_objs.end());
  int requestor_id = (*_now_creating).first;

  PointerReference &pref = _object_pointers[requestor_id];
  pref._int_tags[tag] = value;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::get_int_tag
//       Access: Public
//  Description: Returns the value previously set via set_int_tag().
//               It is an error if no value has been set.
////////////////////////////////////////////////////////////////////
int BamReader::
get_int_tag(const string &tag) const {
  nassertr(_now_creating != _created_objs.end(), 0);
  int requestor_id = (*_now_creating).first;

  ObjectPointers::const_iterator opi = _object_pointers.find(requestor_id);
  nassertr(opi != _object_pointers.end(), 0);
  const PointerReference &pref = (*opi).second;

  IntTags::const_iterator iti = pref._int_tags.find(tag);
  nassertr(iti != pref._int_tags.end(), 0);
  return (*iti).second;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::set_aux_tag
//       Access: Public
//  Description: Allows the creating object to store a temporary data
//               value on the BamReader.  This method may be called
//               during an object's fillin() method; it will associate
//               a newly-allocated BamReaderAuxData construct with an
//               arbitrary string key (which is in turn associated
//               with the calling object only).  Later, in the
//               complete_pointers() method, the same object may query
//               this data again via get_aux_tag().
//
//               The BamReader will maintain the reference count on
//               the BamReaderAuxData, and destruct it when it is
//               cleaned up.
//
//               The tag string need not be unique between different
//               objects, but it should be unique between an object
//               and its CData object(s).
////////////////////////////////////////////////////////////////////
void BamReader::
set_aux_tag(const string &tag, BamReaderAuxData *value) {
  nassertv(_now_creating != _created_objs.end());
  int requestor_id = (*_now_creating).first;

  PointerReference &pref = _object_pointers[requestor_id];
  pref._aux_tags[tag] = value;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::get_aux_tag
//       Access: Public
//  Description: Returns the value previously set via set_aux_tag().
//               It is an error if no value has been set.
////////////////////////////////////////////////////////////////////
BamReaderAuxData *BamReader::
get_aux_tag(const string &tag) const {
  nassertr(_now_creating != _created_objs.end(), NULL);
  int requestor_id = (*_now_creating).first;

  ObjectPointers::const_iterator opi = _object_pointers.find(requestor_id);
  nassertr(opi != _object_pointers.end(), NULL);
  const PointerReference &pref = (*opi).second;

  AuxTags::const_iterator ati = pref._aux_tags.find(tag);
  nassertr(ati != pref._aux_tags.end(), NULL);
  return (*ati).second;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::register_finalize
//       Access: Public
//  Description: Should be called by an object reading itself from the
//               Bam file to indicate that this particular object
//               would like to receive the finalize() callback when
//               all the objects and pointers in the Bam file are
//               completely read.
//
//               This provides a hook for objects that need to do any
//               additional finalization work after all of their
//               related pointers are guaranteed to be filled in.
////////////////////////////////////////////////////////////////////
void BamReader::
register_finalize(TypedWritable *whom) {
  nassertv(whom != (TypedWritable *)NULL);

  if (bam_cat.is_spam()) {
    bam_cat.spam()
      << "register_finalize(" << (void *)whom << ") (" << whom->get_type()
      << ")\n";
  }

  _finalize_list.insert(whom);
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::register_change_this
//       Access: Public
//  Description: Called by an object reading itself from the bam file
//               to indicate that the object pointer that will be
//               returned is temporary, and will eventually need to be
//               replaced with another pointer.
//
//               The supplied function pointer will later be called on
//               the object, immediately after complete_pointers() is
//               called; it should return the new and final pointer.
//
//               We use a static function pointer instead of a virtual
//               function (as in finalize()), to allow the function to
//               destruct the old pointer if necessary.  (It is
//               invalid to destruct the this pointer within a virtual
//               function.)
////////////////////////////////////////////////////////////////////
void BamReader::
register_change_this(ChangeThisFunc func, TypedWritable *object) {
  nassertv(_now_creating != _created_objs.end());
  CreatedObj &created_obj = (*_now_creating).second;

#ifndef NDEBUG
  // Sanity check the pointer--it should always be the same pointer
  // after we set it the first time.
  if (created_obj._ptr == (TypedWritable *)NULL) {
    created_obj.set_ptr(object, object->as_reference_count());
  } else {
    // We've previously assigned this pointer, and we should have
    // assigned it to the same this pointer we have now.
    nassertv(created_obj._ptr == object);
  }
#endif  // NDEBUG

  created_obj._change_this = func;
  created_obj._change_this_ref = NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::register_change_this
//       Access: Public
//  Description: Called by an object reading itself from the bam file
//               to indicate that the object pointer that will be
//               returned is temporary, and will eventually need to be
//               replaced with another pointer.
//
//               The supplied function pointer will later be called on
//               the object, immediately after complete_pointers() is
//               called; it should return the new and final pointer.
//
//               We use a static function pointer instead of a virtual
//               function (as in finalize()), to allow the function to
//               destruct the old pointer if necessary.  (It is
//               invalid to destruct the this pointer within a virtual
//               function.)
////////////////////////////////////////////////////////////////////
void BamReader::
register_change_this(ChangeThisRefFunc func, TypedWritableReferenceCount *object) {
  nassertv(_now_creating != _created_objs.end());
  CreatedObj &created_obj = (*_now_creating).second;

#ifndef NDEBUG
  // Sanity check the pointer--it should always be the same pointer
  // after we set it the first time.
  if (created_obj._ptr == (TypedWritable *)NULL) {
    created_obj.set_ptr(object, object);
  } else {
    // We've previously assigned this pointer, and we should have
    // assigned it to the same this pointer we have now.
    nassertv(created_obj._ptr == object);
    nassertv(created_obj._ref_ptr == object);
  }
#endif  // NDEBUG

  created_obj._change_this = NULL;
  created_obj._change_this_ref = func;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::finalize_now
//       Access: Public
//  Description: Forces the finalization of a particular object.  This
//               may be called by any of the objects during
//               finalization, to guarantee finalization ordering
//               where it is important.
////////////////////////////////////////////////////////////////////
void BamReader::
finalize_now(TypedWritable *whom) {
  if (whom == (TypedWritable *)NULL) {
    return;
  }

  Finalize::iterator fi = _finalize_list.find(whom);
  if (fi != _finalize_list.end()) {
    _finalize_list.erase(fi);
    if (bam_cat.is_spam()) {
      bam_cat.spam()
        << "finalizing " << (void *)whom << " (" << whom->get_type()
        << ")\n";
    }
    whom->finalize(this);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::get_pta
//       Access: Public
//  Description: This function works in conjection with
//               register_pta(), below, to read a PointerToArray (PTA)
//               from the Bam file, and unify references to the same
//               PTA.
//
//               The first time get_pta() encounters a particular PTA,
//               it will return NULL.  This is the indication that the
//               caller should then read in the data associated with
//               the PTA, and subsequently call register_pta() with
//               the address of the filled-in array.
//
//               The next time (and all subsequent times) that
//               get_pta() encounters this same PTA, it will return
//               the pointer that was passed with register_pta().
//
//               Also see the READ_PTA() macro, which consolidates all
//               the work that must be done to read a PTA.
////////////////////////////////////////////////////////////////////
void *BamReader::
get_pta(DatagramIterator &scan) {
  nassertr(_pta_id == -1, (void *)NULL);
  int id = read_pta_id(scan);

  if (id == 0) {
    // As always, a 0 ID indicates a NULL pointer.  The caller will
    // not be able to differentiate this case from that of a
    // previously-read pointer, but that's OK because the next data in
    // the Bam file is the length of the array, which will be
    // zero--indicating an empty or NULL array.
    return (void *)NULL;
  }

  PTAMap::iterator pi = _pta_map.find(id);
  if (pi == _pta_map.end()) {
    // This is the first time we've encountered this particular ID,
    // meaning we need to read the data now and register it.
    _pta_id = id;
    return (void *)NULL;
  }

  return (*pi).second;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::register_pta
//       Access: Public
//  Description: The second part of read_pta(), this should be called
//               with the pointer to the array that was read in after
//               read_pta() returned NULL.  This associates the
//               pointer with the ID that was previously read, so that
//               future calls to read_pta() will return the same
//               pointer.
//
//               Also see the READ_PTA() macro, which consolidates all
//               the work that must be done to read a PTA.
////////////////////////////////////////////////////////////////////
void BamReader::
register_pta(void *ptr) {
  if (_pta_id != -1) {
    bool inserted = _pta_map.insert(PTAMap::value_type(_pta_id, ptr)).second;
    _pta_id = -1;
    nassertv(inserted);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::free_object_ids
//       Access: Private
//  Description: Handles a record that begins with the _remove_flag
//               TypeHandle; this contains a list of object ID's that
//               will no longer be used in this file and can safely be
//               removed.
////////////////////////////////////////////////////////////////////
void BamReader::
free_object_ids(DatagramIterator &scan) {
  // We have to fully complete any objects before we remove them.
  // Might as well try to complete everything before we get started.
  resolve();

  while (scan.get_remaining_size() > 0) {
    int object_id = read_object_id(scan);

    CreatedObjs::iterator ci = _created_objs.find(object_id);
    if (ci == _created_objs.end()) {
      util_cat.warning()
        << "Bam file suggests eliminating object_id " << object_id
        << ", already gone.\n";
    } else {

      // Make sure the object was successfully completed.
      ObjectPointers::iterator oi = _object_pointers.find(object_id);
      if (oi != _object_pointers.end()) {
        util_cat.warning()
          << "Unable to resolve object " << object_id
          << " before removing from table.\n";
      }

      _created_objs_by_pointer.erase((*ci).second._ptr);
      _created_objs.erase(ci);
    }
  }
}


////////////////////////////////////////////////////////////////////
//     Function: BamReader::read_object_id
//       Access: Private
//  Description: Reads an object id from the datagram.
////////////////////////////////////////////////////////////////////
int BamReader::
read_object_id(DatagramIterator &scan) {
  int object_id;

  if (_long_object_id) {
    object_id = scan.get_uint32();

  } else {
    object_id = scan.get_uint16();
    if (object_id == 0xffff) {
      _long_object_id = true;
    }
  }

  return object_id;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::read_pta_id
//       Access: Private
//  Description: Reads an pta id from the datagram.
////////////////////////////////////////////////////////////////////
int BamReader::
read_pta_id(DatagramIterator &scan) {
  int pta_id;

  if (_long_pta_id) {
    pta_id = scan.get_uint32();

  } else {
    pta_id = scan.get_uint16();
    if (pta_id == 0xffff) {
      _long_pta_id = true;
    }
  }

  return pta_id;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::p_read_object
//       Access: Private
//  Description: The private implementation of read_object(); this
//               reads an object from the file and returns its object
//               ID.
////////////////////////////////////////////////////////////////////
int BamReader::
p_read_object() {
  Datagram dg;

  // First, read a datagram for the object.
  if (!get_datagram(dg)) {
    // When we run out of datagrams, we're at the end of the file.
    if (bam_cat.is_debug()) {
      bam_cat.debug()
        << "Reached end of bam source.\n";
    }
    return 0;
  }

  // Now extract the object definition from the datagram.
  DatagramIterator scan(dg);

  // First, read the BamObjectCode.  In bam versions prior to 6.21,
  // there was no BamObjectCode in the stream.
  BamObjectCode boc = BOC_adjunct;
  if (get_file_minor_ver() >= 21) {
    boc = (BamObjectCode)scan.get_uint8();
  }
  switch (boc) {
  case BOC_push:
    ++_nesting_level;
    break;

  case BOC_pop:
    --_nesting_level;
    return 0;

  case BOC_adjunct:
    break;

  case BOC_remove:
    // The BOC_remove code is a special case; it begins a
    // record that simply lists all of the object ID's that are no
    // longer important to the file and may be released.
    free_object_ids(scan);

    // Now that we've freed all of the object id's indicate, read the
    // next object id in the stream.  It's easiest to do this by
    // calling recursively.
    return p_read_object();

  case BOC_file_data:
    // Another special case.  This marks an auxiliary file data record
    // that we skip over for now, but we note its position within the
    // stream, so that we can hand it to a future object who may
    // request it.
    {
      SubfileInfo info;
      if (!_source->save_datagram(info)) {
        bam_cat.error()
          << "Failed to read file data.\n";
        return 0;
      }
      _file_data_records.push_back(info);
    }

    return p_read_object();
  }

  // An object definition in a Bam file consists of a TypeHandle
  // definition, defining the object's type, followed by an object ID
  // index, defining the particular instance (e.g. pointer) of this
  // object.
  
  TypeHandle type = read_handle(scan);

  int object_id = read_object_id(scan);

  // There are two cases (not counting the special _remove_flag case,
  // above).  Either this is a new object definition, or this is a
  // reference to an object that was previously defined.

  // We use the TypeHandle to differentiate these two cases.  By
  // convention, we write a TypeHandle::none() to the Bam file when we
  // are writing a reference to a previously-defined object, but we
  // write the object's actual type when we are writing its definition
  // right now.

  // Therefore, if the type is TypeHandle::none(), then we must have
  // already read in and created the object (although its pointers may
  // not be fully instantiated yet).  On the other hand, if the type
  // is anything else, then we must read the definition to follow.

  if (type != TypeHandle::none()) {
    // Now we are going to read and create a new object.

    // First, we must add an entry into the map for this object ID, so
    // that in case this function is called recursively during the
    // object's factory constructor, we will have some definition for
    // the object.  For now, we give it a NULL pointer.
    CreatedObj new_created_obj;
    CreatedObjs::iterator oi =
      _created_objs.insert(CreatedObjs::value_type(object_id, new_created_obj)).first;
    CreatedObj &created_obj = (*oi).second;

    if (created_obj._ptr != NULL) {
      // This object had already existed; thus, we are just receiving
      // an update for it.

      // Update _now_creating during this call so if this function
      // calls read_pointer() or register_change_this() we'll match it
      // up properly.  This might recursively call back into this
      // p_read_object(), so be sure to save and restore the original
      // value of _now_creating.
      CreatedObjs::iterator was_creating = _now_creating;
      _now_creating = oi;
      created_obj._ptr->fillin(scan, this);
      _now_creating = was_creating;

    } else {
      // We are receiving a new object.  Now we can call the factory
      // to create the object.

      // Define the parameters for passing to the object factory.
      FactoryParams fparams;
      fparams.add_param(new BamReaderParam(scan, this));
      
      // As above, we update and preserve _now_creating during this
      // call.
      CreatedObjs::iterator was_creating = _now_creating;
      _now_creating = oi;
      TypedWritable *object =
        _factory->make_instance_more_general(type, fparams);
      _now_creating = was_creating;
      
      // And now we can store the new object pointer in the map.
      nassertr(created_obj._ptr == object || created_obj._ptr == NULL, object_id);
      if (object == NULL) {
        created_obj.set_ptr(NULL, NULL);
      } else {
        created_obj.set_ptr(object, object->as_reference_count());
      }
      created_obj._created = true;

      if (created_obj._change_this_ref != NULL) {
        // If the pointer is scheduled to change after
        // complete_pointers(), but we have no entry in
        // _object_pointers for this object (and hence no plan to call
        // complete_pointers()), then just change the pointer
        // immediately.
        ObjectPointers::const_iterator ri = _object_pointers.find(object_id);
        if (ri == _object_pointers.end()) {
          PT(TypedWritableReferenceCount) object_ref = (*created_obj._change_this_ref)((TypedWritableReferenceCount *)object, this);
          TypedWritable *new_ptr = object_ref;
          created_obj.set_ptr(object_ref, object_ref);
          created_obj._change_this = NULL;
          created_obj._change_this_ref = NULL;
          
          // Remove the pointer from the finalize list (the new
          // pointer presumably doesn't require finalizing).
          if (new_ptr != object) {
            _finalize_list.erase(object);
          }
          object = new_ptr;
        }
        
      } else if (created_obj._change_this != NULL) {
        // Non-reference-counting variant.
        ObjectPointers::const_iterator ri = _object_pointers.find(object_id);
        if (ri == _object_pointers.end()) {
          TypedWritable *new_ptr = (*created_obj._change_this)(object, this);
          created_obj.set_ptr(new_ptr, new_ptr->as_reference_count());
          created_obj._change_this = NULL;
          created_obj._change_this_ref = NULL;

          if (new_ptr != object) {
            _finalize_list.erase(object);
          }
          object = new_ptr;
        }
      }
      
      _created_objs_by_pointer[created_obj._ptr].push_back(object_id);

      // Just some sanity checks
      if (object == (TypedWritable *)NULL) {
        if (bam_cat.is_debug()) {
          bam_cat.debug()
            << "Unable to create an object of type " << type << endl;
        }

      } else if (object->get_type() != type) {
        if (_new_types.find(type) != _new_types.end()) {
          // This was a type we hadn't heard of before, so it's not
          // really surprising we didn't know how to create it.
          // Suppress the warning (make it a debug statement instead).
          if (bam_cat.is_debug()) {
            bam_cat.warning()
              << "Attempted to create a " << type.get_name()    \
              << " but a " << object->get_type()                \
              << " was created instead." << endl;
          }
          
        } else {
          // This was a normal type that we should have known how to
          // create.  Report the error.
          bam_cat.warning()
            << "Attempted to create a " << type.get_name()      \
            << " but a " << object->get_type()                  \
            << " was created instead." << endl;
        }
        
      } else {
        if (bam_cat.is_spam()) {
          bam_cat.spam()
            << "Read a " << object->get_type() << ": " << (void *)object << "\n";
        }
      }
    }
  }

  return object_id;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::resolve_object_pointers
//       Access: Private
//  Description: Checks whether all of the pointers a particular
//               object is waiting for have been filled in yet.  If
//               they have, calls complete_pointers() on the object
//               and returns true; otherwise, returns false.
////////////////////////////////////////////////////////////////////
bool BamReader::
resolve_object_pointers(TypedWritable *object, 
                        BamReader::PointerReference &pref) {
  // Some objects further require all of their nested objects to have
  // been completed (i.e. complete_pointers has been called on each
  // nested object) before they can themselves be completed.
  bool require_fully_complete = object->require_fully_complete();

  // First do the PipelineCycler objects.    
  CyclerPointers::iterator ci;
  ci = pref._cycler_pointers.begin();
  while (ci != pref._cycler_pointers.end()) {
    PipelineCyclerBase *cycler = (*ci).first;
    const vector_int &pointer_ids = (*ci).second;
      
    if (resolve_cycler_pointers(cycler, pointer_ids, require_fully_complete)) {
      // Now remove this cycler from the list of things that need
      // completion.  We have to be a bit careful when deleting things
      // from the STL container while we are traversing it.
      CyclerPointers::iterator old = ci;
      ++ci;
      pref._cycler_pointers.erase(old);
      
    } else {
      // Couldn't complete this cycler yet; it'll wait for next time.
      ++ci;
    }
  }

  if (!pref._cycler_pointers.empty()) {
    // If we didn't get all the cyclers, we have to wait.
    if (bam_cat.is_spam()) {
      bam_cat.spam()
        << "some cyclers pending: complete_pointers for " << (void *)object
        << " (" << object->get_type() << ")\n";
    }
    return false;
  }
  
  // Now make sure we have all of the pointers this object is
  // waiting for.  If any of the pointers has not yet been read
  // in, we can't resolve this object--we can't do anything for a
  // given object until we have *all* outstanding pointers for
  // that object.
  bool is_complete = true;

  vector_typedWritable references;
  references.reserve(pref._objects.size());

  vector_int::const_iterator pi;
  for (pi = pref._objects.begin(); 
       pi != pref._objects.end() && is_complete; 
       ++pi) {
    int child_id = (*pi);
    if (child_id == 0) {
      // A NULL pointer is a NULL pointer.
      references.push_back((TypedWritable *)NULL);
      
    } else {
      // See if we have the pointer available now.
      CreatedObjs::const_iterator oi = _created_objs.find(child_id);
      if (oi == _created_objs.end()) {
        // No, too bad.
        is_complete = false;

      } else {
        const CreatedObj &child_obj = (*oi).second;
        if (!child_obj._created) {
          // The child object hasn't yet been created.
          is_complete = false;
        } else if (child_obj._change_this != NULL || child_obj._change_this_ref != NULL) {
          // It's been created, but the pointer might still change.
          is_complete = false;
        } else {
          if (require_fully_complete && 
              _object_pointers.find(child_id) != _object_pointers.end()) {
            // It's not yet complete itself.
            is_complete = false;
            
          } else {
            // Yes, it's ready.
            references.push_back(child_obj._ptr);
          }
        }
      }
    }
  }
      
  if (is_complete) {
    // Okay, here's the complete list of pointers for you!
    nassertr(references.size() == pref._objects.size(), false);

    if (bam_cat.is_spam()) {
      bam_cat.spam()
        << "complete_pointers for " << (void *)object
        << " (" << object->get_type() << "), " << references.size()
        << " pointers.\n";
    }
    int num_completed = 0;
    if (!references.empty()) {
      num_completed = object->complete_pointers(&references[0], this);
    }
    if (num_completed != (int)references.size()) {
      bam_cat.warning()
        << object->get_type() << " completed " << num_completed
        << " of " << references.size() << " pointers.\n";
      nassertr(num_completed < (int)references.size(), true);
    }
    return true;

  } else {
    if (bam_cat.is_spam()) {
      bam_cat.spam()
        << "not ready: complete_pointers for " << (void *)object
        << " (" << object->get_type() << ")\n";
    }
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::resolve_cycler_pointers
//       Access: Private
//  Description: Checks whether all of the pointers a particular
//               PipelineCycler is waiting for have been filled in
//               yet.  If they have, calls complete_pointers() on the
//               cycler and returns true; otherwise, returns false.
////////////////////////////////////////////////////////////////////
bool BamReader::
resolve_cycler_pointers(PipelineCyclerBase *cycler,
                        const vector_int &pointer_ids,
                        bool require_fully_complete) {
  // Now make sure we have all of the pointers this cycler is
  // waiting for.  If any of the pointers has not yet been read
  // in, we can't resolve this cycler--we can't do anything for a
  // given cycler until we have *all* outstanding pointers for
  // that cycler.
  
  bool is_complete = true;
  vector_typedWritable references;
  
  vector_int::const_iterator pi;
  for (pi = pointer_ids.begin(); pi != pointer_ids.end() && is_complete; ++pi) {
    int child_id = (*pi);
    
    if (child_id == 0) {
      // A NULL pointer is a NULL pointer.
      references.push_back((TypedWritable *)NULL);
      
    } else {
      // See if we have the pointer available now.
      CreatedObjs::const_iterator oi = _created_objs.find(child_id);
      if (oi == _created_objs.end()) {
        // No, too bad.
        is_complete = false;

      } else {
        const CreatedObj &child_obj = (*oi).second;
        if (child_obj._change_this != NULL || child_obj._change_this_ref != NULL) {
          // It's been created, but the pointer might still change.
          is_complete = false;

        } else {
          if (require_fully_complete && 
              _object_pointers.find(child_id) != _object_pointers.end()) {
            // It's not yet complete itself.
            is_complete = false;
            
          } else {
            // Yes, it's ready.
            references.push_back(child_obj._ptr);
          }
        }
      }
    }
  }
      
  if (is_complete) {
    // Okay, here's the complete list of pointers for you!
    CycleData *cdata = cycler->write(Thread::get_current_thread());
    if (bam_cat.is_spam()) {
      bam_cat.spam()
        << "complete_pointers for CycleData object " << (void *)cdata
        << "\n";
    }
    int num_completed = cdata->complete_pointers(&references[0], this);
    cycler->release_write(cdata);
    if (num_completed != (int)references.size()) {
      bam_cat.warning()
        << "CycleData object completed " << num_completed
        << " of " << references.size() << " pointers.\n";
      nassertr(num_completed < (int)references.size(), true);
    }
    return true;
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::finalize
//       Access: Private
//  Description: Should be called after all objects have been read,
//               this will finalize all the objects that registered
//               themselves for the finalize callback.
////////////////////////////////////////////////////////////////////
void BamReader::
finalize() {
  if (bam_cat.is_debug()) {
    bam_cat.debug()
      << "Finalizing bam source\n";
  }

  Finalize::iterator fi = _finalize_list.begin();
  while (fi != _finalize_list.end()) {
    TypedWritable *object = (*fi);
    nassertv(object != (TypedWritable *)NULL);
    _finalize_list.erase(fi);
    if (bam_cat.is_spam()) {
      bam_cat.spam()
        << "finalizing " << (void *)object << " (" << object->get_type()
        << ")\n";
    }
    object->finalize(this);
    _aux_data.erase(object);
    fi = _finalize_list.begin();
  }

  // Now clear the aux data of all objects, except the NULL object.
  if (!_aux_data.empty()) {
    AuxDataTable::iterator ti = _aux_data.find((TypedWritable *)NULL);

    if (ti != _aux_data.end()) {
      if (_aux_data.size() > 1) {
        // Move the NULL data to the new table; remove the rest.
        AuxDataTable new_aux_data;
        AuxDataTable::iterator nti = 
          new_aux_data.insert(AuxDataTable::value_type((TypedWritable *)NULL, AuxDataNames())).first;
        (*nti).second.swap((*ti).second);
        _aux_data.swap(new_aux_data);
      }
    } else {
      // There's no NULL data; clear the whole table.
      _aux_data.clear();
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: BamReader::AuxData::Destructor
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
BamReader::AuxData::
~AuxData() {
}

