Browse Source

collide: Add pickle support for most collision handlers

Also redo CollisionHandlerEvent pickling to use Datagram instead

Related to #1090
rdb 5 years ago
parent
commit
f8ce339960

+ 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.
  */
  */

+ 6 - 2
panda/src/collide/collisionHandlerEvent.h

@@ -68,8 +68,12 @@ PUBLISHED:
   void clear();
   void clear();
   void flush();
   void flush();
 
 
-  EXTENSION(PyObject *__getstate__() const);
-  EXTENSION(void __setstate__(PyObject *state));
+  // 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);

+ 35 - 76
panda/src/collide/collisionHandlerEvent_ext.cxx

@@ -12,6 +12,9 @@
  */
  */
 
 
 #include "collisionHandlerEvent_ext.h"
 #include "collisionHandlerEvent_ext.h"
+#include "collisionHandlerFloor.h"
+#include "collisionHandlerGravity.h"
+#include "collisionHandlerPusher.h"
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
 
 
@@ -19,52 +22,35 @@
  * Implements pickling behavior.
  * Implements pickling behavior.
  */
  */
 PyObject *Extension<CollisionHandlerEvent>::
 PyObject *Extension<CollisionHandlerEvent>::
-__getstate__() const {
-  PyObject *state = PyTuple_New(3);
-  if (state == nullptr) {
-    return nullptr;
-  }
-
-  size_t num_patterns;
-  PyObject *patterns;
+__reduce__(PyObject *self) const {
+  extern struct Dtool_PyTypedObject Dtool_Datagram;
 
 
-  num_patterns = _this->get_num_in_patterns();
-  patterns = PyTuple_New(num_patterns);
-  for (size_t i = 0; i < num_patterns; ++i) {
-    std::string pattern = _this->get_in_pattern(i);
+  // Call the write_datagram method via Python, since it's not a virtual method
+  // on the C++ end.
 #if PY_MAJOR_VERSION >= 3
 #if PY_MAJOR_VERSION >= 3
-    PyTuple_SET_ITEM(patterns, i, PyUnicode_FromStringAndSize(pattern.data(), pattern.size()));
+  PyObject *method_name = PyUnicode_FromString("write_datagram");
 #else
 #else
-    PyTuple_SET_ITEM(patterns, i, PyString_FromStringAndSize(pattern.data(), pattern.size()));
+  PyObject *method_name = PyString_FromString("write_datagram");
 #endif
 #endif
-  }
-  PyTuple_SET_ITEM(state, 0, patterns);
 
 
-  num_patterns = _this->get_num_again_patterns();
-  patterns = PyTuple_New(num_patterns);
-  for (size_t i = 0; i < num_patterns; ++i) {
-    std::string pattern = _this->get_again_pattern(i);
-#if PY_MAJOR_VERSION >= 3
-    PyTuple_SET_ITEM(patterns, i, PyUnicode_FromStringAndSize(pattern.data(), pattern.size()));
-#else
-    PyTuple_SET_ITEM(patterns, i, PyString_FromStringAndSize(pattern.data(), pattern.size()));
-#endif
+  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;
   }
   }
-  PyTuple_SET_ITEM(state, 1, patterns);
+  Py_DECREF(retval);
 
 
-  num_patterns = _this->get_num_out_patterns();
-  patterns = PyTuple_New(num_patterns);
-  for (size_t i = 0; i < num_patterns; ++i) {
-    std::string pattern = _this->get_out_pattern(i);
+  const char *data = (const char *)dg.get_data();
+  Py_ssize_t size = dg.get_length();
 #if PY_MAJOR_VERSION >= 3
 #if PY_MAJOR_VERSION >= 3
-    PyTuple_SET_ITEM(patterns, i, PyUnicode_FromStringAndSize(pattern.data(), pattern.size()));
+  return Py_BuildValue("O()y#", Py_TYPE(self), data, size);
 #else
 #else
-    PyTuple_SET_ITEM(patterns, i, PyString_FromStringAndSize(pattern.data(), pattern.size()));
+  return Py_BuildValue("O()s#", Py_TYPE(self), data, size);
 #endif
 #endif
-  }
-  PyTuple_SET_ITEM(state, 2, patterns);
-
-  return state;
 }
 }
 
 
 /**
 /**
@@ -72,52 +58,25 @@ __getstate__() const {
  * this CollisionHandlerEvent object.
  * this CollisionHandlerEvent object.
  */
  */
 void Extension<CollisionHandlerEvent>::
 void Extension<CollisionHandlerEvent>::
-__setstate__(PyObject *state) {
-  nassertv(Py_SIZE(state) >= 3);
+__setstate__(PyObject *self, vector_uchar data) {
+  extern struct Dtool_PyTypedObject Dtool_DatagramIterator;
 
 
-  PyObject *patterns;
-
-  _this->clear_in_patterns();
-  patterns = PyTuple_GET_ITEM(state, 0);
-  for (size_t i = 0; i < Py_SIZE(patterns); ++i) {
-    PyObject *pattern = PyTuple_GET_ITEM(patterns, i);
-    Py_ssize_t len = 0;
+  // Call the read_datagram method via Python, since it's not a virtual method
+  // on the C++ end.
 #if PY_MAJOR_VERSION >= 3
 #if PY_MAJOR_VERSION >= 3
-    const char *data = PyUnicode_AsUTF8AndSize(pattern, &len);
+  PyObject *method_name = PyUnicode_FromString("read_datagram");
 #else
 #else
-    char *data;
-    PyString_AsStringAndSize(pattern, &data, &len);
+  PyObject *method_name = PyString_FromString("read_datagram");
 #endif
 #endif
-    _this->add_in_pattern(std::string(data, len));
-  }
 
 
-  _this->clear_again_patterns();
-  patterns = PyTuple_GET_ITEM(state, 1);
-  for (size_t i = 0; i < Py_SIZE(patterns); ++i) {
-    PyObject *pattern = PyTuple_GET_ITEM(patterns, i);
-    Py_ssize_t len = 0;
-#if PY_MAJOR_VERSION >= 3
-    const char *data = PyUnicode_AsUTF8AndSize(pattern, &len);
-#else
-    char *data;
-    PyString_AsStringAndSize(pattern, &data, &len);
-#endif
-    _this->add_again_pattern(std::string(data, len));
-  }
+  Datagram dg(std::move(data));
+  DatagramIterator scan(dg);
+  PyObject *source = DTool_CreatePyInstance(&scan, Dtool_DatagramIterator, false, false);
 
 
-  _this->clear_out_patterns();
-  patterns = PyTuple_GET_ITEM(state, 2);
-  for (size_t i = 0; i < Py_SIZE(patterns); ++i) {
-    PyObject *pattern = PyTuple_GET_ITEM(patterns, i);
-    Py_ssize_t len = 0;
-#if PY_MAJOR_VERSION >= 3
-    const char *data = PyUnicode_AsUTF8AndSize(pattern, &len);
-#else
-    char *data;
-    PyString_AsStringAndSize(pattern, &data, &len);
-#endif
-    _this->add_out_pattern(std::string(data, len));
-  }
+  PyObject *retval = PyObject_CallMethodOneArg(self, method_name, source);
+  Py_DECREF(method_name);
+  Py_DECREF(source);
+  Py_XDECREF(retval);
 }
 }
 
 
 #endif
 #endif

+ 2 - 2
panda/src/collide/collisionHandlerEvent_ext.h

@@ -29,8 +29,8 @@
 template<>
 template<>
 class Extension<CollisionHandlerEvent> : public ExtensionBase<CollisionHandlerEvent> {
 class Extension<CollisionHandlerEvent> : public ExtensionBase<CollisionHandlerEvent> {
 public:
 public:
-  PyObject *__getstate__() const;
-  void __setstate__(PyObject *state);
+  PyObject *__reduce__(PyObject *self) const;
+  void __setstate__(PyObject *self, vector_uchar data);
 };
 };
 
 
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON

+ 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;

+ 120 - 0
panda/src/collide/collisionHandlerPhysical_ext.cxx

@@ -0,0 +1,120 @@
+/**
+ * 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.
+#if PY_MAJOR_VERSION >= 3
+  PyObject *method_name = PyUnicode_FromString("write_datagram");
+#else
+  PyObject *method_name = PyString_FromString("write_datagram");
+#endif
+
+  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();
+#if PY_MAJOR_VERSION >= 3
+  return Py_BuildValue("O()(y#N)", Py_TYPE(self), data, size, nodepaths);
+#else
+  return Py_BuildValue("O()(s#N)", Py_TYPE(self), data, size, nodepaths);
+#endif
+}
+
+/**
+ * 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.
+#if PY_MAJOR_VERSION >= 3
+  PyObject *method_name = PyUnicode_FromString("read_datagram");
+#else
+  PyObject *method_name = PyString_FromString("read_datagram");
+#endif
+
+  {
+    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(

+ 1 - 0
panda/src/collide/p3collide_ext_composite.cxx

@@ -1,3 +1,4 @@
 #include "collisionHandlerEvent_ext.cxx"
 #include "collisionHandlerEvent_ext.cxx"
+#include "collisionHandlerPhysical_ext.cxx"
 #include "collisionHandlerQueue_ext.cxx"
 #include "collisionHandlerQueue_ext.cxx"
 #include "collisionTraverser_ext.cxx"
 #include "collisionTraverser_ext.cxx"

+ 92 - 2
tests/collide/test_collision_handlers.py

@@ -1,4 +1,5 @@
 from direct.stdpy.pickle import dumps, loads
 from direct.stdpy.pickle import dumps, loads
+from panda3d.core import NodePath, CollisionNode
 
 
 
 
 def test_collision_handler_event_pickle():
 def test_collision_handler_event_pickle():
@@ -7,12 +8,101 @@ def test_collision_handler_event_pickle():
     handler = CollisionHandlerEvent()
     handler = CollisionHandlerEvent()
     handler.add_in_pattern("abcdefg")
     handler.add_in_pattern("abcdefg")
     handler.add_in_pattern("test")
     handler.add_in_pattern("test")
-    handler.add_out_pattern("out pattern")
     handler.add_again_pattern("again pattern")
     handler.add_again_pattern("again pattern")
     handler.add_again_pattern("another again pattern")
     handler.add_again_pattern("another again pattern")
+    handler.add_out_pattern("out pattern")
 
 
     handler = loads(dumps(handler, -1))
     handler = loads(dumps(handler, -1))
 
 
     assert tuple(handler.in_patterns) == ("abcdefg", "test")
     assert tuple(handler.in_patterns) == ("abcdefg", "test")
-    assert tuple(handler.out_patterns) == ("out pattern",)
     assert tuple(handler.again_patterns) == ("again pattern", "another again pattern")
     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