From 8852c835fc6b362ce6e331c1e4d19fa9949930c6 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 31 Dec 2020 16:57:34 +0100 Subject: [PATCH] collide: Support pickling for CollisionTraverser, HandlerEvent+Queue Fixes #1090 --- makepanda/makepanda.py | 2 + panda/src/collide/collisionHandlerEvent.h | 4 + .../src/collide/collisionHandlerEvent_ext.cxx | 123 ++++++++++++++++++ panda/src/collide/collisionHandlerEvent_ext.h | 38 ++++++ panda/src/collide/collisionHandlerQueue.h | 3 + .../src/collide/collisionHandlerQueue_ext.cxx | 27 ++++ panda/src/collide/collisionHandlerQueue_ext.h | 37 ++++++ panda/src/collide/collisionTraverser.h | 4 + panda/src/collide/collisionTraverser_ext.cxx | 82 ++++++++++++ panda/src/collide/collisionTraverser_ext.h | 38 ++++++ panda/src/collide/p3collide_ext_composite.cxx | 3 + tests/collide/test_collision_handlers.py | 18 +++ tests/collide/test_collision_traverser.py | 31 +++++ 13 files changed, 410 insertions(+) create mode 100644 panda/src/collide/collisionHandlerEvent_ext.cxx create mode 100644 panda/src/collide/collisionHandlerEvent_ext.h create mode 100644 panda/src/collide/collisionHandlerQueue_ext.cxx create mode 100644 panda/src/collide/collisionHandlerQueue_ext.h create mode 100644 panda/src/collide/collisionTraverser_ext.cxx create mode 100644 panda/src/collide/collisionTraverser_ext.h create mode 100644 panda/src/collide/p3collide_ext_composite.cxx create mode 100644 tests/collide/test_collision_handlers.py create mode 100644 tests/collide/test_collision_traverser.py diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index fa7df48550..e493900a5e 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -4363,6 +4363,7 @@ if (not RUNTIME): IGATEFILES=GetDirectoryContents('panda/src/collide', ["*.h", "*_composite*.cxx"]) TargetAdd('libp3collide.in', opts=OPTS, input=IGATEFILES) TargetAdd('libp3collide.in', opts=['IMOD:panda3d.core', 'ILIB:libp3collide', 'SRCDIR:panda/src/collide']) + PyTargetAdd('p3collide_ext_composite.obj', opts=OPTS, input='p3collide_ext_composite.cxx') # # DIRECTORY: panda/src/parametrics/ @@ -4606,6 +4607,7 @@ if (not RUNTIME): if PkgSkip("FREETYPE")==0: PyTargetAdd('core.pyd', input="libp3pnmtext_igate.obj") + PyTargetAdd('core.pyd', input='p3collide_ext_composite.obj') PyTargetAdd('core.pyd', input='p3pipeline_pythonThread.obj') PyTargetAdd('core.pyd', input='p3putil_ext_composite.obj') PyTargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj') diff --git a/panda/src/collide/collisionHandlerEvent.h b/panda/src/collide/collisionHandlerEvent.h index 05f15f025a..3bdad89812 100644 --- a/panda/src/collide/collisionHandlerEvent.h +++ b/panda/src/collide/collisionHandlerEvent.h @@ -22,6 +22,7 @@ #include "vector_string.h" #include "pointerTo.h" +#include "extension.h" /** * A specialized kind of CollisionHandler that throws an event for each @@ -67,6 +68,9 @@ PUBLISHED: void clear(); void flush(); + EXTENSION(PyObject *__getstate__() const); + EXTENSION(void __setstate__(PyObject *state)); + protected: void throw_event_for(const vector_string &patterns, CollisionEntry *entry); void throw_event_pattern(const std::string &pattern, CollisionEntry *entry); diff --git a/panda/src/collide/collisionHandlerEvent_ext.cxx b/panda/src/collide/collisionHandlerEvent_ext.cxx new file mode 100644 index 0000000000..1315f0fc03 --- /dev/null +++ b/panda/src/collide/collisionHandlerEvent_ext.cxx @@ -0,0 +1,123 @@ +/** + * 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" + +#ifdef HAVE_PYTHON + +/** + * Implements pickling behavior. + */ +PyObject *Extension:: +__getstate__() const { + PyObject *state = PyTuple_New(3); + if (state == nullptr) { + return nullptr; + } + + 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); +#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, 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; +} + +/** + * Takes the value returned by __getstate__ and uses it to freshly initialize + * this CollisionHandlerEvent object. + */ +void Extension:: +__setstate__(PyObject *state) { + nassertv(Py_SIZE(state) >= 3); + + 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; +#if PY_MAJOR_VERSION >= 3 + const char *data = PyUnicode_AsUTF8AndSize(pattern, &len); +#else + char *data; + PyString_AsStringAndSize(pattern, &data, &len); +#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)); + } + + _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)); + } +} + +#endif diff --git a/panda/src/collide/collisionHandlerEvent_ext.h b/panda/src/collide/collisionHandlerEvent_ext.h new file mode 100644 index 0000000000..cd9a0b8024 --- /dev/null +++ b/panda/src/collide/collisionHandlerEvent_ext.h @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file collisionHandlerEvent_ext.h + * @author rdb + * @date 2020-12-31 + */ + +#ifndef COLLISIONHANDLEREVENT_EXT_H +#define COLLISIONHANDLEREVENT_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "collisionHandlerEvent.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for CollisionHandlerEvent, which are + * called instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__getstate__() const; + void __setstate__(PyObject *state); +}; + +#endif // HAVE_PYTHON + +#endif // COLLISIONHANDLEREVENT_EXT_H diff --git a/panda/src/collide/collisionHandlerQueue.h b/panda/src/collide/collisionHandlerQueue.h index cfef93c540..48e48600b7 100644 --- a/panda/src/collide/collisionHandlerQueue.h +++ b/panda/src/collide/collisionHandlerQueue.h @@ -18,6 +18,7 @@ #include "collisionHandler.h" #include "collisionEntry.h" +#include "extension.h" /** * A special kind of CollisionHandler that does nothing except remember the @@ -45,6 +46,8 @@ PUBLISHED: void output(std::ostream &out) const; void write(std::ostream &out, int indent_level = 0) const; + EXTENSION(PyObject *__reduce__(PyObject *self) const); + private: typedef pvector< PT(CollisionEntry) > Entries; Entries _entries; diff --git a/panda/src/collide/collisionHandlerQueue_ext.cxx b/panda/src/collide/collisionHandlerQueue_ext.cxx new file mode 100644 index 0000000000..b439d78214 --- /dev/null +++ b/panda/src/collide/collisionHandlerQueue_ext.cxx @@ -0,0 +1,27 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file collisionHandlerQueue_ext.cxx + * @author rdb + * @date 2020-12-31 + */ + +#include "collisionHandlerQueue_ext.h" + +#ifdef HAVE_PYTHON + +/** + * Implements pickling behavior. + */ +PyObject *Extension:: +__reduce__(PyObject *self) const { + // CollisionHandlerQueue has no interesting properties. + return Py_BuildValue("(O())", Py_TYPE(self)); +} + +#endif diff --git a/panda/src/collide/collisionHandlerQueue_ext.h b/panda/src/collide/collisionHandlerQueue_ext.h new file mode 100644 index 0000000000..23b2768e61 --- /dev/null +++ b/panda/src/collide/collisionHandlerQueue_ext.h @@ -0,0 +1,37 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file collisionHandler_ext.h + * @author rdb + * @date 2020-12-31 + */ + +#ifndef COLLISIONHANDLERQUEUE_EXT_H +#define COLLISIONHANDLERQUEUE_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "collisionHandlerQueue.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for CollisionHandlerQueue, which are + * called instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__reduce__(PyObject *self) const; +}; + +#endif // HAVE_PYTHON + +#endif // COLLISIONHANDLERQUEUE_EXT_H diff --git a/panda/src/collide/collisionTraverser.h b/panda/src/collide/collisionTraverser.h index fc039e3f7d..77ef1f20f1 100644 --- a/panda/src/collide/collisionTraverser.h +++ b/panda/src/collide/collisionTraverser.h @@ -24,6 +24,7 @@ #include "pset.h" #include "register_type.h" +#include "extension.h" class CollisionNode; class CollisionRecorder; @@ -81,6 +82,9 @@ PUBLISHED: void output(std::ostream &out) const; void write(std::ostream &out, int indent_level) const; + EXTENSION(PyObject *__getstate__() const); + EXTENSION(void __setstate__(PyObject *state)); + private: typedef pvector LevelStatesSingle; void prepare_colliders_single(LevelStatesSingle &level_states, const NodePath &root); diff --git a/panda/src/collide/collisionTraverser_ext.cxx b/panda/src/collide/collisionTraverser_ext.cxx new file mode 100644 index 0000000000..5dd6c171f6 --- /dev/null +++ b/panda/src/collide/collisionTraverser_ext.cxx @@ -0,0 +1,82 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file collisionTraverser_ext.cxx + * @author rdb + * @date 2020-12-31 + */ + +#include "collisionTraverser_ext.h" + +#ifdef HAVE_PYTHON + +/** + * Implements pickling behavior. + */ +PyObject *Extension:: +__getstate__() const { + extern struct Dtool_PyTypedObject Dtool_CollisionHandler; + extern struct Dtool_PyTypedObject Dtool_CollisionTraverser; + extern struct Dtool_PyTypedObject Dtool_NodePath; + + const std::string &name = _this->get_name(); + size_t num_colliders = _this->get_num_colliders(); + + PyObject *state = PyTuple_New(num_colliders * 2 + 3); +#if PY_MAJOR_VERSION >= 3 + PyTuple_SET_ITEM(state, 0, PyUnicode_FromStringAndSize(name.data(), name.size())); +#else + PyTuple_SET_ITEM(state, 0, PyString_FromStringAndSize(name.data(), name.size())); +#endif + PyTuple_SET_ITEM(state, 1, PyBool_FromLong(_this->get_respect_prev_transform())); + PyTuple_SET_ITEM(state, 2, PyLong_FromLong((long)num_colliders)); + + for (size_t i = 0; i < num_colliders; ++i) { + NodePath *collider = new NodePath(_this->get_collider(i)); + PyTuple_SET_ITEM(state, i * 2 + 3, + DTool_CreatePyInstance((void *)collider, Dtool_NodePath, true, false)); + + PT(CollisionHandler) handler = _this->get_handler(*collider); + handler->ref(); + PyTuple_SET_ITEM(state, i * 2 + 4, + DTool_CreatePyInstanceTyped((void *)handler.p(), Dtool_CollisionHandler, true, false, handler->get_type_index())); + handler.cheat() = nullptr; + } + + return state; +} + +/** + * Takes the value returned by __getstate__ and uses it to freshly initialize + * this CollisionTraverser object. + */ +void Extension:: +__setstate__(PyObject *state) { + _this->clear_colliders(); + + Py_ssize_t len = 0; +#if PY_MAJOR_VERSION >= 3 + const char *data = PyUnicode_AsUTF8AndSize(PyTuple_GET_ITEM(state, 0), &len); +#else + char *data; + PyString_AsStringAndSize(PyTuple_GET_ITEM(state, 0), &data, &len); +#endif + _this->set_name(std::string(data, len)); + + _this->set_respect_prev_transform(PyTuple_GET_ITEM(state, 1) != Py_False); + size_t num_colliders = (ssize_t)PyLong_AsLong(PyTuple_GET_ITEM(state, 2)); + + for (size_t i = 0; i < num_colliders; ++i) { + NodePath *collider = (NodePath *)DtoolInstance_VOID_PTR(PyTuple_GET_ITEM(state, i * 2 + 3)); + CollisionHandler *handler = (CollisionHandler *)DtoolInstance_VOID_PTR(PyTuple_GET_ITEM(state, i * 2 + 4)); + + _this->add_collider(*collider, handler); + } +} + +#endif diff --git a/panda/src/collide/collisionTraverser_ext.h b/panda/src/collide/collisionTraverser_ext.h new file mode 100644 index 0000000000..445e4bcf5d --- /dev/null +++ b/panda/src/collide/collisionTraverser_ext.h @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file collisionTraverser_ext.h + * @author rdb + * @date 2020-12-31 + */ + +#ifndef COLLISIONTRAVERSER_EXT_H +#define COLLISIONTRAVERSER_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "collisionTraverser.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for CollisionTraverser, which are + * called instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *__getstate__() const; + void __setstate__(PyObject *state); +}; + +#endif // HAVE_PYTHON + +#endif // COLLISIONTRAVERSER_EXT_H diff --git a/panda/src/collide/p3collide_ext_composite.cxx b/panda/src/collide/p3collide_ext_composite.cxx new file mode 100644 index 0000000000..2c3175044b --- /dev/null +++ b/panda/src/collide/p3collide_ext_composite.cxx @@ -0,0 +1,3 @@ +#include "collisionHandlerEvent_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 new file mode 100644 index 0000000000..56f59cf17a --- /dev/null +++ b/tests/collide/test_collision_handlers.py @@ -0,0 +1,18 @@ +from direct.stdpy.pickle import dumps, loads + + +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_out_pattern("out pattern") + handler.add_again_pattern("again pattern") + handler.add_again_pattern("another again 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") diff --git a/tests/collide/test_collision_traverser.py b/tests/collide/test_collision_traverser.py new file mode 100644 index 0000000000..fd1f93ee56 --- /dev/null +++ b/tests/collide/test_collision_traverser.py @@ -0,0 +1,31 @@ +from panda3d.core import CollisionTraverser, CollisionHandlerQueue +from panda3d.core import NodePath, CollisionNode + + + +def test_collision_traverser_pickle(): + from direct.stdpy.pickle import dumps, loads + + handler = CollisionHandlerQueue() + + collider1 = NodePath(CollisionNode("collider1")) + collider2 = NodePath(CollisionNode("collider2")) + + trav = CollisionTraverser("test123") + trav.respect_prev_transform = True + trav.add_collider(collider1, handler) + trav.add_collider(collider2, handler) + + trav = loads(dumps(trav, -1)) + assert trav.respect_prev_transform is True + + assert trav.name == "test123" + assert trav.get_num_colliders() == 2 + collider1 = trav.get_collider(0) + collider2 = trav.get_collider(1) + assert collider1.name == "collider1" + assert collider2.name == "collider2" + + # Two colliders must still be the same object; this only works with our own + # version of the pickle module, in direct.stdpy.pickle. + assert trav.get_handler(collider1) == trav.get_handler(collider2)