Browse Source

collide: Support pickling for CollisionTraverser, HandlerEvent+Queue

Fixes #1090
rdb 5 years ago
parent
commit
8852c835fc

+ 2 - 0
makepanda/makepanda.py

@@ -4363,6 +4363,7 @@ if (not RUNTIME):
   IGATEFILES=GetDirectoryContents('panda/src/collide', ["*.h", "*_composite*.cxx"])
   IGATEFILES=GetDirectoryContents('panda/src/collide', ["*.h", "*_composite*.cxx"])
   TargetAdd('libp3collide.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3collide.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3collide.in', opts=['IMOD:panda3d.core', 'ILIB:libp3collide', 'SRCDIR:panda/src/collide'])
   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/
 # DIRECTORY: panda/src/parametrics/
@@ -4606,6 +4607,7 @@ if (not RUNTIME):
   if PkgSkip("FREETYPE")==0:
   if PkgSkip("FREETYPE")==0:
     PyTargetAdd('core.pyd', input="libp3pnmtext_igate.obj")
     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='p3pipeline_pythonThread.obj')
   PyTargetAdd('core.pyd', input='p3putil_ext_composite.obj')
   PyTargetAdd('core.pyd', input='p3putil_ext_composite.obj')
   PyTargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj')
   PyTargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj')

+ 4 - 0
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
@@ -67,6 +68,9 @@ PUBLISHED:
   void clear();
   void clear();
   void flush();
   void flush();
 
 
+  EXTENSION(PyObject *__getstate__() const);
+  EXTENSION(void __setstate__(PyObject *state));
+
 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);

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

+ 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 *__getstate__() const;
+  void __setstate__(PyObject *state);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // COLLISIONHANDLEREVENT_EXT_H

+ 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;
@@ -81,6 +82,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);

+ 82 - 0
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<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);
+#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<CollisionTraverser>::
+__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

+ 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

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

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

+ 18 - 0
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")

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