removeNode() and its depth

Hi,
recently I found out that removeNode() only removes one particular node and all children are still there and only dispatched.

Consider the following code

import direct.directbase.DirectStart
from pandac.PandaModules import NodePath as NP


class C(object):
    def __init__(self):
        a = render.attachNewNode("a")
        aa = a.attachNewNode("aa")
        aaa = aa.attachNewNode("aaa")
        smiley = loader.loadModel("smiley")
        smiley.reparentTo(aaa)
        
        print "-"*10
        print "a = "+str(NP(a))
        print "aa = "+str(NP(aa))
        print "aaa = "+str(NP(aaa))
        print "smiley = "+str(smiley)
        print "-"*10
        render.ls()
        print "-"*10
        print "removing aa"
        aa.removeNode()
        print "-"*10
        print "a = "+str(NP(a))
        print "aa = "+str(NP(aa))
        print "aaa = "+str(NP(aaa))
        print "smiley = "+str(smiley)
        print "-"*10

c = C()
render.ls()
#run()

It recults in

----------
a = render/a
aa = render/a/aa
aaa = render/a/aa/aaa
smiley = render/a/aa/aaa/smiley.egg
----------
PandaNode render S:(CullFaceAttrib RescaleNormalAttrib)
  ModelNode camera
    Camera cam ( PerspectiveLens )
      PerspectiveLens fov = 39.3201 30
  PandaNode a
    PandaNode aa
      PandaNode aaa
        ModelRoot smiley.egg
          GeomNode  (1 geoms: S:(TextureAttrib))
----------
removing aa
----------
a = render/a
aa = **removed**
aaa = aa/aaa
smiley = aa/aaa/smiley.egg
----------
PandaNode render S:(CullFaceAttrib RescaleNormalAttrib)
  ModelNode camera
    Camera cam ( PerspectiveLens )
      PerspectiveLens fov = 39.3201 30
  PandaNode a

Now what you notice is that the loaded smiley is still there and thus won’t be cleaned up, still consuming memory. Correct me if I’m wrong here.

How about a method like removeNodes() (or removeNodeAndChildren() or removeNodeRecursively()) that goes through all the children of a node and removes those before removing the actual node?
I think this would be quite useful.
Imagine you have everything stored under a node called “foo”, which is direct ancestor of render. If you want to restart everythig or move on to another part of the game, you could simply call one function on “foo” and everything would be cleaned up implicitly.

removeNode() and detach() work the same way–they break the connection between the node and its parent. It doesn’t actually deallocate the node; it relies on reference counting to do this (this is the same as everything else in Panda and Python). When the last reference to the node is removed, then the node will be cleaned up.

But removeNode() goes one step further than detach(), it also invalidates the particular NodePath that you called it on (setting it to “removed”). This also doesn’t deallocate the node, but it does ensure that you don’t accidentally reuse it later. If you had a different NodePath handle to the same node, that NodePath would remain valid even if you called removeNode() on the first one.

The point is that the reason that all of your child nodes are still there is because you have a NodePath to them. If you were not holding a NodePath, then there would be no more references and they would be deleted correctly.

But as with any reference-counting language, there’s not really an easy way to tell when nodes are deleted. They’re deleted when the last reference goes away. That means if you still have a reference to the node to ask “are you deleted?” the answer is always going to be “no.”

The bottom line: just call removeNode() and don’t worry about it. All of the children will be deleted.

David

That makes sense. It’s only hard to wrap my head around the idea.

Given that there is no such thing as real removing of nodes, is it safe and fast enough to do (np.getTop() == render) each frame? I want to know if a node is still in the scene graph in some tasks in order to implicitly let them end themselves.
Imagine the code snippet above had a task or interval moving the smiley around. Removing “aa” wouldn’t make the task raise any errors. Instead it would still keep a reference and transform a pseudo-existent node.
Could this be worked around keeping not a nodepath, but a reference to the node and wrapping a NodePath around it each frame or maybe only keeping a proxy nodepath, which points to the parent of any actual node?

I think you could solve this problem with one of the proposals you mention, but it would be better to explicitly end any tasks you want to end, or to at least set an explicit “end this task” flag that the task can check for, rather than relying on it to check the state of a node.

This is one of Guido’s principles of Python: explicit is better than implicit. :slight_smile:

David