open_toontown_panda3d/panda/src/putil/bamReader.cxx

1640 lines
59 KiB
C++

// 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() {
}