collide: Add pickle support for most collision handlers

Also redo CollisionHandlerEvent pickling to use Datagram instead

Related to #1090
This commit is contained in:
rdb 2021-01-01 16:54:27 +01:00
parent a7042091be
commit f8ce339960
15 changed files with 429 additions and 94 deletions

View File

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

View File

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

View File

@ -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<CollisionHandlerEvent>::
__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<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
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

View File

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

View File

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

View File

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

View File

@ -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();
}
/**
*
*/

View File

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

View File

@ -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<CollisionHandlerPhysical>;
public:
static TypeHandle get_class_type() {
return _type_handle;

View File

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

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

View File

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

View File

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

View File

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

View File

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