瀏覽代碼

Merge branch 'release/1.10.x'

rdb 5 年之前
父節點
當前提交
e099d9e787
共有 54 個文件被更改,包括 1260 次插入44 次删除
  1. 33 22
      direct/src/stdpy/pickle.py
  2. 3 0
      dtool/src/interrogate/functionRemap.cxx
  3. 23 10
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  4. 2 2
      makepanda/makepanda.py
  5. 46 0
      panda/src/collide/collisionHandlerEvent.cxx
  6. 9 1
      panda/src/collide/collisionHandlerEvent.h
  7. 70 0
      panda/src/collide/collisionHandlerEvent_ext.cxx
  8. 38 0
      panda/src/collide/collisionHandlerEvent_ext.h
  9. 21 8
      panda/src/collide/collisionHandlerFloor.cxx
  10. 3 0
      panda/src/collide/collisionHandlerFloor.h
  11. 29 0
      panda/src/collide/collisionHandlerGravity.cxx
  12. 3 0
      panda/src/collide/collisionHandlerGravity.h
  13. 5 0
      panda/src/collide/collisionHandlerPhysical.h
  14. 108 0
      panda/src/collide/collisionHandlerPhysical_ext.cxx
  15. 38 0
      panda/src/collide/collisionHandlerPhysical_ext.h
  16. 21 0
      panda/src/collide/collisionHandlerPusher.cxx
  17. 3 0
      panda/src/collide/collisionHandlerPusher.h
  18. 3 0
      panda/src/collide/collisionHandlerQueue.h
  19. 27 0
      panda/src/collide/collisionHandlerQueue_ext.cxx
  20. 37 0
      panda/src/collide/collisionHandlerQueue_ext.h
  21. 4 0
      panda/src/collide/collisionTraverser.h
  22. 73 0
      panda/src/collide/collisionTraverser_ext.cxx
  23. 38 0
      panda/src/collide/collisionTraverser_ext.h
  24. 4 0
      panda/src/collide/p3collide_ext_composite.cxx
  25. 2 1
      panda/src/display/graphicsStateGuardian_ext.cxx
  26. 3 0
      panda/src/egg/eggAnimPreload.h
  27. 2 0
      panda/src/egg/eggComment.h
  28. 32 0
      panda/src/egg/eggComment_ext.cxx
  29. 37 0
      panda/src/egg/eggComment_ext.h
  30. 2 0
      panda/src/egg/eggCoordinateSystem.h
  31. 32 0
      panda/src/egg/eggCoordinateSystem_ext.cxx
  32. 37 0
      panda/src/egg/eggCoordinateSystem_ext.h
  33. 2 0
      panda/src/egg/eggNode.h
  34. 93 0
      panda/src/egg/eggNode_ext.cxx
  35. 42 0
      panda/src/egg/eggNode_ext.h
  36. 10 0
      panda/src/egg/lexer.cxx.prebuilt
  37. 10 0
      panda/src/egg/lexer.lxx
  38. 1 0
      panda/src/egg/lexerDefs.h
  39. 4 0
      panda/src/egg/p3egg_ext_composite.cxx
  40. 2 0
      panda/src/egg/parser.cxx.prebuilt
  41. 2 0
      panda/src/egg/parser.yxx
  42. 4 0
      panda/src/express/pointerToArray.h
  43. 6 0
      panda/src/express/pointerToArrayBase.h
  44. 35 0
      panda/src/express/pointerToArray_ext.I
  45. 4 0
      panda/src/express/pointerToArray_ext.h
  46. 2 0
      panda/src/pgraph/loaderFileTypeRegistry.h
  47. 10 0
      panda/src/pgraph/loaderFileTypeRegistry_ext.cxx
  48. 2 0
      panda/src/pgraph/loaderFileTypeRegistry_ext.h
  49. 108 0
      tests/collide/test_collision_handlers.py
  50. 31 0
      tests/collide/test_collision_traverser.py
  51. 71 0
      tests/express/test_pointertoarray.py
  52. 9 0
      tests/pgraph/test_loader_types.py
  53. 10 0
      tests/putil/test_bitmask.py
  54. 14 0
      tests/stdpy/test_pickle.py

+ 33 - 22
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
 Unfortunately, cPickle cannot be supported, because it does not
 support extensions of this nature. """
 support extensions of this nature. """
 
 
+__all__ = ["PickleError", "PicklingError", "UnpicklingError", "Pickler",
+           "Unpickler", "dump", "dumps", "load", "loads",
+           "HIGHEST_PROTOCOL", "DEFAULT_PROTOCOL"]
+
 import sys
 import sys
-from panda3d.core import BamWriter, BamReader
+from panda3d.core import BamWriter, BamReader, TypedObject
 from copyreg import dispatch_table
 from copyreg import dispatch_table
 
 
 
 
@@ -30,17 +34,29 @@ from copyreg import dispatch_table
 # with the local pickle.py.
 # with the local pickle.py.
 pickle = __import__('pickle')
 pickle = __import__('pickle')
 
 
+HIGHEST_PROTOCOL = pickle.HIGHEST_PROTOCOL
+DEFAULT_PROTOCOL = pickle.DEFAULT_PROTOCOL
+
+PickleError = pickle.PickleError
 PicklingError = pickle.PicklingError
 PicklingError = pickle.PicklingError
+UnpicklingError = pickle.UnpicklingError
+
 BasePickler = pickle._Pickler
 BasePickler = pickle._Pickler
 BaseUnpickler = pickle._Unpickler
 BaseUnpickler = pickle._Unpickler
 
 
 
 
-class _Pickler(BasePickler):
+class Pickler(BasePickler):
 
 
     def __init__(self, *args, **kw):
     def __init__(self, *args, **kw):
         self.bamWriter = BamWriter()
         self.bamWriter = BamWriter()
+        self._canonical = {}
         BasePickler.__init__(self, *args, **kw)
         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
     # We have to duplicate most of the save() method, so we can add
     # support for __reduce_persist__().
     # support for __reduce_persist__().
 
 
@@ -54,6 +70,21 @@ class _Pickler(BasePickler):
             self.save_pers(pid)
             self.save_pers(pid)
             return
             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
         # Check the memo
         x = self.memo.get(id(obj))
         x = self.memo.get(id(obj))
         if x:
         if x:
@@ -61,7 +92,6 @@ class _Pickler(BasePickler):
             return
             return
 
 
         # Check the type dispatch table
         # Check the type dispatch table
-        t = type(obj)
         f = self.dispatch.get(t)
         f = self.dispatch.get(t)
         if f:
         if f:
             f(self, obj) # Call unbound method with explicit self
             f(self, obj) # Call unbound method with explicit self
@@ -146,25 +176,6 @@ class Unpickler(BaseUnpickler):
     BaseUnpickler.dispatch[pickle.REDUCE[0]] = load_reduce
     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
 # Shorthands
 from io import BytesIO
 from io import BytesIO
 
 

+ 3 - 0
dtool/src/interrogate/functionRemap.cxx

@@ -920,6 +920,9 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
             || fname == "__delattr__") {
             || fname == "__delattr__") {
       // Just to prevent these from getting keyword arguments.
       // Just to prevent these from getting keyword arguments.
 
 
+    } else if (fname == "__setstate__") {
+      _args_type = InterfaceMaker::AT_single_arg;
+
     } else {
     } else {
       if (_args_type == InterfaceMaker::AT_varargs) {
       if (_args_type == InterfaceMaker::AT_varargs) {
         // Every other method can take keyword arguments, if they take more
         // Every other method can take keyword arguments, if they take more

+ 23 - 10
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 ClassName = make_safe_name(obj->_itype.get_scoped_name());
     std::string cClassName = obj->_itype.get_true_name();
     std::string cClassName = obj->_itype.get_true_name();
     // string class_name = remap->_cpptype->get_simple_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 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) {
     else if (all_nonconst) {
       // All remaps are non-const.  Also check that this object isn't const.
       // 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;
     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) {
   if (args_type == AT_keyword_args || args_type == AT_varargs) {
     max_required_args = collapse_default_remaps(map_sets, max_required_args);
     max_required_args = collapse_default_remaps(map_sets, max_required_args);
   }
   }

+ 2 - 2
makepanda/makepanda.py

@@ -4397,7 +4397,7 @@ if not PkgSkip("EGG"):
         IGATEFILES.remove("parser.h")
         IGATEFILES.remove("parser.h")
     TargetAdd('libp3egg.in', opts=OPTS, input=IGATEFILES)
     TargetAdd('libp3egg.in', opts=OPTS, input=IGATEFILES)
     TargetAdd('libp3egg.in', opts=['IMOD:panda3d.egg', 'ILIB:libp3egg', 'SRCDIR:panda/src/egg'])
     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/
 # 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_module.obj', opts=['IMOD:panda3d.egg', 'ILIB:egg', 'IMPORT:panda3d.core'])
 
 
     PyTargetAdd('egg.pyd', input='egg_module.obj')
     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='libp3egg_igate.obj')
     PyTargetAdd('egg.pyd', input='libp3egg2pg_igate.obj')
     PyTargetAdd('egg.pyd', input='libp3egg2pg_igate.obj')
     PyTargetAdd('egg.pyd', input='libpandaegg.dll')
     PyTargetAdd('egg.pyd', input='libpandaegg.dll')

+ 46 - 0
panda/src/collide/collisionHandlerEvent.cxx

@@ -153,6 +153,52 @@ flush() {
   end_group();
   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.
  * Throws whatever events are suggested by the list of patterns.
  */
  */

+ 9 - 1
panda/src/collide/collisionHandlerEvent.h

@@ -22,6 +22,7 @@
 
 
 #include "vector_string.h"
 #include "vector_string.h"
 #include "pointerTo.h"
 #include "pointerTo.h"
+#include "extension.h"
 
 
 /**
 /**
  * A specialized kind of CollisionHandler that throws an event for each
  * 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(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(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);
   MAKE_SEQ_PROPERTY(out_patterns, get_num_out_patterns, get_out_pattern);
 
 
   void clear();
   void clear();
   void flush();
   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:
 protected:
   void throw_event_for(const vector_string &patterns, CollisionEntry *entry);
   void throw_event_for(const vector_string &patterns, CollisionEntry *entry);
   void throw_event_pattern(const std::string &pattern, CollisionEntry *entry);
   void throw_event_pattern(const std::string &pattern, CollisionEntry *entry);

+ 70 - 0
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<CollisionHandlerEvent>::
+__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<CollisionHandlerEvent>::
+__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

+ 38 - 0
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<CollisionHandlerEvent> : public ExtensionBase<CollisionHandlerEvent> {
+public:
+  PyObject *__reduce__(PyObject *self) const;
+  void __setstate__(PyObject *self, vector_uchar data);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // COLLISIONHANDLEREVENT_EXT_H

+ 21 - 8
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::
 PN_stdfloat CollisionHandlerFloor::

+ 3 - 0
panda/src/collide/collisionHandlerFloor.h

@@ -43,6 +43,9 @@ PUBLISHED:
   MAKE_PROPERTY(reach, get_reach, set_reach);
   MAKE_PROPERTY(reach, get_reach, set_reach);
   MAKE_PROPERTY(max_velocity, get_max_velocity, set_max_velocity);
   MAKE_PROPERTY(max_velocity, get_max_velocity, set_max_velocity);
 
 
+  void write_datagram(Datagram &destination) const;
+  void read_datagram(DatagramIterator &source);
+
 protected:
 protected:
   PN_stdfloat set_highest_collision(const NodePath &target_node_path, const NodePath &from_node_path, const Entries &entries);
   PN_stdfloat set_highest_collision(const NodePath &target_node_path, const NodePath &from_node_path, const Entries &entries);
   virtual bool handle_entries();
   virtual bool handle_entries();

+ 29 - 0
panda/src/collide/collisionHandlerGravity.cxx

@@ -46,6 +46,35 @@ CollisionHandlerGravity::
 ~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();
+}
+
 /**
 /**
  *
  *
  */
  */

+ 3 - 0
panda/src/collide/collisionHandlerGravity.h

@@ -64,6 +64,9 @@ PUBLISHED:
   MAKE_PROPERTY(gravity, get_gravity, set_gravity);
   MAKE_PROPERTY(gravity, get_gravity, set_gravity);
   MAKE_PROPERTY(max_velocity, get_max_velocity, set_max_velocity);
   MAKE_PROPERTY(max_velocity, get_max_velocity, set_max_velocity);
 
 
+  void write_datagram(Datagram &destination) const;
+  void read_datagram(DatagramIterator &source);
+
 protected:
 protected:
   PN_stdfloat set_highest_collision(const NodePath &target_node_path, const NodePath &from_node_path, const Entries &entries);
   PN_stdfloat set_highest_collision(const NodePath &target_node_path, const NodePath &from_node_path, const Entries &entries);
   virtual bool handle_entries();
   virtual bool handle_entries();

+ 5 - 0
panda/src/collide/collisionHandlerPhysical.h

@@ -54,6 +54,9 @@ PUBLISHED:
 PUBLISHED:
 PUBLISHED:
   MAKE_PROPERTY2(center, has_center, get_center, set_center, clear_center);
   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:
 protected:
   bool _has_contact; // Are we in contact with anything?
   bool _has_contact; // Are we in contact with anything?
 
 
@@ -83,6 +86,8 @@ protected:
 
 
   NodePath _center;
   NodePath _center;
 
 
+  friend class Extension<CollisionHandlerPhysical>;
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;

+ 108 - 0
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<CollisionHandlerPhysical>::
+__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<CollisionHandlerPhysical>::
+__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

+ 38 - 0
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<CollisionHandlerPhysical> : public ExtensionBase<CollisionHandlerPhysical> {
+public:
+  PyObject *__reduce__(PyObject *self) const;
+  void __setstate__(PyObject *self, vector_uchar data, PyObject *nodepaths);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // COLLISIONHANDLERPHYSICAL_EXT_H

+ 21 - 0
panda/src/collide/collisionHandlerPusher.cxx

@@ -49,6 +49,27 @@ CollisionHandlerPusher::
 ~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
  * Called by the parent class after all collisions have been detected, this
  * manages the various collisions and moves around the nodes as necessary.
  * manages the various collisions and moves around the nodes as necessary.

+ 3 - 0
panda/src/collide/collisionHandlerPusher.h

@@ -34,6 +34,9 @@ PUBLISHED:
 PUBLISHED:
 PUBLISHED:
   MAKE_PROPERTY(horizontal, get_horizontal, set_horizontal);
   MAKE_PROPERTY(horizontal, get_horizontal, set_horizontal);
 
 
+  void write_datagram(Datagram &destination) const;
+  void read_datagram(DatagramIterator &source);
+
 protected:
 protected:
   virtual bool handle_entries();
   virtual bool handle_entries();
   virtual void apply_net_shove(
   virtual void apply_net_shove(

+ 3 - 0
panda/src/collide/collisionHandlerQueue.h

@@ -18,6 +18,7 @@
 
 
 #include "collisionHandler.h"
 #include "collisionHandler.h"
 #include "collisionEntry.h"
 #include "collisionEntry.h"
+#include "extension.h"
 
 
 /**
 /**
  * A special kind of CollisionHandler that does nothing except remember the
  * A special kind of CollisionHandler that does nothing except remember the
@@ -45,6 +46,8 @@ PUBLISHED:
   void output(std::ostream &out) const;
   void output(std::ostream &out) const;
   void write(std::ostream &out, int indent_level = 0) const;
   void write(std::ostream &out, int indent_level = 0) const;
 
 
+  EXTENSION(PyObject *__reduce__(PyObject *self) const);
+
 private:
 private:
   typedef pvector< PT(CollisionEntry) > Entries;
   typedef pvector< PT(CollisionEntry) > Entries;
   Entries _entries;
   Entries _entries;

+ 27 - 0
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<CollisionHandlerQueue>::
+__reduce__(PyObject *self) const {
+  // CollisionHandlerQueue has no interesting properties.
+  return Py_BuildValue("(O())", Py_TYPE(self));
+}
+
+#endif

+ 37 - 0
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<CollisionHandlerQueue> : public ExtensionBase<CollisionHandlerQueue> {
+public:
+  PyObject *__reduce__(PyObject *self) const;
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // COLLISIONHANDLERQUEUE_EXT_H

+ 4 - 0
panda/src/collide/collisionTraverser.h

@@ -24,6 +24,7 @@
 
 
 #include "pset.h"
 #include "pset.h"
 #include "register_type.h"
 #include "register_type.h"
+#include "extension.h"
 
 
 class CollisionNode;
 class CollisionNode;
 class CollisionRecorder;
 class CollisionRecorder;
@@ -79,6 +80,9 @@ PUBLISHED:
   void output(std::ostream &out) const;
   void output(std::ostream &out) const;
   void write(std::ostream &out, int indent_level) const;
   void write(std::ostream &out, int indent_level) const;
 
 
+  EXTENSION(PyObject *__getstate__() const);
+  EXTENSION(void __setstate__(PyObject *state));
+
 private:
 private:
   typedef pvector<CollisionLevelStateSingle> LevelStatesSingle;
   typedef pvector<CollisionLevelStateSingle> LevelStatesSingle;
   void prepare_colliders_single(LevelStatesSingle &level_states, const NodePath &root);
   void prepare_colliders_single(LevelStatesSingle &level_states, const NodePath &root);

+ 73 - 0
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<CollisionTraverser>::
+__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<CollisionTraverser>::
+__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

+ 38 - 0
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<CollisionTraverser> : public ExtensionBase<CollisionTraverser> {
+public:
+  PyObject *__getstate__() const;
+  void __setstate__(PyObject *state);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // COLLISIONTRAVERSER_EXT_H

+ 4 - 0
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 "collisionPolygon_ext.cxx"
+#include "collisionTraverser_ext.cxx"

+ 2 - 1
panda/src/display/graphicsStateGuardian_ext.cxx

@@ -25,10 +25,11 @@ static bool traverse_callback(TextureContext *tc, void *data) {
   PyObject *element =
   PyObject *element =
     DTool_CreatePyInstanceTyped(tex, Dtool_Texture,
     DTool_CreatePyInstanceTyped(tex, Dtool_Texture,
                                 true, false, tex->get_type_index());
                                 true, false, tex->get_type_index());
-  tex->ref();
+  tex.cheat() = nullptr;
 
 
   PyObject *list = (PyObject *) data;
   PyObject *list = (PyObject *) data;
   PyList_Append(list, element);
   PyList_Append(list, element);
+  Py_DECREF(element);
 
 
   return true;
   return true;
 }
 }

+ 3 - 0
panda/src/egg/eggAnimPreload.h

@@ -37,6 +37,9 @@ PUBLISHED:
   INLINE bool has_num_frames() const;
   INLINE bool has_num_frames() const;
   INLINE int get_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;
   virtual void write(std::ostream &out, int indent_level) const;
 
 
 private:
 private:

+ 2 - 0
panda/src/egg/eggComment.h

@@ -41,6 +41,8 @@ PUBLISHED:
 
 
   virtual void write(std::ostream &out, int indent_level) const;
   virtual void write(std::ostream &out, int indent_level) const;
 
 
+  EXTENSION(PyObject *__reduce__() const);
+
 private:
 private:
   std::string _comment;
   std::string _comment;
 
 

+ 32 - 0
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<EggComment>::
+__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

+ 37 - 0
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<EggComment> : public ExtensionBase<EggComment> {
+public:
+  PyObject *__reduce__() const;
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // EGGCOMMENT_EXT_H

+ 2 - 0
panda/src/egg/eggCoordinateSystem.h

@@ -36,6 +36,8 @@ PUBLISHED:
 
 
   virtual void write(std::ostream &out, int indent_level) const;
   virtual void write(std::ostream &out, int indent_level) const;
 
 
+  EXTENSION(PyObject *__reduce__() const);
+
 private:
 private:
   CoordinateSystem _value;
   CoordinateSystem _value;
 
 

+ 32 - 0
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<EggCoordinateSystem>::
+__reduce__() const {
+  extern struct Dtool_PyTypedObject Dtool_EggCoordinateSystem;
+
+  // We can't use the regular EggNode handling for EggCoordinateSystem, because
+  // the <CoordinateSystem> 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

+ 37 - 0
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<EggCoordinateSystem> : public ExtensionBase<EggCoordinateSystem> {
+public:
+  PyObject *__reduce__() const;
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // EGGCOORDINATESYSTEM_EXT_H

+ 2 - 0
panda/src/egg/eggNode.h

@@ -22,6 +22,7 @@
 #include "lmatrix.h"
 #include "lmatrix.h"
 #include "pointerTo.h"
 #include "pointerTo.h"
 #include "referenceCount.h"
 #include "referenceCount.h"
+#include "extension.h"
 
 
 class EggGroupNode;
 class EggGroupNode;
 class EggRenderMode;
 class EggRenderMode;
@@ -90,6 +91,7 @@ PUBLISHED:
   void test_under_integrity() const { }
   void test_under_integrity() const { }
 #endif  // _DEBUG
 #endif  // _DEBUG
 
 
+  EXTENSION(PyObject *__reduce__() const);
 
 
 protected:
 protected:
   enum UnderFlags {
   enum UnderFlags {

+ 93 - 0
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<EggNode>::
+__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

+ 42 - 0
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<EggNode> : public ExtensionBase<EggNode> {
+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

+ 10 - 0
panda/src/egg/lexer.cxx.prebuilt

@@ -971,6 +971,7 @@ extern "C" int eggyywrap(void);  // declared below.
 
 
 static int yyinput(void);        // declared by flex.
 static int yyinput(void);        // declared by flex.
 
 
+int eggyylex_destroy(void);
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 // Static variables
 // Static variables
@@ -1018,6 +1019,15 @@ egg_init_lexer(istream &in, const string &filename) {
   initial_token = START_EGG;
   initial_token = START_EGG;
 }
 }
 
 
+void
+egg_cleanup_lexer() {
+  // Reset the lexer state.
+  eggyylex_destroy();
+
+  input_p = nullptr;
+  egg_filename.clear();
+}
+
 void
 void
 egg_start_group_body() {
 egg_start_group_body() {
   /* Set the initial state to begin within a group_body context,
   /* Set the initial state to begin within a group_body context,

+ 10 - 0
panda/src/egg/lexer.lxx

@@ -26,6 +26,7 @@ extern "C" int eggyywrap(void);  // declared below.
 
 
 static int yyinput(void);        // declared by flex.
 static int yyinput(void);        // declared by flex.
 
 
+int eggyylex_destroy(void);
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 // Static variables
 // Static variables
@@ -73,6 +74,15 @@ egg_init_lexer(istream &in, const string &filename) {
   initial_token = START_EGG;
   initial_token = START_EGG;
 }
 }
 
 
+void
+egg_cleanup_lexer() {
+  // Reset the lexer state.
+  yylex_destroy();
+
+  input_p = nullptr;
+  egg_filename.clear();
+}
+
 void
 void
 egg_start_group_body() {
 egg_start_group_body() {
   /* Set the initial state to begin within a group_body context,
   /* Set the initial state to begin within a group_body context,

+ 1 - 0
panda/src/egg/lexerDefs.h

@@ -21,6 +21,7 @@
 #include <string>
 #include <string>
 
 
 void egg_init_lexer(std::istream &in, const std::string &filename);
 void egg_init_lexer(std::istream &in, const std::string &filename);
+void egg_cleanup_lexer();
 void egg_start_group_body();
 void egg_start_group_body();
 void egg_start_texture_body();
 void egg_start_texture_body();
 void egg_start_primitive_body();
 void egg_start_primitive_body();

+ 4 - 0
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"

+ 2 - 0
panda/src/egg/parser.cxx.prebuilt

@@ -223,6 +223,8 @@ egg_cleanup_parser() {
   textures.clear();
   textures.clear();
   materials.clear();
   materials.clear();
   groups.clear();
   groups.clear();
+
+  egg_cleanup_lexer();
 }
 }
 
 
 
 

+ 2 - 0
panda/src/egg/parser.yxx

@@ -153,6 +153,8 @@ egg_cleanup_parser() {
   textures.clear();
   textures.clear();
   materials.clear();
   materials.clear();
   groups.clear();
   groups.clear();
+
+  egg_cleanup_lexer();
 }
 }
 
 
 %}
 %}

+ 4 - 0
panda/src/express/pointerToArray.h

@@ -117,6 +117,8 @@ PUBLISHED:
   INLINE size_t count(const Element &) const;
   INLINE size_t count(const Element &) const;
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
+  EXTENSION(PyObject *__reduce__(PyObject *self) const);
+
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags));
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags));
   EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);
   EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);
 #endif
 #endif
@@ -273,6 +275,8 @@ PUBLISHED:
   INLINE size_t count(const Element &) const;
   INLINE size_t count(const Element &) const;
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
+  EXTENSION(PyObject *__reduce__(PyObject *self) const);
+
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
   EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);
   EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);
 #endif
 #endif

+ 6 - 0
panda/src/express/pointerToArrayBase.h

@@ -77,6 +77,12 @@ protected:
 
 
 PUBLISHED:
 PUBLISHED:
   INLINE ~PointerToArrayBase();
   INLINE ~PointerToArrayBase();
+
+#ifdef CPPPARSER
+  // These are implemented in PointerToVoid, but expose them here.
+  INLINE bool operator == (const PointerToArrayBase<Element> &other) const;
+  INLINE bool operator != (const PointerToArrayBase<Element> &other) const;
+#endif
 };
 };
 
 
 #include "pointerToArrayBase.I"
 #include "pointerToArrayBase.I"

+ 35 - 0
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);
   return PyBytes_FromStringAndSize((char *)(this->_this->p() + n), sizeof(Element) * count);
 }
 }
 
 
+/**
+ * Implements pickle support.
+ */
+template<class Element>
+INLINE PyObject *Extension<PointerToArray<Element> >::
+__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.
  * 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);
   return PyBytes_FromStringAndSize((char *)(this->_this->p() + n), sizeof(Element) * count);
 }
 }
 
 
+/**
+ * Implements pickle support.
+ */
+template<class Element>
+INLINE PyObject *Extension<ConstPointerToArray<Element> >::
+__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
  * This is used to implement the buffer protocol, in order to allow efficient
  * access to the array data through a Python multiview object.
  * access to the array data through a Python multiview object.

+ 4 - 0
panda/src/express/pointerToArray_ext.h

@@ -40,6 +40,8 @@ public:
   INLINE void set_data(PyObject *data);
   INLINE void set_data(PyObject *data);
   INLINE PyObject *get_subdata(size_t n, size_t count) 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);
   INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags);
   INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
   INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
 };
 };
@@ -75,6 +77,8 @@ public:
   INLINE PyObject *get_data() const;
   INLINE PyObject *get_data() const;
   INLINE PyObject *get_subdata(size_t n, size_t count) 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 int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
   INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
   INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
 };
 };

+ 2 - 0
panda/src/pgraph/loaderFileTypeRegistry.h

@@ -53,6 +53,8 @@ PUBLISHED:
 
 
   static LoaderFileTypeRegistry *get_global_ptr();
   static LoaderFileTypeRegistry *get_global_ptr();
 
 
+  EXTENSION(PyObject *__reduce__() const);
+
 private:
 private:
   void record_extension(const std::string &extension, LoaderFileType *type);
   void record_extension(const std::string &extension, LoaderFileType *type);
 
 

+ 10 - 0
panda/src/pgraph/loaderFileTypeRegistry_ext.cxx

@@ -18,6 +18,7 @@
 #include "pythonLoaderFileType.h"
 #include "pythonLoaderFileType.h"
 
 
 extern struct Dtool_PyTypedObject Dtool_LoaderFileType;
 extern struct Dtool_PyTypedObject Dtool_LoaderFileType;
+extern struct Dtool_PyTypedObject Dtool_LoaderFileTypeRegistry;
 
 
 /**
 /**
  * Registers a loader file type that is implemented in Python.
  * Registers a loader file type that is implemented in Python.
@@ -106,4 +107,13 @@ unregister_type(PyObject *type) {
   Py_XDECREF(save_func);
   Py_XDECREF(save_func);
 }
 }
 
 
+/**
+ * Implements pickle support.
+ */
+PyObject *Extension<LoaderFileTypeRegistry>::
+__reduce__() const {
+  PyObject *func = PyObject_GetAttrString((PyObject *)&Dtool_LoaderFileTypeRegistry, "get_global_ptr");
+  return Py_BuildValue("N()", func);
+}
+
 #endif
 #endif

+ 2 - 0
panda/src/pgraph/loaderFileTypeRegistry_ext.h

@@ -33,6 +33,8 @@ public:
   void register_deferred_type(PyObject *entry_point);
   void register_deferred_type(PyObject *entry_point);
 
 
   void unregister_type(PyObject *type);
   void unregister_type(PyObject *type);
+
+  PyObject *__reduce__() const;
 };
 };
 
 
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON

+ 108 - 0
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

+ 31 - 0
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)

+ 71 - 0
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()

+ 9 - 0
tests/pgraph/test_loader_types.py

@@ -3,6 +3,7 @@ import pytest
 import tempfile
 import tempfile
 import os
 import os
 from contextlib import contextmanager
 from contextlib import contextmanager
+import sys
 
 
 
 
 @pytest.fixture
 @pytest.fixture
@@ -218,3 +219,11 @@ def test_loader_ram_cache(test_filename):
         assert model1 == model2
         assert model1 == model2
 
 
         ModelPool.release_model(model2)
         ModelPool.release_model(model2)
+
+
[email protected](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

+ 10 - 0
tests/putil/test_bitmask.py

@@ -86,3 +86,13 @@ def test_bitmask_pickle():
     data = pickle.dumps(mask1, -1)
     data = pickle.dumps(mask1, -1)
     mask2 = pickle.loads(data)
     mask2 = pickle.loads(data)
     assert mask1 == mask2
     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

+ 14 - 0
tests/stdpy/test_pickle.py

@@ -12,6 +12,20 @@ def test_reduce_persist():
     assert tuple(parent2.children) == (child2,)
     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():
 def test_pickle_error():
     class ErroneousPickleable(object):
     class ErroneousPickleable(object):
         def __reduce__(self):
         def __reduce__(self):