mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 02:42:49 -04:00
1338 lines
42 KiB
C++
Executable File
1338 lines
42 KiB
C++
Executable File
// Filename: dcPacker.cxx
|
|
// Created by: drose (15Jun04)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
|
//
|
|
// All use of this software is subject to the terms of the Panda 3d
|
|
// Software license. You should have received a copy of this license
|
|
// along with this source code; you will also find a current copy of
|
|
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
|
//
|
|
// To contact the maintainers of this program write to
|
|
// panda3d-general@lists.sourceforge.net .
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "dcPacker.h"
|
|
#include "dcSwitch.h"
|
|
#include "dcParserDefs.h"
|
|
#include "dcLexerDefs.h"
|
|
#include "dcClassParameter.h"
|
|
#include "dcSwitchParameter.h"
|
|
#include "dcClass.h"
|
|
|
|
DCPacker::StackElement *DCPacker::StackElement::_deleted_chain = NULL;
|
|
int DCPacker::StackElement::_num_ever_allocated = 0;
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::Constructor
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
DCPacker::
|
|
DCPacker() {
|
|
_mode = M_idle;
|
|
_unpack_data = NULL;
|
|
_unpack_length = 0;
|
|
_owns_unpack_data = false;
|
|
_unpack_p = 0;
|
|
_live_catalog = NULL;
|
|
_pack_error = false;
|
|
_range_error = false;
|
|
_stack = NULL;
|
|
|
|
clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::Destructor
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
DCPacker::
|
|
~DCPacker() {
|
|
clear_data();
|
|
clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::begin_pack
|
|
// Access: Published
|
|
// Description: Begins a packing session. The parameter is the DC
|
|
// object that describes the packing format; it may be a
|
|
// DCParameter or DCField.
|
|
//
|
|
// Unless you call clear_data() between sessions,
|
|
// multiple packing sessions will be concatenated
|
|
// together into the same buffer. If you wish to add
|
|
// bytes to the buffer between packing sessions, use
|
|
// append_data() or get_write_pointer().
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
begin_pack(const DCPackerInterface *root) {
|
|
nassertv(_mode == M_idle);
|
|
|
|
_mode = M_pack;
|
|
_pack_error = false;
|
|
_range_error = false;
|
|
|
|
_root = root;
|
|
_catalog = NULL;
|
|
_live_catalog = NULL;
|
|
|
|
_current_field = root;
|
|
_current_parent = NULL;
|
|
_current_field_index = 0;
|
|
_num_nested_fields = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::end_pack
|
|
// Access: Published, Virtual
|
|
// Description: Finishes a packing session.
|
|
//
|
|
// The return value is true on success, or false if
|
|
// there has been some error during packing.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool DCPacker::
|
|
end_pack() {
|
|
nassertr(_mode == M_pack, false);
|
|
|
|
_mode = M_idle;
|
|
|
|
if (_stack != NULL || _current_field != NULL || _current_parent != NULL) {
|
|
_pack_error = true;
|
|
}
|
|
|
|
clear();
|
|
|
|
return !had_error();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::set_unpack_data
|
|
// Access: Public
|
|
// Description: Sets up the unpack_data pointer. You may call this
|
|
// before calling the version of begin_unpack() that
|
|
// takes only one parameter.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
set_unpack_data(const string &data) {
|
|
nassertv(_mode == M_idle);
|
|
|
|
char *buffer = new char[data.length()];
|
|
memcpy(buffer, data.data(), data.length());
|
|
set_unpack_data(buffer, data.length(), true);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::set_unpack_data
|
|
// Access: Public
|
|
// Description: Sets up the unpack_data pointer. You may call this
|
|
// before calling the version of begin_unpack() that
|
|
// takes only one parameter.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
set_unpack_data(const char *unpack_data, size_t unpack_length,
|
|
bool owns_unpack_data) {
|
|
nassertv(_mode == M_idle);
|
|
|
|
if (_owns_unpack_data) {
|
|
delete[] _unpack_data;
|
|
}
|
|
_unpack_data = unpack_data;
|
|
_unpack_length = unpack_length;
|
|
_owns_unpack_data = owns_unpack_data;
|
|
_unpack_p = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::begin_unpack
|
|
// Access: Public
|
|
// Description: Begins an unpacking session. You must have
|
|
// previously called set_unpack_data() to specify a
|
|
// buffer to unpack.
|
|
//
|
|
// If there was data left in the buffer after a previous
|
|
// begin_unpack() .. end_unpack() session, the new
|
|
// session will resume from the current point. This
|
|
// method may be used, therefore, to unpack a sequence
|
|
// of objects from the same buffer.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
begin_unpack(const DCPackerInterface *root) {
|
|
nassertv(_mode == M_idle);
|
|
nassertv(_unpack_data != NULL);
|
|
|
|
_mode = M_unpack;
|
|
_pack_error = false;
|
|
_range_error = false;
|
|
|
|
_root = root;
|
|
_catalog = NULL;
|
|
_live_catalog = NULL;
|
|
|
|
_current_field = root;
|
|
_current_parent = NULL;
|
|
_current_field_index = 0;
|
|
_num_nested_fields = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::end_unpack
|
|
// Access: Published
|
|
// Description: Finishes the unpacking session.
|
|
//
|
|
// The return value is true on success, or false if
|
|
// there has been some error during unpacking (or if all
|
|
// fields have not been unpacked).
|
|
////////////////////////////////////////////////////////////////////
|
|
bool DCPacker::
|
|
end_unpack() {
|
|
nassertr(_mode == M_unpack, false);
|
|
|
|
_mode = M_idle;
|
|
|
|
if (_stack != NULL || _current_field != NULL || _current_parent != NULL) {
|
|
// This happens if we have not unpacked all of the fields.
|
|
// However, this is not an error if we have called seek() during
|
|
// the unpack session (in which case the _catalog will be
|
|
// non-NULL). On the other hand, if the catalog is still NULL,
|
|
// then we have never called seek() and it is an error not to
|
|
// unpack all values.
|
|
if (_catalog == (DCPackerCatalog *)NULL) {
|
|
_pack_error = true;
|
|
}
|
|
}
|
|
|
|
clear();
|
|
|
|
return !had_error();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::begin_repack
|
|
// Access: Public
|
|
// Description: Begins a repacking session. You must have previously
|
|
// called set_unpack_data() to specify a buffer to
|
|
// unpack.
|
|
//
|
|
// Unlike begin_pack() or begin_unpack() you may not
|
|
// concatenate the results of multiple begin_repack()
|
|
// sessions in one buffer.
|
|
//
|
|
// Also, unlike in packing or unpacking modes, you may
|
|
// not walk through the fields from beginning to end, or
|
|
// even pack two consecutive fields at once. Instead,
|
|
// you must call seek() for each field you wish to
|
|
// modify and pack only that one field; then call seek()
|
|
// again to modify another field.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
begin_repack(const DCPackerInterface *root) {
|
|
nassertv(_mode == M_idle);
|
|
nassertv(_unpack_data != NULL);
|
|
nassertv(_unpack_p == 0);
|
|
|
|
_mode = M_repack;
|
|
_pack_error = false;
|
|
_range_error = false;
|
|
_pack_data.clear();
|
|
|
|
// In repack mode, we immediately get the catalog, since we know
|
|
// we'll need it.
|
|
_root = root;
|
|
_catalog = _root->get_catalog();
|
|
_live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length);
|
|
if (_live_catalog == NULL) {
|
|
_pack_error = true;
|
|
}
|
|
|
|
// We don't begin at the first field in repack mode. Instead, you
|
|
// must explicitly call seek().
|
|
_current_field = NULL;
|
|
_current_parent = NULL;
|
|
_current_field_index = 0;
|
|
_num_nested_fields = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::end_repack
|
|
// Access: Published
|
|
// Description: Finishes the repacking session.
|
|
//
|
|
// The return value is true on success, or false if
|
|
// there has been some error during repacking (or if all
|
|
// fields have not been repacked).
|
|
////////////////////////////////////////////////////////////////////
|
|
bool DCPacker::
|
|
end_repack() {
|
|
nassertr(_mode == M_repack, false);
|
|
|
|
// Put the rest of the data onto the pack stream.
|
|
_pack_data.append_data(_unpack_data + _unpack_p, _unpack_length - _unpack_p);
|
|
|
|
_mode = M_idle;
|
|
clear();
|
|
|
|
return !had_error();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::seek
|
|
// Access: Published
|
|
// Description: Sets the current unpack (or repack) position to the
|
|
// named field. In unpack mode, the next call to
|
|
// unpack_*() or push() will begin to read the named
|
|
// field. In repack mode, the next call to pack_*() or
|
|
// push() will modify the named field.
|
|
//
|
|
// Returns true if successful, false if the field is not
|
|
// known (or if the packer is in an invalid mode).
|
|
////////////////////////////////////////////////////////////////////
|
|
bool DCPacker::
|
|
seek(const string &field_name) {
|
|
if (_catalog == (DCPackerCatalog *)NULL) {
|
|
_catalog = _root->get_catalog();
|
|
_live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length);
|
|
}
|
|
nassertr(_catalog != (DCPackerCatalog *)NULL, false);
|
|
if (_live_catalog == NULL) {
|
|
_pack_error = true;
|
|
return false;
|
|
}
|
|
|
|
int seek_index = _live_catalog->find_entry_by_name(field_name);
|
|
if (seek_index < 0) {
|
|
// The field was not known.
|
|
_pack_error = true;
|
|
return false;
|
|
}
|
|
|
|
return seek(seek_index);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::seek
|
|
// Access: Published
|
|
// Description: Seeks to the field indentified by seek_index, which
|
|
// was returned by an earlier call to
|
|
// DCField::find_seek_index() to get the index of some
|
|
// nested field. Also see the version of seek() that
|
|
// accepts a field name.
|
|
//
|
|
// Returns true if successful, false if the field is not
|
|
// known (or if the packer is in an invalid mode).
|
|
////////////////////////////////////////////////////////////////////
|
|
bool DCPacker::
|
|
seek(int seek_index) {
|
|
if (_catalog == (DCPackerCatalog *)NULL) {
|
|
_catalog = _root->get_catalog();
|
|
_live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length);
|
|
}
|
|
nassertr(_catalog != (DCPackerCatalog *)NULL, false);
|
|
if (_live_catalog == NULL) {
|
|
_pack_error = true;
|
|
return false;
|
|
}
|
|
|
|
if (_mode == M_unpack) {
|
|
const DCPackerCatalog::Entry &entry = _live_catalog->get_entry(seek_index);
|
|
|
|
// If we are seeking, we don't need to remember our current stack
|
|
// position.
|
|
clear_stack();
|
|
_current_field = entry._field;
|
|
_current_parent = entry._parent;
|
|
_current_field_index = entry._field_index;
|
|
_num_nested_fields = _current_parent->get_num_nested_fields();
|
|
_unpack_p = _live_catalog->get_begin(seek_index);
|
|
|
|
// We don't really need _push_marker and _pop_marker now, except
|
|
// that we should set _push_marker in case we have just seeked to
|
|
// a switch parameter, and we should set _pop_marker to 0 just so
|
|
// it won't get in the way.
|
|
_push_marker = _unpack_p;
|
|
_pop_marker = 0;
|
|
|
|
return true;
|
|
|
|
} else if (_mode == M_repack) {
|
|
nassertr(_catalog != (DCPackerCatalog *)NULL, false);
|
|
|
|
if (_stack != NULL || _current_field != NULL) {
|
|
// It is an error to reseek while the stack is nonempty--that
|
|
// means we haven't finished packing the current field.
|
|
_pack_error = true;
|
|
return false;
|
|
}
|
|
const DCPackerCatalog::Entry &entry = _live_catalog->get_entry(seek_index);
|
|
|
|
if (entry._parent->as_switch_parameter() != (DCSwitchParameter *)NULL) {
|
|
// If the parent is a DCSwitch, that can only mean that the
|
|
// seeked field is a switch parameter. We can't support seeking
|
|
// to a switch parameter and modifying it directly--what would
|
|
// happen to all of the related fields? Instead, you'll have to
|
|
// seek to the switch itself and repack the whole entity.
|
|
_pack_error = true;
|
|
return false;
|
|
}
|
|
|
|
size_t begin = _live_catalog->get_begin(seek_index);
|
|
if (begin < _unpack_p) {
|
|
// Whoops, we are seeking fields out-of-order. That means we
|
|
// need to write the entire record and start again.
|
|
_pack_data.append_data(_unpack_data + _unpack_p, _unpack_length - _unpack_p);
|
|
size_t length = _pack_data.get_length();
|
|
char *buffer = _pack_data.take_data();
|
|
set_unpack_data(buffer, length, true);
|
|
_unpack_p = 0;
|
|
|
|
_catalog->release_live_catalog(_live_catalog);
|
|
_live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length);
|
|
|
|
if (_live_catalog == NULL) {
|
|
_pack_error = true;
|
|
return false;
|
|
}
|
|
|
|
begin = _live_catalog->get_begin(seek_index);
|
|
}
|
|
|
|
// Now copy the bytes from _unpack_p to begin from the
|
|
// _unpack_data to the _pack_data. These are the bytes we just
|
|
// skipped over with the call to seek().
|
|
_pack_data.append_data(_unpack_data + _unpack_p, begin - _unpack_p);
|
|
|
|
// And set the packer up to pack the indicated field (but no
|
|
// subsequent fields).
|
|
_current_field = entry._field;
|
|
_current_parent = entry._parent;
|
|
_current_field_index = entry._field_index;
|
|
_num_nested_fields = 1;
|
|
_unpack_p = _live_catalog->get_end(seek_index);
|
|
|
|
// Set up push_marker and pop_marker so we won't try to advance
|
|
// beyond this field.
|
|
_push_marker = begin;
|
|
_pop_marker = _live_catalog->get_end(seek_index);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Invalid mode.
|
|
_pack_error = true;
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::push
|
|
// Access: Published
|
|
// Description: Marks the beginning of a nested series of fields.
|
|
//
|
|
// This must be called before filling the elements of an
|
|
// array or the individual fields in a structure field.
|
|
// It must also be balanced by a matching pop().
|
|
//
|
|
// It is necessary to use push() / pop() only if
|
|
// has_nested_fields() returns true.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
push() {
|
|
if (!has_nested_fields()) {
|
|
_pack_error = true;
|
|
|
|
} else {
|
|
StackElement *element = new StackElement;
|
|
element->_current_parent = _current_parent;
|
|
element->_current_field_index = _current_field_index;
|
|
element->_push_marker = _push_marker;
|
|
element->_pop_marker = _pop_marker;
|
|
element->_next = _stack;
|
|
_stack = element;
|
|
_current_parent = _current_field;
|
|
|
|
|
|
// Now deal with the length prefix that might or might not be
|
|
// before a sequence of nested fields.
|
|
int num_nested_fields = _current_parent->get_num_nested_fields();
|
|
size_t length_bytes = _current_parent->get_num_length_bytes();
|
|
|
|
if (_mode == M_pack || _mode == M_repack) {
|
|
// Reserve length_bytes for when we figure out what the length
|
|
// is.
|
|
_push_marker = _pack_data.get_length();
|
|
_pop_marker = 0;
|
|
_pack_data.append_junk(length_bytes);
|
|
|
|
} else if (_mode == M_unpack) {
|
|
// Read length_bytes to determine the end of this nested
|
|
// sequence.
|
|
_push_marker = _unpack_p;
|
|
_pop_marker = 0;
|
|
|
|
if (length_bytes != 0) {
|
|
if (_unpack_p + length_bytes > _unpack_length) {
|
|
_pack_error = true;
|
|
|
|
} else {
|
|
size_t length;
|
|
if (length_bytes == 4) {
|
|
length = DCPackerInterface::do_unpack_uint32
|
|
(_unpack_data + _unpack_p);
|
|
_unpack_p += 4;
|
|
} else {
|
|
length = DCPackerInterface::do_unpack_uint16
|
|
(_unpack_data + _unpack_p);
|
|
_unpack_p += 2;
|
|
}
|
|
_pop_marker = _unpack_p + length;
|
|
|
|
// The explicit length trumps the number of nested fields
|
|
// reported by get_num_nested_fields().
|
|
if (length == 0) {
|
|
num_nested_fields = 0;
|
|
} else {
|
|
num_nested_fields = _current_parent->calc_num_nested_fields(length);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
_pack_error = true;
|
|
}
|
|
|
|
|
|
// Now point to the first field in the nested range.
|
|
_num_nested_fields = num_nested_fields;
|
|
_current_field_index = 0;
|
|
|
|
if (_num_nested_fields >= 0 &&
|
|
_current_field_index >= _num_nested_fields) {
|
|
_current_field = NULL;
|
|
|
|
} else {
|
|
_current_field = _current_parent->get_nested_field(_current_field_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::pop
|
|
// Access: Published
|
|
// Description: Marks the end of a nested series of fields.
|
|
//
|
|
// This must be called to match a previous push() only
|
|
// after all the expected number of nested fields have
|
|
// been packed. It is an error to call it too early, or
|
|
// too late.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
pop() {
|
|
if (_current_field != NULL && _num_nested_fields >= 0) {
|
|
// Oops, didn't pack or unpack enough values.
|
|
_pack_error = true;
|
|
|
|
} else if (_mode == M_unpack && _pop_marker != 0 &&
|
|
_unpack_p != _pop_marker) {
|
|
// Didn't unpack the right number of values.
|
|
_pack_error = true;
|
|
}
|
|
|
|
if (_stack == NULL) {
|
|
// Unbalanced pop().
|
|
_pack_error = true;
|
|
|
|
} else {
|
|
if (!_current_parent->validate_num_nested_fields(_current_field_index)) {
|
|
// Incorrect number of nested elements.
|
|
_pack_error = true;
|
|
}
|
|
|
|
if (_mode == M_pack || _mode == M_repack) {
|
|
size_t length_bytes = _current_parent->get_num_length_bytes();
|
|
if (length_bytes != 0) {
|
|
// Now go back and fill in the length of the array.
|
|
size_t length = _pack_data.get_length() - _push_marker - length_bytes;
|
|
if (length_bytes == 4) {
|
|
DCPackerInterface::do_pack_uint32
|
|
(_pack_data.get_rewrite_pointer(_push_marker, 4), length);
|
|
} else {
|
|
DCPackerInterface::validate_uint_limits(length, 16, _range_error);
|
|
DCPackerInterface::do_pack_uint16
|
|
(_pack_data.get_rewrite_pointer(_push_marker, 2), length);
|
|
}
|
|
}
|
|
}
|
|
|
|
_current_field = _current_parent;
|
|
_current_parent = _stack->_current_parent;
|
|
_current_field_index = _stack->_current_field_index;
|
|
_push_marker = _stack->_push_marker;
|
|
_pop_marker = _stack->_pop_marker;
|
|
_num_nested_fields = (_current_parent == NULL) ? 0 : _current_parent->get_num_nested_fields();
|
|
|
|
StackElement *next = _stack->_next;
|
|
delete _stack;
|
|
_stack = next;
|
|
}
|
|
|
|
advance();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::pack_default_value
|
|
// Access: Published
|
|
// Description: Adds the default value for the current element into
|
|
// the stream. If no default has been set for the
|
|
// current element, creates a sensible default.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
pack_default_value() {
|
|
nassertv(_mode == M_pack || _mode == M_repack);
|
|
if (_current_field == NULL) {
|
|
_pack_error = true;
|
|
} else {
|
|
if (_current_field->pack_default_value(_pack_data, _pack_error)) {
|
|
advance();
|
|
|
|
} else {
|
|
// If the single field didn't know how to pack a default value,
|
|
// try packing nested fields.
|
|
push();
|
|
while (more_nested_fields()) {
|
|
pack_default_value();
|
|
}
|
|
pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::unpack_validate
|
|
// Access: Published
|
|
// Description: Internally unpacks the current numeric or string
|
|
// value and validates it against the type range limits,
|
|
// but does not return the value. If the current field
|
|
// contains nested fields, validates all of them.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
unpack_validate() {
|
|
nassertv(_mode == M_unpack);
|
|
if (_current_field == NULL) {
|
|
_pack_error = true;
|
|
|
|
} else {
|
|
if (_current_field->unpack_validate(_unpack_data, _unpack_length, _unpack_p,
|
|
_pack_error, _range_error)) {
|
|
advance();
|
|
} else {
|
|
// If the single field couldn't be validated, try validating
|
|
// nested fields.
|
|
push();
|
|
while (more_nested_fields()) {
|
|
unpack_validate();
|
|
}
|
|
pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::unpack_skip
|
|
// Access: Published
|
|
// Description: Skips the current field without unpacking it and
|
|
// advances to the next field. If the current field
|
|
// contains nested fields, skips all of them.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
unpack_skip() {
|
|
nassertv(_mode == M_unpack);
|
|
if (_current_field == NULL) {
|
|
_pack_error = true;
|
|
|
|
} else {
|
|
if (_current_field->unpack_skip(_unpack_data, _unpack_length, _unpack_p,
|
|
_pack_error)) {
|
|
advance();
|
|
|
|
} else {
|
|
// If the single field couldn't be skipped, try skipping nested fields.
|
|
push();
|
|
while (more_nested_fields()) {
|
|
unpack_skip();
|
|
}
|
|
pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_PYTHON
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::pack_object
|
|
// Access: Published
|
|
// Description: Packs the Python object of whatever type into the
|
|
// packer. Each numeric object and string object maps
|
|
// to the corresponding pack_value() call; a tuple or
|
|
// sequence maps to a push() followed by all of the
|
|
// tuple's contents followed by a pop().
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
pack_object(PyObject *object) {
|
|
nassertv(_mode == M_pack || _mode == M_repack);
|
|
|
|
if (PyInt_Check(object)) {
|
|
pack_int(PyInt_AS_LONG(object));
|
|
|
|
} else if (PyFloat_Check(object)) {
|
|
pack_double(PyFloat_AS_DOUBLE(object));
|
|
|
|
} else if (PyLong_Check(object)) {
|
|
pack_int64(PyLong_AsLongLong(object));
|
|
|
|
} else if (PyString_Check(object) || PyUnicode_Check(object)) {
|
|
char *buffer;
|
|
int length;
|
|
PyString_AsStringAndSize(object, &buffer, &length);
|
|
if (buffer) {
|
|
pack_string(string(buffer, length));
|
|
}
|
|
|
|
} else {
|
|
int size = PySequence_Size(object);
|
|
bool is_instance = false;
|
|
|
|
const DCClass *dclass = NULL;
|
|
const DCClassParameter *class_param = get_current_field()->as_class_parameter();
|
|
if (class_param != (DCClassParameter *)NULL) {
|
|
dclass = class_param->get_class();
|
|
|
|
if (dclass->has_class_def()) {
|
|
PyObject *class_def = dclass->get_class_def();
|
|
is_instance = (PyObject_IsInstance(object, dclass->get_class_def()) != 0);
|
|
Py_DECREF(class_def);
|
|
}
|
|
}
|
|
|
|
// If dclass is not NULL, the packer is expecting a class object.
|
|
// There are then two cases: (1) the user has supplied a matching
|
|
// class object, or (2) the user has supplied a sequence object.
|
|
// Unfortunately, it may be difficult to differentiate these two
|
|
// cases, since a class object may also be a sequence object.
|
|
|
|
// The rule to differentiate them is:
|
|
|
|
// (1) If the supplied class object is an instance of the expected
|
|
// class object, it is considered to be a class object.
|
|
|
|
// (2) Otherwise, if the supplied class object has a __len__()
|
|
// method (i.e. PySequence_Size() returns a number >= 0), then it
|
|
// is considered to be a sequence.
|
|
|
|
// (3) Otherwise, it is considered to be a class object.
|
|
|
|
if (dclass != (DCClass *)NULL && (is_instance || size < 0)) {
|
|
// The supplied object is either an instance of the expected
|
|
// class object, or the size is less than 0--this is case (1) or
|
|
// (3).
|
|
pack_class_object(dclass, object);
|
|
|
|
} else if (size >= 0) {
|
|
// The supplied object is not an instance of the expected class
|
|
// object, but it does have a size. This is case (2).
|
|
push();
|
|
int size = PySequence_Size(object);
|
|
for (int i = 0; i < size; i++) {
|
|
PyObject *element = PySequence_GetItem(object, i);
|
|
pack_object(element);
|
|
Py_DECREF(element);
|
|
}
|
|
pop();
|
|
|
|
} else {
|
|
// The supplied object does not return a valid size, and we
|
|
// weren't expecting a class parameter. This is none of the
|
|
// above, an error.
|
|
ostringstream strm;
|
|
PyObject *str = PyObject_Str(object);
|
|
strm << "Don't know how to pack object: "
|
|
<< PyString_AsString(str) << "\n";
|
|
Py_DECREF(str);
|
|
nassert_raise(strm.str());
|
|
_pack_error = true;
|
|
}
|
|
}
|
|
}
|
|
#endif // HAVE_PYTHON
|
|
|
|
#ifdef HAVE_PYTHON
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::unpack_object
|
|
// Access: Published
|
|
// Description: Unpacks a Python object of the appropriate type from
|
|
// the stream for the current field. This may be an
|
|
// integer or a string for a simple field object; if the
|
|
// current field represents a list of fields it will be
|
|
// a tuple.
|
|
////////////////////////////////////////////////////////////////////
|
|
PyObject *DCPacker::
|
|
unpack_object() {
|
|
PyObject *object = NULL;
|
|
|
|
DCPackType pack_type = get_pack_type();
|
|
|
|
switch (pack_type) {
|
|
case PT_invalid:
|
|
object = Py_None;
|
|
unpack_skip();
|
|
break;
|
|
|
|
case PT_double:
|
|
{
|
|
double value = unpack_double();
|
|
object = PyFloat_FromDouble(value);
|
|
}
|
|
break;
|
|
|
|
case PT_int:
|
|
{
|
|
int value = unpack_int();
|
|
object = PyInt_FromLong(value);
|
|
}
|
|
break;
|
|
|
|
case PT_uint:
|
|
{
|
|
unsigned int value = unpack_uint();
|
|
if (value & 0x80000000) {
|
|
object = PyLong_FromUnsignedLong(value);
|
|
} else {
|
|
object = PyInt_FromLong(value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PT_int64:
|
|
{
|
|
PN_int64 value = unpack_int64();
|
|
object = PyLong_FromLongLong(value);
|
|
}
|
|
break;
|
|
|
|
case PT_uint64:
|
|
{
|
|
PN_uint64 value = unpack_uint64();
|
|
object = PyLong_FromUnsignedLongLong(value);
|
|
}
|
|
break;
|
|
|
|
case PT_string:
|
|
case PT_blob:
|
|
{
|
|
string str;
|
|
unpack_string(str);
|
|
object = PyString_FromStringAndSize(str.data(), str.size());
|
|
}
|
|
break;
|
|
|
|
case PT_class:
|
|
{
|
|
const DCClassParameter *class_param = get_current_field()->as_class_parameter();
|
|
if (class_param != (DCClassParameter *)NULL) {
|
|
const DCClass *dclass = class_param->get_class();
|
|
if (dclass->has_class_def()) {
|
|
// If we know what kind of class object this is and it has a
|
|
// valid constructor, create the class object instead of
|
|
// just a tuple.
|
|
object = unpack_class_object(dclass);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// If we don't know what kind of class object it is, or it doesn't
|
|
// have a constructor, fall through and make a tuple.
|
|
default:
|
|
{
|
|
// First, build up a list from the nested objects.
|
|
object = PyList_New(0);
|
|
|
|
push();
|
|
while (more_nested_fields()) {
|
|
PyObject *element = unpack_object();
|
|
PyList_Append(object, element);
|
|
Py_DECREF(element);
|
|
}
|
|
pop();
|
|
|
|
if (pack_type != PT_array) {
|
|
// For these other kinds of objects, we'll convert the list
|
|
// into a tuple.
|
|
PyObject *tuple = PyList_AsTuple(object);
|
|
Py_DECREF(object);
|
|
object = tuple;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return object;
|
|
}
|
|
#endif // HAVE_PYTHON
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::parse_and_pack
|
|
// Access: Published
|
|
// Description: Parses an object's value according to the DC file
|
|
// syntax (e.g. as a default value string) and packs it.
|
|
// Returns true on success, false on a parse error.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool DCPacker::
|
|
parse_and_pack(const string &formatted_object) {
|
|
istringstream strm(formatted_object);
|
|
return parse_and_pack(strm);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::parse_and_pack
|
|
// Access: Published
|
|
// Description: Parses an object's value according to the DC file
|
|
// syntax (e.g. as a default value string) and packs it.
|
|
// Returns true on success, false on a parse error.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool DCPacker::
|
|
parse_and_pack(istream &in) {
|
|
dc_init_parser_parameter_value(in, "parse_and_pack", *this);
|
|
dcyyparse();
|
|
dc_cleanup_parser();
|
|
|
|
return (dc_error_count() == 0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::unpack_and_format
|
|
// Access: Published
|
|
// Description: Unpacks an object and formats its value into a syntax
|
|
// suitable for parsing in the dc file (e.g. as a
|
|
// default value), or as an input to parse_object.
|
|
////////////////////////////////////////////////////////////////////
|
|
string DCPacker::
|
|
unpack_and_format() {
|
|
ostringstream strm;
|
|
unpack_and_format(strm);
|
|
return strm.str();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::unpack_and_format
|
|
// Access: Published
|
|
// Description: Unpacks an object and formats its value into a syntax
|
|
// suitable for parsing in the dc file (e.g. as a
|
|
// default value), or as an input to parse_object.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
unpack_and_format(ostream &out) {
|
|
DCPackType pack_type = get_pack_type();
|
|
|
|
switch (pack_type) {
|
|
case PT_invalid:
|
|
out << "<invalid>";
|
|
break;
|
|
|
|
case PT_double:
|
|
out << unpack_double();
|
|
break;
|
|
|
|
case PT_int:
|
|
out << unpack_int();
|
|
break;
|
|
|
|
case PT_uint:
|
|
out << unpack_uint();
|
|
break;
|
|
|
|
case PT_int64:
|
|
out << unpack_int64();
|
|
break;
|
|
|
|
case PT_uint64:
|
|
out << unpack_uint64();
|
|
break;
|
|
|
|
case PT_string:
|
|
enquote_string(out, '"', unpack_string());
|
|
break;
|
|
|
|
case PT_blob:
|
|
output_hex_string(out, unpack_literal_value());
|
|
break;
|
|
|
|
default:
|
|
{
|
|
switch (pack_type) {
|
|
case PT_array:
|
|
out << '[';
|
|
break;
|
|
|
|
case PT_field:
|
|
case PT_switch:
|
|
out << '(';
|
|
break;
|
|
|
|
case PT_class:
|
|
default:
|
|
out << '{';
|
|
break;
|
|
}
|
|
|
|
push();
|
|
while (more_nested_fields() && !had_pack_error()) {
|
|
unpack_and_format(out);
|
|
|
|
if (more_nested_fields()) {
|
|
out << ", ";
|
|
}
|
|
}
|
|
pop();
|
|
|
|
switch (pack_type) {
|
|
case PT_array:
|
|
out << ']';
|
|
break;
|
|
|
|
case PT_field:
|
|
case PT_switch:
|
|
out << ')';
|
|
break;
|
|
|
|
case PT_class:
|
|
default:
|
|
out << '}';
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::enquote_string
|
|
// Access: Public, Static
|
|
// Description: Outputs the indicated string within quotation marks.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
enquote_string(ostream &out, char quote_mark, const string &str) {
|
|
out << quote_mark;
|
|
for (string::const_iterator pi = str.begin();
|
|
pi != str.end();
|
|
++pi) {
|
|
if ((*pi) == quote_mark || (*pi) == '\\') {
|
|
out << '\\' << (*pi);
|
|
|
|
} else if (!isprint(*pi)) {
|
|
char buffer[10];
|
|
sprintf(buffer, "%02x", (unsigned char)(*pi));
|
|
out << "\\x" << buffer;
|
|
|
|
} else {
|
|
out << (*pi);
|
|
}
|
|
}
|
|
out << quote_mark;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::output_hex_string
|
|
// Access: Public, Static
|
|
// Description: Outputs the indicated string as a hex constant.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
output_hex_string(ostream &out, const string &str) {
|
|
out << '<';
|
|
for (string::const_iterator pi = str.begin();
|
|
pi != str.end();
|
|
++pi) {
|
|
char buffer[10];
|
|
sprintf(buffer, "%02x", (unsigned char)(*pi));
|
|
out << buffer;
|
|
}
|
|
out << '>';
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::handle_switch
|
|
// Access: Private
|
|
// Description: When we advance past the key field on a switch
|
|
// record, we suddenly have more fields available--all
|
|
// the appropriate alternate fields in the switch.
|
|
//
|
|
// This function is called when we detect this
|
|
// condition; it switches the _current_parent to the
|
|
// appropriate case of the switch record.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
handle_switch(const DCSwitchParameter *switch_parameter) {
|
|
// First, get the value from the key. This is either found in the
|
|
// unpack or the pack data, depending on what mode we're in.
|
|
const DCPackerInterface *new_parent = NULL;
|
|
|
|
if (_mode == M_pack || _mode == M_repack) {
|
|
const char *data = _pack_data.get_data();
|
|
new_parent = switch_parameter->apply_switch
|
|
(data + _push_marker, _pack_data.get_length() - _push_marker);
|
|
|
|
} else if (_mode == M_unpack) {
|
|
new_parent = switch_parameter->apply_switch
|
|
(_unpack_data + _push_marker, _unpack_p - _push_marker);
|
|
}
|
|
|
|
if (new_parent == (DCPackerInterface *)NULL) {
|
|
// This means an invalid value was packed for the key.
|
|
_pack_error = true;
|
|
return;
|
|
}
|
|
|
|
_last_switch = switch_parameter;
|
|
|
|
// Now substitute in the switch case for the previous parent (which
|
|
// replaces the switch node itself). This will suddenly make a slew
|
|
// of new fields appear.
|
|
_current_parent = new_parent;
|
|
_num_nested_fields = _current_parent->get_num_nested_fields();
|
|
|
|
if (_num_nested_fields < 0 ||
|
|
_current_field_index < _num_nested_fields) {
|
|
_current_field = _current_parent->get_nested_field(_current_field_index);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::clear
|
|
// Access: Private
|
|
// Description: Resets the data structures after a pack or unpack
|
|
// sequence.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
clear() {
|
|
clear_stack();
|
|
_current_field = NULL;
|
|
_current_parent = NULL;
|
|
_current_field_index = 0;
|
|
_num_nested_fields = 0;
|
|
_push_marker = 0;
|
|
_pop_marker = 0;
|
|
_last_switch = NULL;
|
|
|
|
if (_live_catalog != (DCPackerCatalog::LiveCatalog *)NULL) {
|
|
_catalog->release_live_catalog(_live_catalog);
|
|
_live_catalog = NULL;
|
|
}
|
|
_catalog = NULL;
|
|
_root = NULL;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::clear_stack
|
|
// Access: Private
|
|
// Description: Empties the stack.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
clear_stack() {
|
|
while (_stack != (StackElement *)NULL) {
|
|
StackElement *next = _stack->_next;
|
|
delete _stack;
|
|
_stack = next;
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_PYTHON
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::pack_class_object
|
|
// Access: Private
|
|
// Description: Given that the current element is a ClassParameter
|
|
// for a Python class object, try to extract the
|
|
// appropriate values from the class object and pack in.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
pack_class_object(const DCClass *dclass, PyObject *object) {
|
|
PyObject *str = PyObject_Str(object);
|
|
Py_DECREF(str);
|
|
push();
|
|
while (more_nested_fields() && !_pack_error) {
|
|
const DCField *field = get_current_field()->as_field();
|
|
nassertv(field != (DCField *)NULL);
|
|
get_class_element(dclass, object, field);
|
|
}
|
|
pop();
|
|
}
|
|
#endif // HAVE_PYTHON
|
|
|
|
#ifdef HAVE_PYTHON
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::unpack_class_object
|
|
// Access: Private
|
|
// Description: Given that the current element is a ClassParameter
|
|
// for a Python class for which we have a valid
|
|
// constructor, unpack it and fill in its values.
|
|
////////////////////////////////////////////////////////////////////
|
|
PyObject *DCPacker::
|
|
unpack_class_object(const DCClass *dclass) {
|
|
PyObject *class_def = dclass->get_class_def();
|
|
nassertr(class_def != (PyObject *)NULL, NULL);
|
|
|
|
PyObject *object = NULL;
|
|
|
|
if (!dclass->has_constructor()) {
|
|
// If the class uses a default constructor, go ahead and create
|
|
// the Python object for it now.
|
|
object = PyObject_CallObject(class_def, NULL);
|
|
if (object == (PyObject *)NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
push();
|
|
if (object == (PyObject *)NULL && more_nested_fields()) {
|
|
// The first nested field will be the constructor.
|
|
const DCField *field = get_current_field()->as_field();
|
|
nassertr(field != (DCField *)NULL, object);
|
|
nassertr(field == dclass->get_constructor(), object);
|
|
|
|
set_class_element(class_def, object, field);
|
|
|
|
// By now, the object should have been constructed.
|
|
if (object == (PyObject *)NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
while (more_nested_fields()) {
|
|
const DCField *field = get_current_field()->as_field();
|
|
nassertr(field != (DCField *)NULL, object);
|
|
|
|
set_class_element(class_def, object, field);
|
|
}
|
|
pop();
|
|
|
|
return object;
|
|
}
|
|
#endif // HAVE_PYTHON
|
|
|
|
|
|
#ifdef HAVE_PYTHON
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::set_class_element
|
|
// Access: Private
|
|
// Description: Unpacks the current element and stuffs it on the
|
|
// Python class object in whatever way is appropriate.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
set_class_element(PyObject *class_def, PyObject *&object,
|
|
const DCField *field) {
|
|
string field_name = field->get_name();
|
|
DCPackType pack_type = get_pack_type();
|
|
|
|
if (field_name.empty()) {
|
|
switch (pack_type) {
|
|
case PT_class:
|
|
case PT_switch:
|
|
// If the field has no name, but it is one of these container
|
|
// objects, we want to unpack its nested objects directly into
|
|
// the class.
|
|
push();
|
|
while (more_nested_fields()) {
|
|
const DCField *field = get_current_field()->as_field();
|
|
nassertv(field != (DCField *)NULL);
|
|
nassertv(object != (PyObject *)NULL);
|
|
set_class_element(class_def, object, field);
|
|
}
|
|
pop();
|
|
break;
|
|
|
|
default:
|
|
// Otherwise, we just skip over the field.
|
|
unpack_skip();
|
|
}
|
|
|
|
} else {
|
|
// If the field does have a name, we will want to store it on the
|
|
// class, either by calling a method (for a PT_field pack_type) or
|
|
// by setting a value (for any other kind of pack_type).
|
|
|
|
PyObject *element = unpack_object();
|
|
|
|
if (pack_type == PT_field) {
|
|
if (object == (PyObject *)NULL) {
|
|
// If the object hasn't been constructed yet, assume this is
|
|
// the constructor.
|
|
object = PyObject_CallObject(class_def, element);
|
|
|
|
} else {
|
|
if (PyObject_HasAttrString(object, (char *)field_name.c_str())) {
|
|
PyObject *func = PyObject_GetAttrString(object, (char *)field_name.c_str());
|
|
if (func != (PyObject *)NULL) {
|
|
PyObject *result = PyObject_CallObject(func, element);
|
|
Py_XDECREF(result);
|
|
Py_DECREF(func);
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
nassertv(object != (PyObject *)NULL);
|
|
PyObject_SetAttrString(object, (char *)field_name.c_str(), element);
|
|
}
|
|
|
|
Py_DECREF(element);
|
|
}
|
|
}
|
|
#endif // HAVE_PYTHON
|
|
|
|
|
|
#ifdef HAVE_PYTHON
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: DCPacker::get_class_element
|
|
// Access: Private
|
|
// Description: Gets the current element from the Python object and
|
|
// packs it.
|
|
////////////////////////////////////////////////////////////////////
|
|
void DCPacker::
|
|
get_class_element(const DCClass *dclass, PyObject *object,
|
|
const DCField *field) {
|
|
string field_name = field->get_name();
|
|
DCPackType pack_type = get_pack_type();
|
|
|
|
if (field_name.empty()) {
|
|
switch (pack_type) {
|
|
case PT_class:
|
|
case PT_switch:
|
|
// If the field has no name, but it is one of these container
|
|
// objects, we want to get its nested objects directly from
|
|
// the class.
|
|
push();
|
|
while (more_nested_fields() && !_pack_error) {
|
|
const DCField *field = get_current_field()->as_field();
|
|
nassertv(field != (DCField *)NULL);
|
|
get_class_element(dclass, object, field);
|
|
}
|
|
pop();
|
|
break;
|
|
|
|
default:
|
|
// Otherwise, we just pack the default value.
|
|
pack_default_value();
|
|
}
|
|
|
|
} else {
|
|
// If the field does have a name, we will want to get it from the
|
|
// class and pack it. It just so happens that there's already a
|
|
// method that does this on DCClass.
|
|
|
|
if (!dclass->pack_required_field(*this, object, field)) {
|
|
_pack_error = true;
|
|
}
|
|
}
|
|
}
|
|
#endif // HAVE_PYTHON
|