diff --git a/panda/src/pgraph/nodePath.I b/panda/src/pgraph/nodePath.I index 44dcd1543c..299420a001 100644 --- a/panda/src/pgraph/nodePath.I +++ b/panda/src/pgraph/nodePath.I @@ -84,31 +84,6 @@ any_path(PandaNode *node, Thread *current_thread) { return result; } -//////////////////////////////////////////////////////////////////// -// Function: NodePath::Constructor -// Access: Published -// Description: Constructs a NodePath with the indicated parent -// NodePath and child node; the child node must be a -// stashed or unstashed child of the parent. -//////////////////////////////////////////////////////////////////// -INLINE NodePath:: -NodePath(const NodePath &parent, PandaNode *child_node, - Thread *current_thread) : - _error_type(ET_fail) -{ - nassertv(!parent.is_empty()); - nassertv(child_node != (PandaNode *)NULL); - int pipeline_stage = current_thread->get_pipeline_stage(); - _head = PandaNode::get_component(parent._head, child_node, pipeline_stage, - current_thread); - nassertv(_head != (NodePathComponent *)NULL); - - if (_head != (NodePathComponent *)NULL) { - _error_type = ET_ok; - } - _backup_key = 0; -} - //////////////////////////////////////////////////////////////////// // Function: NodePath::Copy Constructor // Access: Published @@ -2387,6 +2362,37 @@ get_name() const { return node()->get_name(); } +//////////////////////////////////////////////////////////////////// +// Function: NodePath::encode_full_path_to_bam_stream +// Access: Published +// Description: Converts the NodePath object into a single +// stream of data using a BamWriter, and returns that +// data as a string string. Returns empty string on +// failure. +// +// This is different from NodePath::write_bam_stream() +// and PandaNode::encode_to_bam_stream(), in that it +// encodes the *entire graph* of all nodes connected to +// the NodePath, including all parent nodes and +// siblings. (The other methods only encode this node +// and the nodes below it.) This may be necessary for +// correct streaming of related NodePaths and +// restoration of instances, etc., but it does mean you +// must detach() a node before writing it if you want to +// limit the nodes that get written. +// +// This method is used by __reduce__ to handle streaming +// of NodePaths to a pickle file. +//////////////////////////////////////////////////////////////////// +INLINE string NodePath:: +encode_full_path_to_bam_stream() const { + string data; + if (!encode_full_path_to_bam_stream(data)) { + return string(); + } + return data; +} + INLINE ostream &operator << (ostream &out, const NodePath &node_path) { node_path.output(out); diff --git a/panda/src/pgraph/nodePath.cxx b/panda/src/pgraph/nodePath.cxx index 24da1e8fd6..45a4e15281 100644 --- a/panda/src/pgraph/nodePath.cxx +++ b/panda/src/pgraph/nodePath.cxx @@ -67,6 +67,7 @@ #include "pStatTimer.h" #include "modelNode.h" #include "py_panda.h" +#include "bam.h" // stack seems to overflow on Intel C++ at 7000. If we need more than // 7000, need to increase stack size. @@ -136,6 +137,36 @@ static ConfigVariableEnum empty_node_path // ***End temporary transition code for operator bool +//////////////////////////////////////////////////////////////////// +// Function: NodePath::Constructor +// Access: Published +// Description: Constructs a NodePath with the indicated parent +// NodePath and child node; the child node must be a +// stashed or unstashed child of the parent. +//////////////////////////////////////////////////////////////////// +NodePath:: +NodePath(const NodePath &parent, PandaNode *child_node, + Thread *current_thread) : + _error_type(ET_fail) +{ + nassertv(child_node != (PandaNode *)NULL); + int pipeline_stage = current_thread->get_pipeline_stage(); + + if (parent.is_empty()) { + // Special case: constructing a NodePath at the root. + _head = PandaNode::attach(NULL, child_node, 0, pipeline_stage, current_thread); + } else { + _head = PandaNode::get_component(parent._head, child_node, pipeline_stage, + current_thread); + } + nassertv(_head != (NodePathComponent *)NULL); + + if (_head != (NodePathComponent *)NULL) { + _error_type = ET_ok; + } + _backup_key = 0; +} + #ifdef HAVE_PYTHON //////////////////////////////////////////////////////////////////// // Function: NodePath::__copy__ @@ -236,20 +267,6 @@ __reduce_persist__(PyObject *self, PyObject *pickler) const { // (e.g. this), and the arguments necessary to reconstruct this // object. - if (is_empty()) { - // Reconstruct an empty NodePath. Not a 100% reconstruction, - // because we lose the specific error status, but I don't think - // that matters much. - PyObject *this_class = PyObject_Type(self); - if (this_class == NULL) { - return NULL; - } - - PyObject *result = Py_BuildValue("(O())", this_class); - Py_DECREF(this_class); - return result; - } - BamWriter *writer = NULL; if (pickler != NULL) { PyObject *py_writer = PyObject_GetAttrString(pickler, "bamWriter"); @@ -262,13 +279,12 @@ __reduce_persist__(PyObject *self, PyObject *pickler) const { } } - // We have a non-empty NodePath. We need to streamify the - // underlying node. + // We have a non-empty NodePath. string bam_stream; - if (!node()->encode_to_bam_stream(bam_stream, writer)) { + if (!encode_full_path_to_bam_stream(bam_stream, writer)) { ostringstream stream; - stream << "Could not bamify object of type " << node()->get_type() << "\n"; + stream << "Could not bamify " << this; string message = stream.str(); PyErr_SetString(PyExc_TypeError, message.c_str()); return NULL; @@ -6606,6 +6622,153 @@ write_bam_stream(ostream &out) const { return okflag; } +//////////////////////////////////////////////////////////////////// +// Function: NodePath::encode_full_path_to_bam_stream +// Access: Published +// Description: Converts the NodePath object into a single +// stream of data using a BamWriter, and stores that +// data in the indicated string. Returns true on +// success, false on failure. +// +// This is different from NodePath::write_bam_stream() +// and PandaNode::encode_to_bam_stream(), in that it +// encodes the *entire graph* of all nodes connected to +// the NodePath, including all parent nodes and +// siblings. (The other methods only encode this node +// and the nodes below it.) This may be necessary for +// correct streaming of related NodePaths and +// restoration of instances, etc., but it does mean you +// must detach() a node before writing it if you want to +// limit the nodes that get written. +// +// This method is used by __reduce__ to handle streaming +// of NodePaths to a pickle file. +//////////////////////////////////////////////////////////////////// +bool NodePath:: +encode_full_path_to_bam_stream(string &data, BamWriter *writer) const { + data.clear(); + ostringstream stream; + + DatagramOutputFile dout; + if (!dout.open(stream)) { + return false; + } + + BamWriter local_writer; + if (writer == NULL) { + // Create our own writer. + + if (!dout.write_header(_bam_header)) { + return false; + } + writer = &local_writer; + } + + writer->set_target(&dout); + + // Write an initial Datagram to represent the error type and + // number of nodes. + int num_nodes = get_num_nodes(); + Datagram dg; + dg.add_uint8(_error_type); + dg.add_int32(num_nodes); + + if (!dout.put_datagram(dg)) { + writer->set_target(NULL); + return false; + } + + // Now write the nodes, one at a time. + for (int i = 0; i < num_nodes; ++i) { + PandaNode *node = get_node(num_nodes - i - 1); + nassertr(node != NULL, false); + if (!writer->write_object(node)) { + writer->set_target(NULL); + return false; + } + } + writer->set_target(NULL); + + data = stream.str(); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: NodePath::decode_full_path_from_bam_stream +// Access: Published, Static +// Description: Reads the string created by a previous call to +// encode_full_path_to_bam_stream(), and extracts and +// returns the NodePath on that string. Returns NULL on +// error. +//////////////////////////////////////////////////////////////////// +NodePath NodePath:: +decode_full_path_from_bam_stream(const string &data, BamReader *reader) { + NodePath result; + + istringstream stream(data); + + DatagramInputFile din; + if (!din.open(stream)) { + return NodePath::fail(); + } + + BamReader local_reader; + if (reader == NULL) { + // Create a local reader. + + string head; + if (!din.read_header(head, _bam_header.size())) { + return NodePath::fail(); + } + + if (head != _bam_header) { + return NodePath::fail(); + } + + reader = &local_reader; + } + + reader->set_source(&din); + + // One initial datagram to encode the error type, and the number of nodes. + Datagram dg; + if (!din.get_datagram(dg)) { + return NodePath::fail(); + } + + DatagramIterator dgi(dg); + ErrorType error_type = (ErrorType)dgi.get_uint8(); + int num_nodes = dgi.get_int32(); + if (num_nodes == 0) { + // An empty NodePath. + result._error_type = error_type; + + } else { + // A real NodePath. Ignore error_type. + for (int i = 0; i < num_nodes; ++i) { + TypedWritable *object = reader->read_object(); + + if (object == (TypedWritable *)NULL || + !object->is_of_type(PandaNode::get_class_type())) { + reader->set_source(NULL); + return NodePath::fail(); + } + + if (!reader->resolve()) { + reader->set_source(NULL); + return NodePath::fail(); + } + + PandaNode *node = DCAST(PandaNode, object); + result = NodePath(result, node); + } + } + + reader->set_source(NULL); + + return result; +} + //////////////////////////////////////////////////////////////////// // Function: NodePath::find_common_ancestor // Access: Private, Static @@ -7542,7 +7705,6 @@ py_decode_NodePath_from_bam_stream(const string &data) { //////////////////////////////////////////////////////////////////// NodePath py_decode_NodePath_from_bam_stream_persist(PyObject *unpickler, const string &data) { - BamReader *reader = NULL; if (unpickler != NULL) { PyObject *py_reader = PyObject_GetAttrString(unpickler, "bamReader"); @@ -7555,13 +7717,7 @@ py_decode_NodePath_from_bam_stream_persist(PyObject *unpickler, const string &da } } - PT(PandaNode) node = PandaNode::decode_from_bam_stream(data, reader); - if (node == (PandaNode *)NULL) { - PyErr_SetString(PyExc_ValueError, "Could not unpack bam stream"); - return NodePath(); - } - - return NodePath(node); + return NodePath::decode_full_path_from_bam_stream(data, reader); } #endif // HAVE_PYTHON diff --git a/panda/src/pgraph/nodePath.h b/panda/src/pgraph/nodePath.h index a0a6945a95..8e7ccec03c 100644 --- a/panda/src/pgraph/nodePath.h +++ b/panda/src/pgraph/nodePath.h @@ -162,8 +162,8 @@ PUBLISHED: INLINE NodePath(const string &top_node_name, Thread *current_thread = Thread::get_current_thread()); INLINE NodePath(PandaNode *node, Thread *current_thread = Thread::get_current_thread()); INLINE static NodePath any_path(PandaNode *node, Thread *current_thread = Thread::get_current_thread()); - INLINE NodePath(const NodePath &parent, PandaNode *child_node, - Thread *current_thread = Thread::get_current_thread()); + NodePath(const NodePath &parent, PandaNode *child_node, + Thread *current_thread = Thread::get_current_thread()); INLINE NodePath(const NodePath ©); INLINE void operator = (const NodePath ©); @@ -869,6 +869,10 @@ PUBLISHED: BLOCKING bool write_bam_file(const string &filename) const; BLOCKING bool write_bam_stream(ostream &out) const; + INLINE string encode_full_path_to_bam_stream() const; + bool encode_full_path_to_bam_stream(string &data, BamWriter *writer = NULL) const; + static NodePath decode_full_path_from_bam_stream(const string &data, BamReader *reader = NULL); + private: static NodePathComponent * find_common_ancestor(const NodePath &a, const NodePath &b, diff --git a/panda/src/putil/typedWritable.I b/panda/src/putil/typedWritable.I index 9baa7721f2..796dffc9bb 100644 --- a/panda/src/putil/typedWritable.I +++ b/panda/src/putil/typedWritable.I @@ -82,7 +82,7 @@ get_bam_modified() const { // efficient to use the same BamWriter to serialize all // of them together. //////////////////////////////////////////////////////////////////// -string TypedWritable:: +INLINE string TypedWritable:: encode_to_bam_stream() const { string data; if (!encode_to_bam_stream(data)) {