collide: Add custom owner field to CollisionNode

This stores a weakref to a custom Python object, useful for being able to look up the game object corresponding to a particular collision node in an event without the downsides of Python tags

Fixes #1703
This commit is contained in:
rdb
2024-12-14 12:48:24 +01:00
parent 6f33bbecea
commit 5e05049725
12 changed files with 324 additions and 2 deletions

View File

@@ -74,6 +74,8 @@ set(P3COLLIDE_IGATEEXT
collisionHandlerPhysical_ext.h
collisionHandlerQueue_ext.cxx
collisionHandlerQueue_ext.h
collisionNode_ext.cxx
collisionNode_ext.h
collisionPolygon_ext.cxx
collisionPolygon_ext.h
collisionTraverser_ext.cxx

View File

@@ -166,3 +166,24 @@ INLINE CollideMask CollisionNode::
get_default_collide_mask() {
return default_collision_node_collide_mask;
}
/**
* Returns the custom pointer set via set_owner().
*/
INLINE void *CollisionNode::
get_owner() const {
return _owner;
}
/**
* Sets a custom pointer, together with an optional callback that will be
* called when the node is deleted.
*
* The owner or the callback will not be copied along with the CollisionNode.
*/
INLINE void CollisionNode::
set_owner(void *owner, OwnerCallback *callback) {
clear_owner();
_owner = owner;
_owner_callback = callback;
}

View File

@@ -40,7 +40,9 @@ CollisionNode::
CollisionNode(const std::string &name) :
PandaNode(name),
_from_collide_mask(get_default_collide_mask()),
_collider_sort(0)
_collider_sort(0),
_owner(nullptr),
_owner_callback(nullptr)
{
set_cull_callback();
set_renderable();
@@ -60,7 +62,9 @@ CollisionNode(const CollisionNode &copy) :
PandaNode(copy),
_from_collide_mask(copy._from_collide_mask),
_collider_sort(copy._collider_sort),
_solids(copy._solids)
_solids(copy._solids),
_owner(nullptr),
_owner_callback(nullptr)
{
}
@@ -69,6 +73,10 @@ CollisionNode(const CollisionNode &copy) :
*/
CollisionNode::
~CollisionNode() {
if (_owner_callback != nullptr) {
_owner_callback(_owner);
_owner_callback = nullptr;
}
}
/**
@@ -251,6 +259,18 @@ set_from_collide_mask(CollideMask mask) {
_from_collide_mask = mask;
}
/**
* Removes the owner that was previously set using set_owner().
*/
void CollisionNode::
clear_owner() {
if (_owner_callback != nullptr) {
_owner_callback(_owner);
}
_owner = nullptr;
_owner_callback = nullptr;
}
/**
* Called when needed to recompute the node's _internal_bound object. Nodes
* that contain anything of substance should redefine this to do the right

View File

@@ -77,6 +77,22 @@ PUBLISHED:
INLINE static CollideMask get_default_collide_mask();
MAKE_PROPERTY(default_collide_mask, get_default_collide_mask);
public:
typedef void (OwnerCallback)(void *);
INLINE void *get_owner() const;
#ifndef CPPPARSER
INLINE void set_owner(void *owner, OwnerCallback *callback = nullptr);
void clear_owner();
#endif
EXTENSION(PyObject *get_owner() const);
EXTENSION(void set_owner(PyObject *owner));
PUBLISHED:
MAKE_PROPERTY(owner, get_owner, set_owner);
protected:
virtual void compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
int &internal_vertices,
@@ -94,6 +110,9 @@ private:
typedef pvector< COWPT(CollisionSolid) > Solids;
Solids _solids;
void *_owner = nullptr;
OwnerCallback *_owner_callback = nullptr;
friend class CollisionTraverser;
public:

View File

@@ -0,0 +1,62 @@
/**
* 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 collisionNode_ext.cxx
* @author rdb
* @date 2024-12-12
*/
#include "collisionNode_ext.h"
#ifdef HAVE_PYTHON
#include "collisionNode.h"
/**
* Returns the object previously set via set_owner(). If the object has been
* destroyed, returns None.
*/
PyObject *Extension<CollisionNode>::
get_owner() const {
PyObject *owner = (PyObject *)_this->get_owner();
#if PY_VERSION_HEX >= 0x030D0000 // 3.13
PyObject *strong_ref;
int result = 0;
if (owner != nullptr) {
PyWeakref_GetRef(owner, &strong_ref);
}
if (result > 0) {
return strong_ref;
}
else if (result == 0) {
return Py_NewRef(Py_None);
}
else {
return nullptr;
}
#else
return Py_NewRef(owner != nullptr ? PyWeakref_GetObject(owner) : Py_None);
#endif
}
/**
* Stores a weak reference to the given object on the CollisionNode, for later
* use in collision events and handlers.
*/
void Extension<CollisionNode>::
set_owner(PyObject *owner) {
if (owner != Py_None) {
PyObject *ref = PyWeakref_NewRef(owner, nullptr);
_this->set_owner(ref, [](void *obj) { Py_DECREF((PyObject *)obj); });
} else {
_this->clear_owner();
}
}
#endif

View File

@@ -0,0 +1,40 @@
/**
* 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 collisionNode_ext.h
* @author rdb
* @date 2024-12-12
*/
#ifndef COLLISIONNODE_EXT_H
#define COLLISIONNODE_EXT_H
#include "pandabase.h"
#ifdef HAVE_PYTHON
#include "extension.h"
#include "collisionNode.h"
#include "py_panda.h"
/**
* This class defines the extension methods for CollisionNode, which are called
* instead of any C++ methods with the same prototype.
*
* @since 1.11.0
*/
template<>
class Extension<CollisionNode> : public ExtensionBase<CollisionNode> {
public:
PyObject *get_owner() const;
void set_owner(PyObject *owner);
};
#endif // HAVE_PYTHON
#endif

View File

@@ -1,5 +1,6 @@
#include "collisionHandlerEvent_ext.cxx"
#include "collisionHandlerPhysical_ext.cxx"
#include "collisionHandlerQueue_ext.cxx"
#include "collisionNode_ext.cxx"
#include "collisionPolygon_ext.cxx"
#include "collisionTraverser_ext.cxx"

View File

@@ -884,6 +884,8 @@ PUBLISHED:
INLINE void set_collide_mask(CollideMask new_mask, CollideMask bits_to_change = CollideMask::all_on(),
TypeHandle node_type = TypeHandle::none());
EXTENSION(void set_collide_owner(PyObject *owner));
// Comparison methods
INLINE bool operator == (const NodePath &other) const;
INLINE bool operator != (const NodePath &other) const;

View File

@@ -15,6 +15,7 @@
#include "typedWritable_ext.h"
#include "shaderInput_ext.h"
#include "shaderAttrib.h"
#include "collisionNode.h"
#ifdef HAVE_PYTHON
@@ -327,4 +328,62 @@ get_tight_bounds(const NodePath &other) const {
}
}
/**
* Recursively assigns a weak reference to the given owner object to all
* collision nodes at this level and below.
*
* You may pass in None to clear all owners below this level.
*
* Note that there is no corresponding get_collide_owner(), since there may be
* multiple nodes below this level with different owners.
*/
void Extension<NodePath>::
set_collide_owner(PyObject *owner) {
if (owner != Py_None) {
PyObject *ref = PyWeakref_NewRef(owner, nullptr);
if (ref != nullptr) {
r_set_collide_owner(_this->node(), ref);
Py_DECREF(ref);
}
} else {
r_clear_collide_owner(_this->node());
}
}
/**
* Recursive implementation of set_collide_owner. weakref must be a weak ref
* object.
*/
void Extension<NodePath>::
r_set_collide_owner(PandaNode *node, PyObject *weakref) {
if (node->is_collision_node()) {
CollisionNode *cnode = (CollisionNode *)node;
cnode->set_owner(Py_NewRef(weakref),
[](void *obj) { Py_DECREF((PyObject *)obj); });
}
PandaNode::Children cr = node->get_children();
int num_children = cr.get_num_children();
for (int i = 0; i < num_children; i++) {
r_set_collide_owner(cr.get_child(i), weakref);
}
}
/**
* Recursive implementation of set_collide_owner(None).
*/
void Extension<NodePath>::
r_clear_collide_owner(PandaNode *node) {
if (node->is_collision_node()) {
CollisionNode *cnode = (CollisionNode *)node;
cnode->clear_owner();
}
PandaNode::Children cr = node->get_children();
int num_children = cr.get_num_children();
for (int i = 0; i < num_children; i++) {
r_clear_collide_owner(cr.get_child(i));
}
}
#endif // HAVE_PYTHON

View File

@@ -54,6 +54,12 @@ public:
void set_shader_inputs(PyObject *args, PyObject *kwargs);
PyObject *get_tight_bounds(const NodePath &other = NodePath()) const;
void set_collide_owner(PyObject *owner);
private:
static void r_set_collide_owner(PandaNode *node, PyObject *weakref);
static void r_clear_collide_owner(PandaNode *node);
};
BEGIN_PUBLISH

View File

@@ -0,0 +1,46 @@
from panda3d.core import *
import sys
class CustomObject:
pass
def test_collision_node_owner():
owner = CustomObject()
initial_rc = sys.getrefcount(owner)
node = CollisionNode("node")
assert node.owner is None
node.owner = owner
assert sys.getrefcount(owner) == initial_rc
assert node.owner is owner
node.owner = owner
assert sys.getrefcount(owner) == initial_rc
assert node.owner is owner
node.owner = None
assert sys.getrefcount(owner) == initial_rc
assert node.owner is None
del node
assert sys.getrefcount(owner) == initial_rc
# Assign owner and then delete node
node = CollisionNode("node")
assert sys.getrefcount(owner) == initial_rc
node.owner = owner
assert sys.getrefcount(owner) == initial_rc
del node
assert sys.getrefcount(owner) == initial_rc
# Delete owner and see what happens to the node
node = CollisionNode("node")
assert sys.getrefcount(owner) == initial_rc
node.owner = owner
assert sys.getrefcount(owner) == initial_rc
del owner
assert node.owner is None

View File

@@ -312,3 +312,47 @@ def test_nodepath_replace_texture_none():
assert path2.get_texture() == tex1
path1.replace_texture(tex1, None)
assert path2.get_texture() is None
def test_nodepath_set_collide_owner():
from panda3d.core import NodePath, CollisionNode
class CustomOwner:
pass
owner1 = CustomOwner()
owner2 = CustomOwner()
owner3 = CustomOwner()
root = NodePath("root")
model1 = root.attach_new_node("model1")
collider1 = model1.attach_new_node(CollisionNode("collider1"))
collider2 = model1.attach_new_node(CollisionNode("collider2"))
model2 = root.attach_new_node("model2")
collider3 = model2.attach_new_node(CollisionNode("collider3"))
root.set_collide_owner(owner1)
assert collider1.node().owner is owner1
assert collider2.node().owner is owner1
assert collider3.node().owner is owner1
model1.set_collide_owner(None)
assert collider1.node().owner is None
assert collider2.node().owner is None
assert collider3.node().owner is owner1
collider2.set_collide_owner(owner2)
assert collider1.node().owner is None
assert collider2.node().owner is owner2
assert collider3.node().owner is owner1
del owner1
assert collider1.node().owner is None
assert collider2.node().owner is owner2
assert collider3.node().owner is None
root.set_collide_owner(owner3)
model2.set_collide_owner(owner2)
assert collider1.node().owner is owner3
assert collider2.node().owner is owner3
assert collider3.node().owner is owner2