collide: Support pickling for CollisionTraverser, HandlerEvent+Queue

Fixes #1090
This commit is contained in:
rdb 2020-12-31 16:57:34 +01:00
parent a5557bc38d
commit 8852c835fc
13 changed files with 410 additions and 0 deletions

View File

@ -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')

View File

@ -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);

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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<CollisionLevelStateSingle> LevelStatesSingle;
void prepare_colliders_single(LevelStatesSingle &level_states, const NodePath &root);

View File

@ -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

View File

@ -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

View File

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

View File

@ -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")

View File

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