panda3d/panda/src/putil/bamReader.cxx
2002-03-08 00:42:13 +00:00

799 lines
28 KiB
C++

// Filename: bamReader.cxx
// Created by: jason (12Jun00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license. You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////
#include <pandabase.h>
#include <notify.h>
#include "bam.h"
#include "bamReader.h"
#include <datagramIterator.h>
#include "config_util.h"
WritableFactory *BamReader::_factory = (WritableFactory*)0L;
BamReader *const BamReader::Null = (BamReader*)0L;
WritableFactory *const BamReader::NullFactory = (WritableFactory*)0L;
const int BamReader::_cur_major = _bam_major_ver;
const int BamReader::_cur_minor = _bam_minor_ver;
////////////////////////////////////////////////////////////////////
// Function: BamReader::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
BamReader::
BamReader(DatagramGenerator *generator)
: _source(generator)
{
_num_extra_objects = 0;
_now_creating = _created_objs.end();
_pta_id = -1;
}
////////////////////////////////////////////////////////////////////
// Function: BamReader::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
BamReader::
~BamReader() {
}
////////////////////////////////////////////////////////////////////
// Function: BamReader::init
// Access: Public
// 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() {
Datagram header;
if (_source->is_error()) {
return false;
}
if (!_source->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_minor_ver) {
bam_cat.error()
<< "Bam file is version " << _file_major << "." << _file_minor
<< ".\n";
if (_bam_minor_ver == 0) {
bam_cat.error()
<< "This program can only load version "
<< _bam_major_ver << ".0 bams.\n";
} else {
bam_cat.error()
<< "This program can only load version "
<< _bam_major_ver << ".0 through "
<< _bam_major_ver << "." << _bam_minor_ver << " bams.\n";
}
return false;
}
if (bam_cat.is_debug()) {
bam_cat.debug()
<< "Bam file is version " << _file_major << "." << _file_minor
<< ".\n";
if (_file_minor != _bam_minor_ver) {
bam_cat.debug()
<< "(Current version is " << _bam_major_ver << "." << _bam_minor_ver
<< ".)\n";
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: BamReader::read_object
// Access: Public
// 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().
////////////////////////////////////////////////////////////////////
TypedWritable *BamReader::
read_object() {
nassertr(_num_extra_objects == 0, (TypedWritable *)NULL);
// 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.
while (_num_extra_objects > 0) {
p_read_object();
_num_extra_objects--;
}
// 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 NULL\n";
}
return (TypedWritable *)NULL;
}
CreatedObjs::iterator oi = _created_objs.find(object_id);
if (oi == _created_objs.end()) {
bam_cat.error()
<< "Undefined object encountered!\n";
return (TypedWritable *)NULL;
} else {
CreatedObj &created_obj = (*oi).second;
TypedWritable *object = created_obj._ptr;
if (bam_cat.is_spam()) {
if (object != (TypedWritable *)NULL) {
bam_cat.spam()
<< "Returning object of type " << object->get_type() << "\n";
}
}
if (created_obj._change_this != NULL) {
bam_cat.warning()
<< "Returning pointer to " << object->get_type()
<< " that might change.\n";
}
return object;
}
}
////////////////////////////////////////////////////////////////////
// Function: BamReader::resolve
// Access: Public
// 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 {
all_completed = true;
any_completed_this_pass = false;
// Walk through all the objects that still have outstanding pointers.
Requests::iterator ri;
ri = _deferred_pointers.begin();
while (ri != _deferred_pointers.end()) {
int object_id = (*ri).first;
const vector_int &children = (*ri).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;
// 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;
vector_int::const_iterator pi;
for (pi = children.begin(); pi != children.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) {
// It's been created, but the pointer might still change.
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!
int num_completed = object_ptr->complete_pointers(&references[0], this);
if (num_completed != (int)references.size()) {
bam_cat.warning()
<< object_ptr->get_type() << " completed " << num_completed
<< " of " << references.size() << " pointers.\n";
}
// 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.
Requests::iterator old = ri;
++ri;
_deferred_pointers.erase(old);
// Does the pointer need to change?
if (created_obj._change_this != NULL) {
created_obj._ptr = created_obj._change_this(object_ptr, this);
created_obj._change_this = NULL;
}
any_completed_this_pass = true;
} else {
// Couldn't complete this object yet; it'll wait for next time.
++ri;
all_completed = false;
}
}
} 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.
Requests::const_iterator ri;
for (ri = _deferred_pointers.begin();
ri != _deferred_pointers.end();
++ri) {
int object_id = (*ri).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::read_handle
// Access: Public
// 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 contains objects of unknown type: " << type << "\n";
new_type = true;
}
// 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) {
bam_cat.warning()
<< "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, passing in the datagram iterator and a
// pointer to their own object, i.e. 'this'.
//
// 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 time
// read_pointer() was called. It is then the calling
// object's responsibilty to store these pointers in the
// object properly.
////////////////////////////////////////////////////////////////////
void BamReader::
read_pointer(DatagramIterator &scan, TypedWritable *for_whom) {
nassertv(_now_creating != _created_objs.end());
int requestor_id = (*_now_creating).first;
/*
On reflection, we'll let this go undetected for now. Maybe we
should remove the this pointer from read_pointer() altogether.
#ifndef NDEBUG
// A bit of sanity checking here: we look up the object ID, and
// assign the "this" pointer into the record if it's not there
// already. Then we can verify the "this" pointer later.
CreatedObj &created_obj = (*_now_creating).second;
if (created_obj._ptr == (TypedWritable *)NULL) {
created_obj._ptr = for_whom;
} 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 == for_whom);
}
#endif // NDEBUG
*/
// Read the object ID, and associate it with the requesting object.
int object_id = scan.get_uint16();
_deferred_pointers[requestor_id].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) {
if (_created_objs.count(object_id) == 0) {
// If we don't already have an entry in the map for this object
// ID (that is, we haven't encountered this object before), we
// must remember to read the object definition later.
_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, TypedWritable *for_whom, int count) {
for (int i = 0; i < count; i++) {
read_pointer(scan, for_whom);
}
}
////////////////////////////////////////////////////////////////////
// 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) {
scan.get_uint16();
}
////////////////////////////////////////////////////////////////////
// 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 (like Characters)
// 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) {
if (whom == TypedWritable::Null) {
bam_cat.error() << "Can't register a null pointer to finalize!" << endl;
return;
}
_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._ptr = 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);
}
#endif // NDEBUG
created_obj._change_this = 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) {
nassertv(whom != (TypedWritable *)NULL);
Finalize::iterator fi = _finalize_list.find(whom);
if (fi != _finalize_list.end()) {
_finalize_list.erase(fi);
whom->finalize();
}
}
////////////////////////////////////////////////////////////////////
// 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 = scan.get_uint16();
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::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 packet;
if (_source->is_error()) {
return 0;
}
// First, read a datagram for the object.
if (!_source->get_datagram(packet)) {
// 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(packet);
// 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 = scan.get_uint16();
// There are two cases. 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.
// Defined the parameters for passing to the object factory.
FactoryParams fparams;
fparams.add_param(new BamReaderParam(scan, this));
// 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;
new_created_obj._ptr = NULL;
new_created_obj._change_this = NULL;
CreatedObjs::iterator oi =
_created_objs.insert(CreatedObjs::value_type(object_id, new_created_obj)).first;
CreatedObj &created_obj = (*oi).second;
// Now we can call the factory to create the object. 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;
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);
created_obj._ptr = object;
if (created_obj._change_this != NULL) {
// If the pointer is scheduled to change after
// complete_pointers(), but we have no entry in
// _deferred_pointers for this object (and hence no plan to call
// complete_pointers()), then just change the pointer
// immediately.
Requests::const_iterator ri = _deferred_pointers.find(object_id);
if (ri == _deferred_pointers.end()) {
object = created_obj._change_this(object, this);
created_obj._ptr = object;
created_obj._change_this = NULL;
}
}
//Just some sanity checks
if (object == (TypedWritable *)NULL) {
bam_cat.error()
<< "Unable to create an object of type " << type << endl;
} else if (object->get_type() != type) {
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() << "\n";
}
}
}
return object_id;
}
////////////////////////////////////////////////////////////////////
// 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);
object->finalize();
fi = _finalize_list.begin();
}
}