diff --git a/direct/src/stdpy/pickle.py b/direct/src/stdpy/pickle.py index 9e7561ebb3..4e03089f84 100644 --- a/direct/src/stdpy/pickle.py +++ b/direct/src/stdpy/pickle.py @@ -21,8 +21,12 @@ shared context between all objects written by that Pickler. Unfortunately, cPickle cannot be supported, because it does not support extensions of this nature. """ +__all__ = ["PickleError", "PicklingError", "UnpicklingError", "Pickler", + "Unpickler", "dump", "dumps", "load", "loads", + "HIGHEST_PROTOCOL", "DEFAULT_PROTOCOL"] + import sys -from panda3d.core import BamWriter, BamReader +from panda3d.core import BamWriter, BamReader, TypedObject from copyreg import dispatch_table @@ -30,17 +34,29 @@ from copyreg import dispatch_table # with the local pickle.py. pickle = __import__('pickle') +HIGHEST_PROTOCOL = pickle.HIGHEST_PROTOCOL +DEFAULT_PROTOCOL = pickle.DEFAULT_PROTOCOL + +PickleError = pickle.PickleError PicklingError = pickle.PicklingError +UnpicklingError = pickle.UnpicklingError + BasePickler = pickle._Pickler BaseUnpickler = pickle._Unpickler -class _Pickler(BasePickler): +class Pickler(BasePickler): def __init__(self, *args, **kw): self.bamWriter = BamWriter() + self._canonical = {} BasePickler.__init__(self, *args, **kw) + def clear_memo(self): + BasePickler.clear_memo(self) + self._canonical.clear() + self.bamWriter = BamWriter() + # We have to duplicate most of the save() method, so we can add # support for __reduce_persist__(). @@ -54,6 +70,21 @@ class _Pickler(BasePickler): self.save_pers(pid) return + # Check if this is a Panda type that we've already saved; if so, store + # a mapping to the canonical copy, so that Python's memoization system + # works properly. This is needed because Python uses id(obj) for + # memoization, but there may be multiple Python wrappers for the same + # C++ pointer, and we don't want that to result in duplication. + t = type(obj) + if issubclass(t, TypedObject.__base__): + canonical = self._canonical.get(obj.this) + if canonical is not None: + obj = canonical + else: + # First time we're seeing this C++ pointer; save it as the + # "canonical" version. + self._canonical[obj.this] = obj + # Check the memo x = self.memo.get(id(obj)) if x: @@ -61,7 +92,6 @@ class _Pickler(BasePickler): return # Check the type dispatch table - t = type(obj) f = self.dispatch.get(t) if f: f(self, obj) # Call unbound method with explicit self @@ -146,25 +176,6 @@ class Unpickler(BaseUnpickler): BaseUnpickler.dispatch[pickle.REDUCE[0]] = load_reduce -if sys.version_info >= (3, 8): - # In Python 3.8 and up, we can use the C implementation of Pickler, which - # supports a reducer_override method. - class Pickler(pickle.Pickler): - def __init__(self, *args, **kw): - self.bamWriter = BamWriter() - pickle.Pickler.__init__(self, *args, **kw) - - def reducer_override(self, obj): - reduce = getattr(obj, "__reduce_persist__", None) - if reduce: - return reduce(self) - - return NotImplemented -else: - # Otherwise, we have to use our custom version that overrides save(). - Pickler = _Pickler - - # Shorthands from io import BytesIO diff --git a/dtool/src/interrogate/functionRemap.cxx b/dtool/src/interrogate/functionRemap.cxx index fd44f9f5fa..e0dec969c0 100644 --- a/dtool/src/interrogate/functionRemap.cxx +++ b/dtool/src/interrogate/functionRemap.cxx @@ -920,6 +920,9 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak || fname == "__delattr__") { // Just to prevent these from getting keyword arguments. + } else if (fname == "__setstate__") { + _args_type = InterfaceMaker::AT_single_arg; + } else { if (_args_type == InterfaceMaker::AT_varargs) { // Every other method can take keyword arguments, if they take more diff --git a/dtool/src/interrogate/interfaceMakerPythonNative.cxx b/dtool/src/interrogate/interfaceMakerPythonNative.cxx index d719d3b549..02c83ebfd4 100644 --- a/dtool/src/interrogate/interfaceMakerPythonNative.cxx +++ b/dtool/src/interrogate/interfaceMakerPythonNative.cxx @@ -3637,18 +3637,23 @@ write_function_for_name(ostream &out, Object *obj, std::string ClassName = make_safe_name(obj->_itype.get_scoped_name()); std::string cClassName = obj->_itype.get_true_name(); // string class_name = remap->_cpptype->get_simple_name(); + CPPStructType *struct_type = obj->_itype._cpptype->as_struct_type(); // If this is a non-static __setstate__, we run the default constructor. - if (remap->_cppfunc->get_local_name() == "__setstate__") { - out << " if (DtoolInstance_VOID_PTR(self) != nullptr) {\n" - << " Dtool_Raise_TypeError(\"C++ object is already constructed.\");\n"; - error_return(out, 4, return_flags); - out << " }\n" - << " " << cClassName << " *local_this = new " << cClassName << ";\n" - << " DTool_PyInit_Finalize(self, local_this, &Dtool_" << ClassName - << ", false, false);\n" - << " if (local_this == nullptr) {\n" - << " PyErr_NoMemory();\n"; + if (remap->_cppfunc->get_local_name() == "__setstate__" && + !struct_type->is_abstract()) { + out << " " << cClassName << " *local_this = nullptr;\n" + << " if (DtoolInstance_VOID_PTR(self) == nullptr) {\n" + << " local_this = new " << cClassName << ";\n" + << " DTool_PyInit_Finalize(self, local_this, &Dtool_" << ClassName + << ", true, false);\n" + << " if (local_this == nullptr) {\n" + << " PyErr_NoMemory();\n"; + error_return(out, 6, return_flags); + out << " }\n" + << " } else if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", " + << "(void **)&local_this, \"" << classNameFromCppName(cClassName, false) + << "." << methodNameFromCppName(remap, cClassName, false) << "\")) {\n"; } else if (all_nonconst) { // All remaps are non-const. Also check that this object isn't const. @@ -3685,6 +3690,14 @@ write_function_for_name(ostream &out, Object *obj, args_type = AT_varargs; } + // If this is a __setstate__ taking multiple arguments, and we're given a + // tuple as argument, unpack it. + if (args_type == AT_single_arg && max_required_args > 1 && + remap->_cppfunc->get_local_name() == "__setstate__") { + out << " PyObject *args = arg;\n"; + args_type = AT_varargs; + } + if (args_type == AT_keyword_args || args_type == AT_varargs) { max_required_args = collapse_default_remaps(map_sets, max_required_args); } diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 848562d989..bbb8e3bc7a 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -4397,7 +4397,7 @@ if not PkgSkip("EGG"): IGATEFILES.remove("parser.h") TargetAdd('libp3egg.in', opts=OPTS, input=IGATEFILES) TargetAdd('libp3egg.in', opts=['IMOD:panda3d.egg', 'ILIB:libp3egg', 'SRCDIR:panda/src/egg']) - PyTargetAdd('p3egg_eggGroupNode_ext.obj', opts=OPTS, input='eggGroupNode_ext.cxx') + PyTargetAdd('p3egg_ext_composite.obj', opts=OPTS, input='p3egg_ext_composite.cxx') # # DIRECTORY: panda/src/egg2pg/ @@ -4486,7 +4486,7 @@ if not PkgSkip("EGG"): PyTargetAdd('egg_module.obj', opts=['IMOD:panda3d.egg', 'ILIB:egg', 'IMPORT:panda3d.core']) PyTargetAdd('egg.pyd', input='egg_module.obj') - PyTargetAdd('egg.pyd', input='p3egg_eggGroupNode_ext.obj') + PyTargetAdd('egg.pyd', input='p3egg_ext_composite.obj') PyTargetAdd('egg.pyd', input='libp3egg_igate.obj') PyTargetAdd('egg.pyd', input='libp3egg2pg_igate.obj') PyTargetAdd('egg.pyd', input='libpandaegg.dll') diff --git a/panda/src/collide/collisionHandlerEvent.cxx b/panda/src/collide/collisionHandlerEvent.cxx index 0fd949074e..668868f078 100644 --- a/panda/src/collide/collisionHandlerEvent.cxx +++ b/panda/src/collide/collisionHandlerEvent.cxx @@ -153,6 +153,52 @@ flush() { end_group(); } +/** + * Serializes this object, to implement pickle support. + */ +void CollisionHandlerEvent:: +write_datagram(Datagram &dg) const { + dg.add_uint32(_in_patterns.size()); + for (const std::string &pattern : _in_patterns) { + dg.add_string(pattern); + } + + dg.add_uint32(_again_patterns.size()); + for (const std::string &pattern : _again_patterns) { + dg.add_string(pattern); + } + + dg.add_uint32(_out_patterns.size()); + for (const std::string &pattern : _out_patterns) { + dg.add_string(pattern); + } +} + +/** + * Restores the object state from the given datagram, previously obtained using + * __getstate__. + */ +void CollisionHandlerEvent:: +read_datagram(DatagramIterator &scan) { + _in_patterns.clear(); + size_t num_in_patterns = scan.get_uint32(); + for (size_t i = 0; i < num_in_patterns; ++i) { + add_in_pattern(scan.get_string()); + } + + _again_patterns.clear(); + size_t num_again_patterns = scan.get_uint32(); + for (size_t i = 0; i < num_again_patterns; ++i) { + add_again_pattern(scan.get_string()); + } + + _out_patterns.clear(); + size_t num_out_patterns = scan.get_uint32(); + for (size_t i = 0; i < num_out_patterns; ++i) { + add_out_pattern(scan.get_string()); + } +} + /** * Throws whatever events are suggested by the list of patterns. */ diff --git a/panda/src/collide/collisionHandlerEvent.h b/panda/src/collide/collisionHandlerEvent.h index 808ee888c5..59173a42fc 100644 --- a/panda/src/collide/collisionHandlerEvent.h +++ b/panda/src/collide/collisionHandlerEvent.h @@ -22,6 +22,7 @@ #include "vector_string.h" #include "pointerTo.h" +#include "extension.h" /** * A specialized kind of CollisionHandler that throws an event for each @@ -61,12 +62,19 @@ PUBLISHED: MAKE_SEQ(get_out_patterns, get_num_out_patterns, get_out_pattern); MAKE_SEQ_PROPERTY(in_patterns, get_num_in_patterns, get_in_pattern); - MAKE_SEQ_PROPERTY(again_patterns, get_num_again_patterns, get_out_pattern); + MAKE_SEQ_PROPERTY(again_patterns, get_num_again_patterns, get_again_pattern); MAKE_SEQ_PROPERTY(out_patterns, get_num_out_patterns, get_out_pattern); void clear(); void flush(); + // These help implement Python pickle support. + EXTENSION(PyObject *__reduce__(PyObject *self) const); + EXTENSION(void __setstate__(PyObject *self, vector_uchar data)); + + void write_datagram(Datagram &destination) const; + void read_datagram(DatagramIterator &source); + protected: void throw_event_for(const vector_string &patterns, CollisionEntry *entry); void throw_event_pattern(const std::string &pattern, CollisionEntry *entry); diff --git a/panda/src/collide/collisionHandlerEvent_ext.cxx b/panda/src/collide/collisionHandlerEvent_ext.cxx new file mode 100644 index 0000000000..98c0f951f4 --- /dev/null +++ b/panda/src/collide/collisionHandlerEvent_ext.cxx @@ -0,0 +1,70 @@ +/** + * 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." + * + * @file collisionHandlerEvent_ext.cxx + * @author rdb + * @date 2020-12-31 + */ + +#include "collisionHandlerEvent_ext.h" +#include "collisionHandlerFloor.h" +#include "collisionHandlerGravity.h" +#include "collisionHandlerPusher.h" + +#ifdef HAVE_PYTHON + +/** + * Implements pickling behavior. + */ +PyObject *Extension:: +__reduce__(PyObject *self) const { + extern struct Dtool_PyTypedObject Dtool_Datagram; + + // Call the write_datagram method via Python, since it's not a virtual method + // on the C++ end. + PyObject *method_name = PyUnicode_FromString("write_datagram"); + + Datagram dg; + PyObject *destination = DTool_CreatePyInstance(&dg, Dtool_Datagram, false, false); + + PyObject *retval = PyObject_CallMethodOneArg(self, method_name, destination); + Py_DECREF(method_name); + Py_DECREF(destination); + if (retval == nullptr) { + return nullptr; + } + Py_DECREF(retval); + + const char *data = (const char *)dg.get_data(); + Py_ssize_t size = dg.get_length(); + return Py_BuildValue("O()y#", Py_TYPE(self), data, size); +} + +/** + * Takes the value returned by __getstate__ and uses it to freshly initialize + * this CollisionHandlerEvent object. + */ +void Extension:: +__setstate__(PyObject *self, vector_uchar data) { + extern struct Dtool_PyTypedObject Dtool_DatagramIterator; + + // Call the read_datagram method via Python, since it's not a virtual method + // on the C++ end. + PyObject *method_name = PyUnicode_FromString("read_datagram"); + + Datagram dg(std::move(data)); + DatagramIterator scan(dg); + PyObject *source = DTool_CreatePyInstance(&scan, Dtool_DatagramIterator, false, false); + + PyObject *retval = PyObject_CallMethodOneArg(self, method_name, source); + Py_DECREF(method_name); + Py_DECREF(source); + Py_XDECREF(retval); +} + +#endif diff --git a/panda/src/collide/collisionHandlerEvent_ext.h b/panda/src/collide/collisionHandlerEvent_ext.h new file mode 100644 index 0000000000..22a3a5e680 --- /dev/null +++ b/panda/src/collide/collisionHandlerEvent_ext.h @@ -0,0 +1,38 @@ +/** + * 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." + * + * @file collisionHandlerEvent_ext.h + * @author rdb + * @date 2020-12-31 + */ + +#ifndef COLLISIONHANDLEREVENT_EXT_H +#define COLLISIONHANDLEREVENT_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "collisionHandlerEvent.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for CollisionHandlerEvent, which are + * called instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__reduce__(PyObject *self) const; + void __setstate__(PyObject *self, vector_uchar data); +}; + +#endif // HAVE_PYTHON + +#endif // COLLISIONHANDLEREVENT_EXT_H diff --git a/panda/src/collide/collisionHandlerFloor.cxx b/panda/src/collide/collisionHandlerFloor.cxx index 506101cb41..8c9838eea7 100644 --- a/panda/src/collide/collisionHandlerFloor.cxx +++ b/panda/src/collide/collisionHandlerFloor.cxx @@ -41,18 +41,31 @@ CollisionHandlerFloor:: } /** - * + * Serializes this object, to implement pickle support. + */ +void CollisionHandlerFloor:: +write_datagram(Datagram &dg) const { + CollisionHandlerPhysical::write_datagram(dg); - * + dg.add_float64(_offset); + dg.add_float64(_reach); + dg.add_float64(_max_velocity); +} - * +/** + * Restores the object state from the given datagram, previously obtained using + * __getstate__. + */ +void CollisionHandlerFloor:: +read_datagram(DatagramIterator &scan) { + CollisionHandlerPhysical::read_datagram(scan); - * - - * - - * + _offset = scan.get_float64(); + _reach = scan.get_float64(); + _max_velocity = scan.get_float64(); +} +/** * */ PN_stdfloat CollisionHandlerFloor:: diff --git a/panda/src/collide/collisionHandlerFloor.h b/panda/src/collide/collisionHandlerFloor.h index d73ca008df..c4307fcc00 100644 --- a/panda/src/collide/collisionHandlerFloor.h +++ b/panda/src/collide/collisionHandlerFloor.h @@ -43,6 +43,9 @@ PUBLISHED: MAKE_PROPERTY(reach, get_reach, set_reach); MAKE_PROPERTY(max_velocity, get_max_velocity, set_max_velocity); + void write_datagram(Datagram &destination) const; + void read_datagram(DatagramIterator &source); + protected: PN_stdfloat set_highest_collision(const NodePath &target_node_path, const NodePath &from_node_path, const Entries &entries); virtual bool handle_entries(); diff --git a/panda/src/collide/collisionHandlerGravity.cxx b/panda/src/collide/collisionHandlerGravity.cxx index 023d220cd4..0b3eb5852c 100644 --- a/panda/src/collide/collisionHandlerGravity.cxx +++ b/panda/src/collide/collisionHandlerGravity.cxx @@ -46,6 +46,35 @@ CollisionHandlerGravity:: ~CollisionHandlerGravity() { } +/** + * Serializes this object, to implement pickle support. + */ +void CollisionHandlerGravity:: +write_datagram(Datagram &dg) const { + CollisionHandlerPhysical::write_datagram(dg); + + dg.add_float64(_offset); + dg.add_float64(_reach); + dg.add_float64(_max_velocity); + dg.add_float64(_gravity); + dg.add_bool(_legacy_mode); +} + +/** + * Restores the object state from the given datagram, previously obtained using + * __getstate__. + */ +void CollisionHandlerGravity:: +read_datagram(DatagramIterator &scan) { + CollisionHandlerPhysical::read_datagram(scan); + + _offset = scan.get_float64(); + _reach = scan.get_float64(); + _max_velocity = scan.get_float64(); + _gravity = scan.get_float64(); + _legacy_mode = scan.get_bool(); +} + /** * */ diff --git a/panda/src/collide/collisionHandlerGravity.h b/panda/src/collide/collisionHandlerGravity.h index de89091564..136da11462 100644 --- a/panda/src/collide/collisionHandlerGravity.h +++ b/panda/src/collide/collisionHandlerGravity.h @@ -64,6 +64,9 @@ PUBLISHED: MAKE_PROPERTY(gravity, get_gravity, set_gravity); MAKE_PROPERTY(max_velocity, get_max_velocity, set_max_velocity); + void write_datagram(Datagram &destination) const; + void read_datagram(DatagramIterator &source); + protected: PN_stdfloat set_highest_collision(const NodePath &target_node_path, const NodePath &from_node_path, const Entries &entries); virtual bool handle_entries(); diff --git a/panda/src/collide/collisionHandlerPhysical.h b/panda/src/collide/collisionHandlerPhysical.h index 9a9225b6b8..0147b6acf8 100644 --- a/panda/src/collide/collisionHandlerPhysical.h +++ b/panda/src/collide/collisionHandlerPhysical.h @@ -54,6 +54,9 @@ PUBLISHED: PUBLISHED: MAKE_PROPERTY2(center, has_center, get_center, set_center, clear_center); + EXTENSION(PyObject *__reduce__(PyObject *self) const); + EXTENSION(void __setstate__(PyObject *self, vector_uchar data, PyObject *nodepaths)); + protected: bool _has_contact; // Are we in contact with anything? @@ -83,6 +86,8 @@ protected: NodePath _center; + friend class Extension; + public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/collide/collisionHandlerPhysical_ext.cxx b/panda/src/collide/collisionHandlerPhysical_ext.cxx new file mode 100644 index 0000000000..9e4a5b944c --- /dev/null +++ b/panda/src/collide/collisionHandlerPhysical_ext.cxx @@ -0,0 +1,108 @@ +/** + * 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." + * + * @file collisionHandlerPhysical_ext.cxx + * @author rdb + * @date 2020-12-31 + */ + +#include "collisionHandlerPhysical_ext.h" +#include "collisionHandlerEvent_ext.h" + +#ifdef HAVE_PYTHON + +/** + * Implements pickling behavior. + */ +PyObject *Extension:: +__reduce__(PyObject *self) const { + extern struct Dtool_PyTypedObject Dtool_Datagram; + extern struct Dtool_PyTypedObject Dtool_NodePath; + + // Create a tuple with all the NodePath pointers. + PyObject *nodepaths = PyTuple_New(_this->_colliders.size() * 2 + 1); + Py_ssize_t i = 0; + + if (_this->has_center()) { + const NodePath *center = &(_this->get_center()); + PyTuple_SET_ITEM(nodepaths, i++, + DTool_CreatePyInstance((void *)center, Dtool_NodePath, false, true)); + } else { + PyTuple_SET_ITEM(nodepaths, i++, Py_None); + Py_INCREF(Py_None); + } + + CollisionHandlerPhysical::Colliders::const_iterator it; + for (it = _this->_colliders.begin(); it != _this->_colliders.end(); ++it) { + const NodePath *collider = &(it->first); + const NodePath *target = &(it->second._target); + PyTuple_SET_ITEM(nodepaths, i++, + DTool_CreatePyInstance((void *)collider, Dtool_NodePath, false, true)); + PyTuple_SET_ITEM(nodepaths, i++, + DTool_CreatePyInstance((void *)target, Dtool_NodePath, false, true)); + } + + // Call the write_datagram method via Python, since it's not a virtual method + // on the C++ end. + PyObject *method_name = PyUnicode_FromString("write_datagram"); + + Datagram dg; + PyObject *destination = DTool_CreatePyInstance(&dg, Dtool_Datagram, false, false); + + PyObject *retval = PyObject_CallMethodOneArg(self, method_name, destination); + Py_DECREF(method_name); + Py_DECREF(destination); + if (retval == nullptr) { + return nullptr; + } + Py_DECREF(retval); + + const char *data = (const char *)dg.get_data(); + Py_ssize_t size = dg.get_length(); + return Py_BuildValue("O()(y#N)", Py_TYPE(self), data, size, nodepaths); +} + +/** + * Takes the value returned by __getstate__ and uses it to freshly initialize + * this CollisionHandlerPhysical object. + */ +void Extension:: +__setstate__(PyObject *self, vector_uchar data, PyObject *nodepaths) { + extern struct Dtool_PyTypedObject Dtool_DatagramIterator; + + // Call the read_datagram method via Python, since it's not a virtual method + // on the C++ end. + PyObject *method_name = PyUnicode_FromString("read_datagram"); + + { + Datagram dg(std::move(data)); + DatagramIterator scan(dg); + PyObject *source = DTool_CreatePyInstance(&scan, Dtool_DatagramIterator, false, false); + + PyObject *retval = PyObject_CallMethodOneArg(self, method_name, source); + Py_DECREF(method_name); + Py_DECREF(source); + Py_XDECREF(retval); + } + + PyObject *center = PyTuple_GET_ITEM(nodepaths, 0); + if (center != Py_None) { + _this->set_center(*(NodePath *)DtoolInstance_VOID_PTR(center)); + } else { + _this->clear_center(); + } + + size_t num_nodepaths = Py_SIZE(nodepaths); + for (size_t i = 1; i < num_nodepaths;) { + NodePath *collider = (NodePath *)DtoolInstance_VOID_PTR(PyTuple_GET_ITEM(nodepaths, i++)); + NodePath *target = (NodePath *)DtoolInstance_VOID_PTR(PyTuple_GET_ITEM(nodepaths, i++)); + _this->add_collider(*collider, *target); + } +} + +#endif diff --git a/panda/src/collide/collisionHandlerPhysical_ext.h b/panda/src/collide/collisionHandlerPhysical_ext.h new file mode 100644 index 0000000000..8e74becd8f --- /dev/null +++ b/panda/src/collide/collisionHandlerPhysical_ext.h @@ -0,0 +1,38 @@ +/** + * 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." + * + * @file collisionHandlerPhysical_ext.h + * @author rdb + * @date 2020-12-31 + */ + +#ifndef COLLISIONHANDLERPHYSICAL_EXT_H +#define COLLISIONHANDLERPHYSICAL_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "collisionHandlerPhysical.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for CollisionHandlerPhysical, which are + * called instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__reduce__(PyObject *self) const; + void __setstate__(PyObject *self, vector_uchar data, PyObject *nodepaths); +}; + +#endif // HAVE_PYTHON + +#endif // COLLISIONHANDLERPHYSICAL_EXT_H diff --git a/panda/src/collide/collisionHandlerPusher.cxx b/panda/src/collide/collisionHandlerPusher.cxx index fb7dc87c66..7c3f6674c3 100644 --- a/panda/src/collide/collisionHandlerPusher.cxx +++ b/panda/src/collide/collisionHandlerPusher.cxx @@ -49,6 +49,27 @@ CollisionHandlerPusher:: ~CollisionHandlerPusher() { } +/** + * Serializes this object, to implement pickle support. + */ +void CollisionHandlerPusher:: +write_datagram(Datagram &dg) const { + CollisionHandlerPhysical::write_datagram(dg); + + dg.add_bool(_horizontal); +} + +/** + * Restores the object state from the given datagram, previously obtained using + * __getstate__. + */ +void CollisionHandlerPusher:: +read_datagram(DatagramIterator &scan) { + CollisionHandlerPhysical::read_datagram(scan); + + _horizontal = scan.get_bool(); +} + /** * Called by the parent class after all collisions have been detected, this * manages the various collisions and moves around the nodes as necessary. diff --git a/panda/src/collide/collisionHandlerPusher.h b/panda/src/collide/collisionHandlerPusher.h index 8f427ca4b2..99ea68104a 100644 --- a/panda/src/collide/collisionHandlerPusher.h +++ b/panda/src/collide/collisionHandlerPusher.h @@ -34,6 +34,9 @@ PUBLISHED: PUBLISHED: MAKE_PROPERTY(horizontal, get_horizontal, set_horizontal); + void write_datagram(Datagram &destination) const; + void read_datagram(DatagramIterator &source); + protected: virtual bool handle_entries(); virtual void apply_net_shove( diff --git a/panda/src/collide/collisionHandlerQueue.h b/panda/src/collide/collisionHandlerQueue.h index cfef93c540..48e48600b7 100644 --- a/panda/src/collide/collisionHandlerQueue.h +++ b/panda/src/collide/collisionHandlerQueue.h @@ -18,6 +18,7 @@ #include "collisionHandler.h" #include "collisionEntry.h" +#include "extension.h" /** * A special kind of CollisionHandler that does nothing except remember the @@ -45,6 +46,8 @@ PUBLISHED: void output(std::ostream &out) const; void write(std::ostream &out, int indent_level = 0) const; + EXTENSION(PyObject *__reduce__(PyObject *self) const); + private: typedef pvector< PT(CollisionEntry) > Entries; Entries _entries; diff --git a/panda/src/collide/collisionHandlerQueue_ext.cxx b/panda/src/collide/collisionHandlerQueue_ext.cxx new file mode 100644 index 0000000000..b439d78214 --- /dev/null +++ b/panda/src/collide/collisionHandlerQueue_ext.cxx @@ -0,0 +1,27 @@ +/** + * 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." + * + * @file collisionHandlerQueue_ext.cxx + * @author rdb + * @date 2020-12-31 + */ + +#include "collisionHandlerQueue_ext.h" + +#ifdef HAVE_PYTHON + +/** + * Implements pickling behavior. + */ +PyObject *Extension:: +__reduce__(PyObject *self) const { + // CollisionHandlerQueue has no interesting properties. + return Py_BuildValue("(O())", Py_TYPE(self)); +} + +#endif diff --git a/panda/src/collide/collisionHandlerQueue_ext.h b/panda/src/collide/collisionHandlerQueue_ext.h new file mode 100644 index 0000000000..23b2768e61 --- /dev/null +++ b/panda/src/collide/collisionHandlerQueue_ext.h @@ -0,0 +1,37 @@ +/** + * 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." + * + * @file collisionHandler_ext.h + * @author rdb + * @date 2020-12-31 + */ + +#ifndef COLLISIONHANDLERQUEUE_EXT_H +#define COLLISIONHANDLERQUEUE_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "collisionHandlerQueue.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for CollisionHandlerQueue, which are + * called instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__reduce__(PyObject *self) const; +}; + +#endif // HAVE_PYTHON + +#endif // COLLISIONHANDLERQUEUE_EXT_H diff --git a/panda/src/collide/collisionTraverser.h b/panda/src/collide/collisionTraverser.h index 688f7e88a5..f343f905a4 100644 --- a/panda/src/collide/collisionTraverser.h +++ b/panda/src/collide/collisionTraverser.h @@ -24,6 +24,7 @@ #include "pset.h" #include "register_type.h" +#include "extension.h" class CollisionNode; class CollisionRecorder; @@ -79,6 +80,9 @@ PUBLISHED: void output(std::ostream &out) const; void write(std::ostream &out, int indent_level) const; + EXTENSION(PyObject *__getstate__() const); + EXTENSION(void __setstate__(PyObject *state)); + private: typedef pvector LevelStatesSingle; void prepare_colliders_single(LevelStatesSingle &level_states, const NodePath &root); diff --git a/panda/src/collide/collisionTraverser_ext.cxx b/panda/src/collide/collisionTraverser_ext.cxx new file mode 100644 index 0000000000..65b934be5b --- /dev/null +++ b/panda/src/collide/collisionTraverser_ext.cxx @@ -0,0 +1,73 @@ +/** + * 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." + * + * @file collisionTraverser_ext.cxx + * @author rdb + * @date 2020-12-31 + */ + +#include "collisionTraverser_ext.h" + +#ifdef HAVE_PYTHON + +/** + * Implements pickling behavior. + */ +PyObject *Extension:: +__getstate__() const { + extern struct Dtool_PyTypedObject Dtool_CollisionHandler; + extern struct Dtool_PyTypedObject Dtool_CollisionTraverser; + extern struct Dtool_PyTypedObject Dtool_NodePath; + + const std::string &name = _this->get_name(); + size_t num_colliders = _this->get_num_colliders(); + + PyObject *state = PyTuple_New(num_colliders * 2 + 3); + PyTuple_SET_ITEM(state, 0, PyUnicode_FromStringAndSize(name.data(), name.size())); + PyTuple_SET_ITEM(state, 1, PyBool_FromLong(_this->get_respect_prev_transform())); + PyTuple_SET_ITEM(state, 2, PyLong_FromLong((long)num_colliders)); + + for (size_t i = 0; i < num_colliders; ++i) { + NodePath *collider = new NodePath(_this->get_collider(i)); + PyTuple_SET_ITEM(state, i * 2 + 3, + DTool_CreatePyInstance((void *)collider, Dtool_NodePath, true, false)); + + PT(CollisionHandler) handler = _this->get_handler(*collider); + handler->ref(); + PyTuple_SET_ITEM(state, i * 2 + 4, + DTool_CreatePyInstanceTyped((void *)handler.p(), Dtool_CollisionHandler, true, false, handler->get_type_index())); + handler.cheat() = nullptr; + } + + return state; +} + +/** + * Takes the value returned by __getstate__ and uses it to freshly initialize + * this CollisionTraverser object. + */ +void Extension:: +__setstate__(PyObject *state) { + _this->clear_colliders(); + + Py_ssize_t len = 0; + const char *data = PyUnicode_AsUTF8AndSize(PyTuple_GET_ITEM(state, 0), &len); + _this->set_name(std::string(data, len)); + + _this->set_respect_prev_transform(PyTuple_GET_ITEM(state, 1) != Py_False); + size_t num_colliders = (ssize_t)PyLong_AsLong(PyTuple_GET_ITEM(state, 2)); + + for (size_t i = 0; i < num_colliders; ++i) { + NodePath *collider = (NodePath *)DtoolInstance_VOID_PTR(PyTuple_GET_ITEM(state, i * 2 + 3)); + CollisionHandler *handler = (CollisionHandler *)DtoolInstance_VOID_PTR(PyTuple_GET_ITEM(state, i * 2 + 4)); + + _this->add_collider(*collider, handler); + } +} + +#endif diff --git a/panda/src/collide/collisionTraverser_ext.h b/panda/src/collide/collisionTraverser_ext.h new file mode 100644 index 0000000000..445e4bcf5d --- /dev/null +++ b/panda/src/collide/collisionTraverser_ext.h @@ -0,0 +1,38 @@ +/** + * 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." + * + * @file collisionTraverser_ext.h + * @author rdb + * @date 2020-12-31 + */ + +#ifndef COLLISIONTRAVERSER_EXT_H +#define COLLISIONTRAVERSER_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "collisionTraverser.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for CollisionTraverser, which are + * called instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__getstate__() const; + void __setstate__(PyObject *state); +}; + +#endif // HAVE_PYTHON + +#endif // COLLISIONTRAVERSER_EXT_H diff --git a/panda/src/collide/p3collide_ext_composite.cxx b/panda/src/collide/p3collide_ext_composite.cxx index 4e61af1ace..76e458c0f3 100644 --- a/panda/src/collide/p3collide_ext_composite.cxx +++ b/panda/src/collide/p3collide_ext_composite.cxx @@ -1 +1,5 @@ +#include "collisionHandlerEvent_ext.cxx" +#include "collisionHandlerPhysical_ext.cxx" +#include "collisionHandlerQueue_ext.cxx" #include "collisionPolygon_ext.cxx" +#include "collisionTraverser_ext.cxx" diff --git a/panda/src/display/graphicsStateGuardian_ext.cxx b/panda/src/display/graphicsStateGuardian_ext.cxx index 3738ef82b8..9e30ec4f30 100644 --- a/panda/src/display/graphicsStateGuardian_ext.cxx +++ b/panda/src/display/graphicsStateGuardian_ext.cxx @@ -25,10 +25,11 @@ static bool traverse_callback(TextureContext *tc, void *data) { PyObject *element = DTool_CreatePyInstanceTyped(tex, Dtool_Texture, true, false, tex->get_type_index()); - tex->ref(); + tex.cheat() = nullptr; PyObject *list = (PyObject *) data; PyList_Append(list, element); + Py_DECREF(element); return true; } diff --git a/panda/src/egg/eggAnimPreload.h b/panda/src/egg/eggAnimPreload.h index e2b52a5c64..1eef9f8084 100644 --- a/panda/src/egg/eggAnimPreload.h +++ b/panda/src/egg/eggAnimPreload.h @@ -37,6 +37,9 @@ PUBLISHED: INLINE bool has_num_frames() const; INLINE int get_num_frames() const; + MAKE_PROPERTY2(fps, has_fps, get_fps, set_fps, clear_fps); + MAKE_PROPERTY2(num_frames, has_num_frames, get_num_frames, set_num_frames, clear_num_frames); + virtual void write(std::ostream &out, int indent_level) const; private: diff --git a/panda/src/egg/eggComment.h b/panda/src/egg/eggComment.h index 17611ed5d5..5e02595d3c 100644 --- a/panda/src/egg/eggComment.h +++ b/panda/src/egg/eggComment.h @@ -41,6 +41,8 @@ PUBLISHED: virtual void write(std::ostream &out, int indent_level) const; + EXTENSION(PyObject *__reduce__() const); + private: std::string _comment; diff --git a/panda/src/egg/eggComment_ext.cxx b/panda/src/egg/eggComment_ext.cxx new file mode 100644 index 0000000000..f058964066 --- /dev/null +++ b/panda/src/egg/eggComment_ext.cxx @@ -0,0 +1,32 @@ +/** + * 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." + * + * @file eggComment_ext.cxx + * @author rdb + * @date 2021-01-01 + */ + +#include "eggComment_ext.h" + +#ifdef HAVE_PYTHON + +/** + * Implements pickle support. + */ +PyObject *Extension:: +__reduce__() const { + extern struct Dtool_PyTypedObject Dtool_EggComment; + + std::string node_name = _this->get_name(); + std::string comment = _this->get_comment(); + return Py_BuildValue("O(s#s#)", (PyObject *)&Dtool_EggComment, + node_name.data(), (Py_ssize_t)node_name.length(), + comment.data(), (Py_ssize_t)comment.length()); +} + +#endif diff --git a/panda/src/egg/eggComment_ext.h b/panda/src/egg/eggComment_ext.h new file mode 100644 index 0000000000..2f13498d43 --- /dev/null +++ b/panda/src/egg/eggComment_ext.h @@ -0,0 +1,37 @@ +/** + * 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." + * + * @file eggComment_ext.h + * @author rdb + * @date 2021-01-01 + */ + +#ifndef EGGCOMMENT_EXT_H +#define EGGCOMMENT_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "eggComment.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for EggComment, which are + * called instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__reduce__() const; +}; + +#endif // HAVE_PYTHON + +#endif // EGGCOMMENT_EXT_H diff --git a/panda/src/egg/eggCoordinateSystem.h b/panda/src/egg/eggCoordinateSystem.h index 54a76e6f78..a3e105827c 100644 --- a/panda/src/egg/eggCoordinateSystem.h +++ b/panda/src/egg/eggCoordinateSystem.h @@ -36,6 +36,8 @@ PUBLISHED: virtual void write(std::ostream &out, int indent_level) const; + EXTENSION(PyObject *__reduce__() const); + private: CoordinateSystem _value; diff --git a/panda/src/egg/eggCoordinateSystem_ext.cxx b/panda/src/egg/eggCoordinateSystem_ext.cxx new file mode 100644 index 0000000000..2b26b0abec --- /dev/null +++ b/panda/src/egg/eggCoordinateSystem_ext.cxx @@ -0,0 +1,32 @@ +/** + * 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." + * + * @file eggCoordinateSystem_ext.cxx + * @author rdb + * @date 2021-01-01 + */ + +#include "eggCoordinateSystem_ext.h" + +#ifdef HAVE_PYTHON + +/** + * Implements pickle support. + */ +PyObject *Extension:: +__reduce__() const { + extern struct Dtool_PyTypedObject Dtool_EggCoordinateSystem; + + // We can't use the regular EggNode handling for EggCoordinateSystem, because + // the node is removed from the EggData after reading. + // Oh well, this is more efficient anyway. + int value = _this->get_value(); + return Py_BuildValue("O(i)", (PyObject *)&Dtool_EggCoordinateSystem, value); +} + +#endif diff --git a/panda/src/egg/eggCoordinateSystem_ext.h b/panda/src/egg/eggCoordinateSystem_ext.h new file mode 100644 index 0000000000..c5dc3794c6 --- /dev/null +++ b/panda/src/egg/eggCoordinateSystem_ext.h @@ -0,0 +1,37 @@ +/** + * 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." + * + * @file eggCoordinateSystem_ext.h + * @author rdb + * @date 2021-01-01 + */ + +#ifndef EGGCOORDINATESYSTEM_EXT_H +#define EGGCOORDINATESYSTEM_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "eggCoordinateSystem.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for EggCoordinateSystem, which are + * called instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__reduce__() const; +}; + +#endif // HAVE_PYTHON + +#endif // EGGCOORDINATESYSTEM_EXT_H diff --git a/panda/src/egg/eggNode.h b/panda/src/egg/eggNode.h index 950ae5e257..48f4443ed4 100644 --- a/panda/src/egg/eggNode.h +++ b/panda/src/egg/eggNode.h @@ -22,6 +22,7 @@ #include "lmatrix.h" #include "pointerTo.h" #include "referenceCount.h" +#include "extension.h" class EggGroupNode; class EggRenderMode; @@ -90,6 +91,7 @@ PUBLISHED: void test_under_integrity() const { } #endif // _DEBUG + EXTENSION(PyObject *__reduce__() const); protected: enum UnderFlags { diff --git a/panda/src/egg/eggNode_ext.cxx b/panda/src/egg/eggNode_ext.cxx new file mode 100644 index 0000000000..3056e40d6d --- /dev/null +++ b/panda/src/egg/eggNode_ext.cxx @@ -0,0 +1,93 @@ +/** + * 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." + * + * @file eggNode_ext.cxx + * @author rdb + * @date 2021-01-01 + */ + +#include "eggNode_ext.h" +#include "eggData.h" + +#ifdef HAVE_PYTHON + +/** + * Implements pickle support. + */ +PyObject *Extension:: +__reduce__() const { + extern struct Dtool_PyTypedObject Dtool_EggNode; + + // Find the parse_egg_node function in this module. + PyObject *sys_modules = PyImport_GetModuleDict(); + nassertr_always(sys_modules != nullptr, nullptr); + + PyObject *module_name = PyObject_GetAttrString((PyObject *)&Dtool_EggNode, "__module__"); + nassertr_always(module_name != nullptr, nullptr); + + PyObject *module = PyDict_GetItem(sys_modules, module_name); + Py_DECREF(module_name); + nassertr_always(module != nullptr, nullptr); + + PyObject *func; + if (_this->is_of_type(EggData::get_class_type())) { + func = PyObject_GetAttrString(module, "parse_egg_data"); + } else { + func = PyObject_GetAttrString(module, "parse_egg_node"); + } + nassertr_always(func != nullptr, nullptr); + + // Get the egg syntax to pass to the parse_egg_node function. + std::ostringstream stream; + _this->write(stream, INT_MIN); + std::string data = stream.str(); + size_t length = data.size(); + + // Trim trailing whitespace. + while (length > 0 && isspace(data[length - 1])) { + --length; + } + + return Py_BuildValue("N(s#)", func, data.data(), (Py_ssize_t)length); +} + +/** + * Parses an EggData from the raw egg syntax. + */ +PT(EggData) parse_egg_data(const std::string &egg_syntax) { + PT(EggData) data = new EggData; + data->set_auto_resolve_externals(false); + + std::istringstream in(egg_syntax); + + if (!data->read(in)) { + PyErr_Format(PyExc_RuntimeError, "failed to parse egg data"); + return nullptr; + } + + return data; +} + +/** + * Parses a single egg node from the raw egg syntax. + */ +PT(EggNode) parse_egg_node(const std::string &egg_syntax) { + PT(EggData) data = parse_egg_data(egg_syntax); + if (data == nullptr) { + return nullptr; + } + + if (data->size() != 1) { + PyErr_Format(PyExc_RuntimeError, "expected exactly one node"); + return nullptr; + } + + return data->remove_child(data->get_first_child()); +} + +#endif diff --git a/panda/src/egg/eggNode_ext.h b/panda/src/egg/eggNode_ext.h new file mode 100644 index 0000000000..aa94aa41cd --- /dev/null +++ b/panda/src/egg/eggNode_ext.h @@ -0,0 +1,42 @@ +/** + * 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." + * + * @file eggNode_ext.h + * @author rdb + * @date 2021-01-01 + */ + +#ifndef EGGNODE_EXT_H +#define EGGNODE_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "eggNode.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for EggNode, which are + * called instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__reduce__() const; +}; + +BEGIN_PUBLISH +PT(EggData) parse_egg_data(const std::string &egg_syntax); +PT(EggNode) parse_egg_node(const std::string &egg_syntax); +END_PUBLISH + +#endif // HAVE_PYTHON + +#endif // EGGNODE_EXT_H diff --git a/panda/src/egg/lexer.cxx.prebuilt b/panda/src/egg/lexer.cxx.prebuilt index a58be815e6..178bb5c152 100644 --- a/panda/src/egg/lexer.cxx.prebuilt +++ b/panda/src/egg/lexer.cxx.prebuilt @@ -971,6 +971,7 @@ extern "C" int eggyywrap(void); // declared below. static int yyinput(void); // declared by flex. +int eggyylex_destroy(void); //////////////////////////////////////////////////////////////////// // Static variables @@ -1018,6 +1019,15 @@ egg_init_lexer(istream &in, const string &filename) { initial_token = START_EGG; } +void +egg_cleanup_lexer() { + // Reset the lexer state. + eggyylex_destroy(); + + input_p = nullptr; + egg_filename.clear(); +} + void egg_start_group_body() { /* Set the initial state to begin within a group_body context, diff --git a/panda/src/egg/lexer.lxx b/panda/src/egg/lexer.lxx index e88e5a388f..88aed62e46 100644 --- a/panda/src/egg/lexer.lxx +++ b/panda/src/egg/lexer.lxx @@ -26,6 +26,7 @@ extern "C" int eggyywrap(void); // declared below. static int yyinput(void); // declared by flex. +int eggyylex_destroy(void); //////////////////////////////////////////////////////////////////// // Static variables @@ -73,6 +74,15 @@ egg_init_lexer(istream &in, const string &filename) { initial_token = START_EGG; } +void +egg_cleanup_lexer() { + // Reset the lexer state. + yylex_destroy(); + + input_p = nullptr; + egg_filename.clear(); +} + void egg_start_group_body() { /* Set the initial state to begin within a group_body context, diff --git a/panda/src/egg/lexerDefs.h b/panda/src/egg/lexerDefs.h index 4e39cb0b10..6b1f321cb5 100644 --- a/panda/src/egg/lexerDefs.h +++ b/panda/src/egg/lexerDefs.h @@ -21,6 +21,7 @@ #include void egg_init_lexer(std::istream &in, const std::string &filename); +void egg_cleanup_lexer(); void egg_start_group_body(); void egg_start_texture_body(); void egg_start_primitive_body(); diff --git a/panda/src/egg/p3egg_ext_composite.cxx b/panda/src/egg/p3egg_ext_composite.cxx new file mode 100644 index 0000000000..f94bca9c30 --- /dev/null +++ b/panda/src/egg/p3egg_ext_composite.cxx @@ -0,0 +1,4 @@ +#include "eggComment_ext.cxx" +#include "eggCoordinateSystem_ext.cxx" +#include "eggGroupNode_ext.cxx" +#include "eggNode_ext.cxx" diff --git a/panda/src/egg/parser.cxx.prebuilt b/panda/src/egg/parser.cxx.prebuilt index 576f851740..d9b32aced5 100644 --- a/panda/src/egg/parser.cxx.prebuilt +++ b/panda/src/egg/parser.cxx.prebuilt @@ -223,6 +223,8 @@ egg_cleanup_parser() { textures.clear(); materials.clear(); groups.clear(); + + egg_cleanup_lexer(); } diff --git a/panda/src/egg/parser.yxx b/panda/src/egg/parser.yxx index a41b08ba57..6f4ed4d443 100644 --- a/panda/src/egg/parser.yxx +++ b/panda/src/egg/parser.yxx @@ -153,6 +153,8 @@ egg_cleanup_parser() { textures.clear(); materials.clear(); groups.clear(); + + egg_cleanup_lexer(); } %} diff --git a/panda/src/express/pointerToArray.h b/panda/src/express/pointerToArray.h index ca6aab7ed4..39837df402 100644 --- a/panda/src/express/pointerToArray.h +++ b/panda/src/express/pointerToArray.h @@ -117,6 +117,8 @@ PUBLISHED: INLINE size_t count(const Element &) const; #ifdef HAVE_PYTHON + EXTENSION(PyObject *__reduce__(PyObject *self) const); + EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags)); EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const); #endif @@ -273,6 +275,8 @@ PUBLISHED: INLINE size_t count(const Element &) const; #ifdef HAVE_PYTHON + EXTENSION(PyObject *__reduce__(PyObject *self) const); + EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const); EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const); #endif diff --git a/panda/src/express/pointerToArrayBase.h b/panda/src/express/pointerToArrayBase.h index 3c985ff379..898d5c82ad 100644 --- a/panda/src/express/pointerToArrayBase.h +++ b/panda/src/express/pointerToArrayBase.h @@ -77,6 +77,12 @@ protected: PUBLISHED: INLINE ~PointerToArrayBase(); + +#ifdef CPPPARSER + // These are implemented in PointerToVoid, but expose them here. + INLINE bool operator == (const PointerToArrayBase &other) const; + INLINE bool operator != (const PointerToArrayBase &other) const; +#endif }; #include "pointerToArrayBase.I" diff --git a/panda/src/express/pointerToArray_ext.I b/panda/src/express/pointerToArray_ext.I index 9d259df11b..69e140c09c 100644 --- a/panda/src/express/pointerToArray_ext.I +++ b/panda/src/express/pointerToArray_ext.I @@ -219,6 +219,25 @@ get_subdata(size_t n, size_t count) const { return PyBytes_FromStringAndSize((char *)(this->_this->p() + n), sizeof(Element) * count); } +/** + * Implements pickle support. + */ +template +INLINE PyObject *Extension >:: +__reduce__(PyObject *self) const { + // This preserves the distinction between a null vs. an empty PTA, though I'm + // not sure that this distinction matters to anyone. + if (this->_this->is_null()) { + return Py_BuildValue("O()", Py_TYPE(self)); + } + else if (this->_this->empty()) { + return Py_BuildValue("O(())", Py_TYPE(self)); + } + else { + return Py_BuildValue("O(N)", Py_TYPE(self), get_data()); + } +} + /** * Same as get_element(), this returns the nth element of the array. */ @@ -256,6 +275,22 @@ get_subdata(size_t n, size_t count) const { return PyBytes_FromStringAndSize((char *)(this->_this->p() + n), sizeof(Element) * count); } +/** + * Implements pickle support. + */ +template +INLINE PyObject *Extension >:: +__reduce__(PyObject *self) const { + // This preserves the distinction between a null vs. an empty PTA, though I'm + // not sure that this distinction matters to anyone. + if (!this->_this->is_null() && this->_this->empty()) { + return Py_BuildValue("O([])", Py_TYPE(self)); + } + else { + return Py_BuildValue("O(N)", Py_TYPE(self), get_data()); + } +} + /** * This is used to implement the buffer protocol, in order to allow efficient * access to the array data through a Python multiview object. diff --git a/panda/src/express/pointerToArray_ext.h b/panda/src/express/pointerToArray_ext.h index f17d1b634a..ce6104791b 100644 --- a/panda/src/express/pointerToArray_ext.h +++ b/panda/src/express/pointerToArray_ext.h @@ -40,6 +40,8 @@ public: INLINE void set_data(PyObject *data); INLINE PyObject *get_subdata(size_t n, size_t count) const; + INLINE PyObject *__reduce__(PyObject *self) const; + INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags); INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const; }; @@ -75,6 +77,8 @@ public: INLINE PyObject *get_data() const; INLINE PyObject *get_subdata(size_t n, size_t count) const; + INLINE PyObject *__reduce__(PyObject *self) const; + INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const; INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const; }; diff --git a/panda/src/pgraph/loaderFileTypeRegistry.h b/panda/src/pgraph/loaderFileTypeRegistry.h index cccb3af333..27f29664dd 100644 --- a/panda/src/pgraph/loaderFileTypeRegistry.h +++ b/panda/src/pgraph/loaderFileTypeRegistry.h @@ -53,6 +53,8 @@ PUBLISHED: static LoaderFileTypeRegistry *get_global_ptr(); + EXTENSION(PyObject *__reduce__() const); + private: void record_extension(const std::string &extension, LoaderFileType *type); diff --git a/panda/src/pgraph/loaderFileTypeRegistry_ext.cxx b/panda/src/pgraph/loaderFileTypeRegistry_ext.cxx index 6c9f78a876..0d9aeac19a 100644 --- a/panda/src/pgraph/loaderFileTypeRegistry_ext.cxx +++ b/panda/src/pgraph/loaderFileTypeRegistry_ext.cxx @@ -18,6 +18,7 @@ #include "pythonLoaderFileType.h" extern struct Dtool_PyTypedObject Dtool_LoaderFileType; +extern struct Dtool_PyTypedObject Dtool_LoaderFileTypeRegistry; /** * Registers a loader file type that is implemented in Python. @@ -106,4 +107,13 @@ unregister_type(PyObject *type) { Py_XDECREF(save_func); } +/** + * Implements pickle support. + */ +PyObject *Extension:: +__reduce__() const { + PyObject *func = PyObject_GetAttrString((PyObject *)&Dtool_LoaderFileTypeRegistry, "get_global_ptr"); + return Py_BuildValue("N()", func); +} + #endif diff --git a/panda/src/pgraph/loaderFileTypeRegistry_ext.h b/panda/src/pgraph/loaderFileTypeRegistry_ext.h index 9c9815c20d..e5325101e6 100644 --- a/panda/src/pgraph/loaderFileTypeRegistry_ext.h +++ b/panda/src/pgraph/loaderFileTypeRegistry_ext.h @@ -33,6 +33,8 @@ public: void register_deferred_type(PyObject *entry_point); void unregister_type(PyObject *type); + + PyObject *__reduce__() const; }; #endif // HAVE_PYTHON diff --git a/tests/collide/test_collision_handlers.py b/tests/collide/test_collision_handlers.py new file mode 100644 index 0000000000..87d602ef3b --- /dev/null +++ b/tests/collide/test_collision_handlers.py @@ -0,0 +1,108 @@ +from direct.stdpy.pickle import dumps, loads +from panda3d.core import NodePath, CollisionNode + + +def test_collision_handler_event_pickle(): + from panda3d.core import CollisionHandlerEvent + + handler = CollisionHandlerEvent() + handler.add_in_pattern("abcdefg") + handler.add_in_pattern("test") + handler.add_again_pattern("again pattern") + handler.add_again_pattern("another again pattern") + handler.add_out_pattern("out pattern") + + handler = loads(dumps(handler, -1)) + + assert tuple(handler.in_patterns) == ("abcdefg", "test") + assert tuple(handler.again_patterns) == ("again pattern", "another again pattern") + assert tuple(handler.out_patterns) == ("out pattern",) + + +def test_collision_handler_queue_pickle(): + from panda3d.core import CollisionHandlerQueue + + handler = CollisionHandlerQueue() + handler = loads(dumps(handler, -1)) + assert type(handler) == CollisionHandlerQueue + + +def test_collision_handler_floor_pickle(): + from panda3d.core import CollisionHandlerFloor + + collider1 = NodePath(CollisionNode("collider1")) + collider2 = NodePath(CollisionNode("collider2")) + target1 = NodePath("target1") + target2 = NodePath("target2") + center = NodePath("center") + + handler = CollisionHandlerFloor() + handler.add_out_pattern("out pattern") + handler.add_collider(collider1, target1) + handler.add_collider(collider2, target2) + handler.center = center + handler.offset = 1.0 + handler.reach = 2.0 + handler.max_velocity = 3.0 + + handler = loads(dumps(handler, -1)) + + assert tuple(handler.in_patterns) == () + assert tuple(handler.again_patterns) == () + assert tuple(handler.out_patterns) == ("out pattern",) + assert handler.center.name == "center" + assert handler.offset == 1.0 + assert handler.reach == 2.0 + assert handler.max_velocity == 3.0 + + +def test_collision_handler_gravity_pickle(): + from panda3d.core import CollisionHandlerGravity + + collider1 = NodePath(CollisionNode("collider1")) + collider2 = NodePath(CollisionNode("collider2")) + target1 = NodePath("target1") + target2 = NodePath("target2") + + handler = CollisionHandlerGravity() + handler.add_out_pattern("out pattern") + handler.add_collider(collider1, target1) + handler.add_collider(collider2, target2) + handler.offset = 1.0 + handler.reach = 2.0 + handler.max_velocity = 3.0 + handler.gravity = -4.0 + + handler = loads(dumps(handler, -1)) + + assert tuple(handler.in_patterns) == () + assert tuple(handler.again_patterns) == () + assert tuple(handler.out_patterns) == ("out pattern",) + assert handler.center == None + assert handler.offset == 1.0 + assert handler.reach == 2.0 + assert handler.max_velocity == 3.0 + assert handler.gravity == -4.0 + + +def test_collision_handler_pusher_pickle(): + from panda3d.core import CollisionHandlerPusher + + collider1 = NodePath(CollisionNode("collider1")) + collider2 = NodePath(CollisionNode("collider2")) + target1 = NodePath("target1") + target2 = NodePath("target2") + + handler = CollisionHandlerPusher() + handler.add_again_pattern("again pattern") + handler.add_collider(collider1, target1) + handler.add_collider(collider2, target2) + handler.horizontal = True + + handler = loads(dumps(handler, -1)) + + assert tuple(handler.in_patterns) == () + assert tuple(handler.again_patterns) == ("again pattern",) + assert tuple(handler.out_patterns) == () + assert not handler.has_center() + assert handler.horizontal diff --git a/tests/collide/test_collision_traverser.py b/tests/collide/test_collision_traverser.py new file mode 100644 index 0000000000..fd1f93ee56 --- /dev/null +++ b/tests/collide/test_collision_traverser.py @@ -0,0 +1,31 @@ +from panda3d.core import CollisionTraverser, CollisionHandlerQueue +from panda3d.core import NodePath, CollisionNode + + + +def test_collision_traverser_pickle(): + from direct.stdpy.pickle import dumps, loads + + handler = CollisionHandlerQueue() + + collider1 = NodePath(CollisionNode("collider1")) + collider2 = NodePath(CollisionNode("collider2")) + + trav = CollisionTraverser("test123") + trav.respect_prev_transform = True + trav.add_collider(collider1, handler) + trav.add_collider(collider2, handler) + + trav = loads(dumps(trav, -1)) + assert trav.respect_prev_transform is True + + assert trav.name == "test123" + assert trav.get_num_colliders() == 2 + collider1 = trav.get_collider(0) + collider2 = trav.get_collider(1) + assert collider1.name == "collider1" + assert collider2.name == "collider2" + + # Two colliders must still be the same object; this only works with our own + # version of the pickle module, in direct.stdpy.pickle. + assert trav.get_handler(collider1) == trav.get_handler(collider2) diff --git a/tests/express/test_pointertoarray.py b/tests/express/test_pointertoarray.py new file mode 100644 index 0000000000..e68fa2ca36 --- /dev/null +++ b/tests/express/test_pointertoarray.py @@ -0,0 +1,71 @@ +def test_pta_float_compare(): + from panda3d.core import PTA_float, CPTA_float + + # Two null PTAs + assert PTA_float() == PTA_float() + assert not (PTA_float() != PTA_float()) + + # Two non-null PTAs + assert PTA_float([1]) != PTA_float([1]) + assert not (PTA_float([1]) == PTA_float([1])) + + # A copy of a PTA + pta = PTA_float([1]) + assert pta == PTA_float(pta) + assert not (pta != PTA_float(pta)) + + # A const copy of a PTA + pta = PTA_float([1]) + cpta = CPTA_float(pta) + assert pta == cpta + assert not (pta != cpta) + + +def test_pta_float_pickle(): + from panda3d.core import PTA_float + from direct.stdpy.pickle import dumps, loads, HIGHEST_PROTOCOL + + null_pta = PTA_float() + + empty_pta = PTA_float([]) + + data_pta = PTA_float([1.0, 2.0, 3.0]) + data = data_pta.get_data() + + for proto in range(1, HIGHEST_PROTOCOL + 1): + null_pta2 = loads(dumps(null_pta, proto)) + assert null_pta2.is_null() + assert len(null_pta2) == 0 + + empty_pta2 = loads(dumps(empty_pta, proto)) + assert not empty_pta2.is_null() + assert len(empty_pta2) == 0 + + data_pta2 = loads(dumps(data_pta, proto)) + assert tuple(data_pta2) == (1.0, 2.0, 3.0) + assert data_pta2.get_data() == data_pta.get_data() + + +def test_cpta_float_pickle(): + from panda3d.core import PTA_float, CPTA_float + from direct.stdpy.pickle import dumps, loads, HIGHEST_PROTOCOL + + null_pta = CPTA_float(PTA_float()) + + empty_pta = CPTA_float([]) + + data_pta = CPTA_float([1.0, 2.0, 3.0]) + data = data_pta.get_data() + + for proto in range(1, HIGHEST_PROTOCOL + 1): + null_pta2 = loads(dumps(null_pta, proto)) + assert null_pta2.is_null() + assert len(null_pta2) == 0 + + empty_pta2 = loads(dumps(empty_pta, proto)) + assert not empty_pta2.is_null() + assert len(empty_pta2) == 0 + + data_pta2 = loads(dumps(data_pta, proto)) + assert tuple(data_pta2) == (1.0, 2.0, 3.0) + assert data_pta2.get_data() == data_pta.get_data() diff --git a/tests/pgraph/test_loader_types.py b/tests/pgraph/test_loader_types.py index 8fca1c5849..2a0114852f 100644 --- a/tests/pgraph/test_loader_types.py +++ b/tests/pgraph/test_loader_types.py @@ -3,6 +3,7 @@ import pytest import tempfile import os from contextlib import contextmanager +import sys @pytest.fixture @@ -218,3 +219,11 @@ def test_loader_ram_cache(test_filename): assert model1 == model2 ModelPool.release_model(model2) + + +@pytest.mark.skipif(sys.version_info < (3, 4), reason="Requires Python 3.4") +def test_loader_file_type_registry_pickle(): + from direct.stdpy.pickle import dumps, loads + + registry = LoaderFileTypeRegistry.get_global_ptr() + assert loads(dumps(registry, -1)) == registry diff --git a/tests/putil/test_bitmask.py b/tests/putil/test_bitmask.py index 9e80758d78..8faaf2fcc8 100644 --- a/tests/putil/test_bitmask.py +++ b/tests/putil/test_bitmask.py @@ -86,3 +86,13 @@ def test_bitmask_pickle(): data = pickle.dumps(mask1, -1) mask2 = pickle.loads(data) assert mask1 == mask2 + + mask1 = DoubleBitMaskNative(0x7fffffffffffffff) + data = pickle.dumps(mask1, -1) + mask2 = pickle.loads(data) + assert mask1 == mask2 + + mask1 = DoubleBitMaskNative(1 << (double_num_bits - 1)) + data = pickle.dumps(mask1, -1) + mask2 = pickle.loads(data) + assert mask1 == mask2 diff --git a/tests/stdpy/test_pickle.py b/tests/stdpy/test_pickle.py index 3e8a7dcd25..37c7df4b25 100644 --- a/tests/stdpy/test_pickle.py +++ b/tests/stdpy/test_pickle.py @@ -12,6 +12,20 @@ def test_reduce_persist(): assert tuple(parent2.children) == (child2,) +def test_pickle_copy(): + from panda3d.core import PandaNode, NodePath + + # Make two Python wrappers pointing to the same node + node1 = PandaNode("node") + node2 = NodePath(node1).node() + assert node1.this == node2.this + assert id(node1) != id(node2) + + # Test that pickling and loading still results in the same node object. + node1, node2 = loads(dumps([node1, node2])) + assert node1 == node2 + + def test_pickle_error(): class ErroneousPickleable(object): def __reduce__(self):