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 3bdad89812..59173a42fc 100644 --- a/panda/src/collide/collisionHandlerEvent.h +++ b/panda/src/collide/collisionHandlerEvent.h @@ -68,8 +68,12 @@ PUBLISHED: void clear(); 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: void throw_event_for(const vector_string &patterns, CollisionEntry *entry); diff --git a/panda/src/collide/collisionHandlerEvent_ext.cxx b/panda/src/collide/collisionHandlerEvent_ext.cxx index 1315f0fc03..242a20da9f 100644 --- a/panda/src/collide/collisionHandlerEvent_ext.cxx +++ b/panda/src/collide/collisionHandlerEvent_ext.cxx @@ -12,6 +12,9 @@ */ #include "collisionHandlerEvent_ext.h" +#include "collisionHandlerFloor.h" +#include "collisionHandlerGravity.h" +#include "collisionHandlerPusher.h" #ifdef HAVE_PYTHON @@ -19,52 +22,35 @@ * Implements pickling behavior. */ PyObject *Extension:: -__getstate__() const { - PyObject *state = PyTuple_New(3); - if (state == nullptr) { +__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. +#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); - size_t num_patterns; - PyObject *patterns; - - 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); + const char *data = (const char *)dg.get_data(); + Py_ssize_t size = dg.get_length(); #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 - PyTuple_SET_ITEM(patterns, i, PyString_FromStringAndSize(pattern.data(), pattern.size())); + return Py_BuildValue("O()s#", Py_TYPE(self), data, size); #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 - } - PyTuple_SET_ITEM(state, 1, patterns); - - 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); -#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 - } - PyTuple_SET_ITEM(state, 2, patterns); - - return state; } /** @@ -72,52 +58,25 @@ __getstate__() const { * this CollisionHandlerEvent object. */ void Extension:: -__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 - const char *data = PyUnicode_AsUTF8AndSize(pattern, &len); + PyObject *method_name = PyUnicode_FromString("read_datagram"); #else - char *data; - PyString_AsStringAndSize(pattern, &data, &len); + PyObject *method_name = PyString_FromString("read_datagram"); #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 diff --git a/panda/src/collide/collisionHandlerEvent_ext.h b/panda/src/collide/collisionHandlerEvent_ext.h index cd9a0b8024..22a3a5e680 100644 --- a/panda/src/collide/collisionHandlerEvent_ext.h +++ b/panda/src/collide/collisionHandlerEvent_ext.h @@ -29,8 +29,8 @@ template<> class Extension : public ExtensionBase { public: - PyObject *__getstate__() const; - void __setstate__(PyObject *state); + PyObject *__reduce__(PyObject *self) const; + void __setstate__(PyObject *self, vector_uchar data); }; #endif // HAVE_PYTHON 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..ceedf7f98e --- /dev/null +++ b/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:: +__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:: +__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 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/p3collide_ext_composite.cxx b/panda/src/collide/p3collide_ext_composite.cxx index 2c3175044b..271fb1a7eb 100644 --- a/panda/src/collide/p3collide_ext_composite.cxx +++ b/panda/src/collide/p3collide_ext_composite.cxx @@ -1,3 +1,4 @@ #include "collisionHandlerEvent_ext.cxx" +#include "collisionHandlerPhysical_ext.cxx" #include "collisionHandlerQueue_ext.cxx" #include "collisionTraverser_ext.cxx" diff --git a/tests/collide/test_collision_handlers.py b/tests/collide/test_collision_handlers.py index 56f59cf17a..87d602ef3b 100644 --- a/tests/collide/test_collision_handlers.py +++ b/tests/collide/test_collision_handlers.py @@ -1,4 +1,5 @@ from direct.stdpy.pickle import dumps, loads +from panda3d.core import NodePath, CollisionNode def test_collision_handler_event_pickle(): @@ -7,12 +8,101 @@ def test_collision_handler_event_pickle(): handler = CollisionHandlerEvent() handler.add_in_pattern("abcdefg") handler.add_in_pattern("test") - handler.add_out_pattern("out pattern") 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.out_patterns) == ("out 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