About CollisionRay, CollisionHandler and custom functions

Hello there.

I wonder is there is abetter solution for what I am doing.

If I have a CollisionTraverser and CollisionRay, so I declare a “MouseOver” Task to get the CollisionHandlerQueue node being collided reading getEntry(0).getIntoNodePath(). This works with mouseover/mouseout events, no issue at all.

So, because I need a different function/method with parameters to be called for each NodePath and for each event, I made something like

When “Mouse Over” happens in that “NodePath”, execute “Function()” with this “var_values”

I have solved this in the following ways, none seem to be the proper one:

  1. Writing an entry into a dictionary when node is created like {"/this/nodepath": Function()} using globals() to store the function itself and another entry to store/pass the parameters if any. This one is the current solution I am using.

  2. Using PythonTags, and store the NodePath and the parameters, making the same globals()[“Function”] call. The thing is it slower the performance in a very noticeable way: about 0.1ms to read the tag variable content and it lags.

I tried also to store (via dict or PythonTags) a class with the Function() method instead of globals() calls, but something says me there would be a better way.

Am I missing something?

Have you looked at using a CollisionHandlerEvent, instead? That should allow you to set up the callbacks that you want without creating a task and checking a queue, I imagine. Further, it allows you to specify event-patterns that may help with your issue of determine which function to call.

See this part of the manual for details, and this page for an example.

Another thought might be to create a Python class that stores and manages your colliders, that implements your function-callbacks, and that keeps a reference to itself in a Python-tag in the collider.

This might allow you to simply access this single Python-tag in your callback, and then call the implemented function.

And since a function in a class may be overridden by a sub-class, it provides a means of creating distinct functions for distinct elements, despite the fact that only one function is called!

If you do take this approach, however, do be careful to clean up such Python-tags when you’re done with the objects in question! Since the objects keep references to the colliders, and the colliders keep references to the objects (in the Python tags), a circular reference is formed. This can interfere with garbage collection, I believe.

However, it’s solved simply by clearing the Python-tag, which then breaks the circular reference!

Something like this:

class MyClass():
    def __init__(self, collider):
        self.collider = collider
        self.collider.setPythonTag("owner", self)
        # More initialisation here...

    def myFunction(self):
        # Function logic here...

    def cleanup(self):
        # Remember to call this!

        self.collider.clearPythonTag("owner")

Then, when a collider is detected:

myClassObj = intoNP.getPythonTag("owner")
myClassObj.myFunction()

Thanks for the reply.

When I used PythonTags, experience some performance lag. But this was when recently started to learn P3D, so maybe I make some ugly implementation ;-). Will try again. Thanks for your advice, Thaumaturge.

1 Like

Without more knowledge of how you were using your Python-tags, it’s hard to say why you might have experienced that lag. (Lots of objects? A great many calls per frame? Objects that are somehow problematic? Something else besides…?) All that I can say is that I’ve made use of them in my own code, and thus far have seen little trouble from doing so.

I have done some more research, and find a way to storage more than strings into a NodePath, via subclassing without using tags:

class NodeHack(NodePath):
    def __init__(self, name="NodeHack"):
        NodePath.__init__(self, name)
        root = PandaNode(name)
        self.assign(NodePath(root))
        self.params = {}

    def set(self, clave, valor):
        self.params[clave] = valor

    def get(self, clave):
        #if not clave in self.params: return False
        return self.params[clave]

I have tried storing commons values, like strings or lists, but also works with objects and so on:

        root = render.attachNewNode("root")

        model = NodeHack("net")
        m = loader.loadModel("box")
        m.instanceTo(model)
        model.reparentTo(root)

        model.set("a", root)
        model.set("b", 1.2)
        ret_a = model.get("a")
        ret_b = model.get("b")
        print(ret_a, ret_b)


And I got:

render/root 1.2

:slight_smile:

I will continue studying, to verify if there is any complain about garbage collecting, but as far as I see it even works referencing other objects methods in there.

The problem with this approach is, if you get the NodePath back through a C++ method (such as a collision result), it will be the base NodePath, not your derived class.

I finally figured out how to solve it, as the documentation says, as I read in all others discussions and @Thaumaturge reiterated me.

With the @rdb answer I now get Python/C++ relationship in getting the object.

Many thanks, you hackers. What a wonderful delight is learning :slight_smile:

One more question: why (as here and in other discussions says) I need to clear the Python Tag? As far as I get it to work, no cross-references problem raises, but it appears needed to be done.

1 Like

The main reason that I’m aware of is that when you have a situation in which an object contains a NodePath, and that NodePath contains a Python-tag referencing the object, a circular reference is created.

Now, in and of itself this isn’t a problem!

Until it comes time to remove the object. At this point, you can remove your references to both the object and the NodePath–but since they refer to each other, they each have at least one other thing referencing them.

As a result, Python’s automatic garbage collection may not realise that they can be cleaned up, and so may leave them in memory–even though they’re not being used. Which is to say, it may cause a memory leak.

Clearing the Python-tag then removes one of the references that compose the circle, and thus allows the NodePath and the object to be garbage collected.

2 Likes