"Removing models at once" tip (for beginners)

Hello! Here’s a very simple tip: how to easily “destroy” a level.

A sample program that loads 2 models:

(...)
   self.man = loader.loadModel("models/man")
   self.man.setScale(2)
   self.man.setPos(0,0,0)
   self.man.reparentTo(self.render)
   
   self.dog = loader.loadModel("models/dog")
   self.dog.setScale(.2)
   self.dog.setPos(0,4,0)
   self.dog.reparentTo(self.render)
(...)
run()

Then, to remove both the dog and the man:

 self.man.destroy()
 self.dog.destroy()

In this example, we have only 2 things to remove, but it could be 30 or 40! There’s nothing worse then writing 40 lines of code like that. But… There’s a very simple way to avoid breaking your fingers:

In the top of code, write:

from panda3d.core import NodePath
def ModelSet(name="set1"):
 return NodePath(name)

So, in the middle:

#when defining main class
class ABC(ShowBase):
   def __init__(self):
     (...)
     self.set1 = Level("set1")
     self.set1.reparentTo(self.render)
     (...)
      #now loading the man and the dog:
      self.man = loader.loadModel("models/man")
      self.man.setScale(2)
      self.man.setPos(0,0,0)
      self.man.reparentTo([b]self.set1[/b])
   
      self.dog = loader.loadModel("models/dog")
      self.dog.setScale(.2)
      self.dog.setPos(0,4,0)
      self.dog.reparentTo([b]self.set1[/b])
      #they're reparented to the level!
      #so, to remove them:
      self.set1.removeNode() #I need only 1 line to remove 2 (or 40) models at once! (PS: it might also work with actors, but I haven't tried it on yet.)

That’s all!
Good luck :smiley:

EDIT:

  1. In the code, “Level” is wrong. It would be “ModelSet”, sorry.
  2. People are complaining that removeNode won’t free memory. New code to solve it:
def destroy(self):
   for m in self.set1.getChildren():
       m.destroy()
   self.set1.removeNode()

Hi! Nice tip :slight_smile: Just a few queries:

I assume “Level(…” is meant to be “ModelSet(” in your last code block?
Also, do we use “destroy” or “removeNode” for removing node paths?

The dog model and man model won’t be destroyed, they will still be in memory.

You need to do self.dog = None and self.man = None before they can be garbage collected.

It is probably a good idea to wrap these members of your level into a class, so that you can simply get rid of the entire class when you have no more need for the level, and all of its members will automatically go out of scope too.

Hm I did not know that you have to do

x = None

to really get rid of things.

In a related note: is it possible to use Python’s WeakKeyDictionary (or is it WeakValueDictionary?) for that?

Let’s say you hold references to NodePaths in there, and doing removeNode() then would delete them without the None thing?

Pseudocode:

from weakref import WeakKeyDictionary # WeakValueDictionary?

model1 = loader.loadModel("path/model")
model2 = ...
model3 = ...

my_wkd = {"1":model1, "2":model2, "3":model3}

def delete_scene():
	for model in my_wkd.itervaluerefs():
		model.removeNode()

In the past I had trouble using WKD or WVD. Usually something about Python unable to assign values/keys to strings or ints.

Still I find the idea promising to use WKD/WVD to hold references to objects to start garbage collection with only one removeNode() command.

No, it is when the original is deleted that the weak ref disappears, not the other way around.
Perhaps this?

my_wkd = WeakValueDictionary()

my_wkd["unique_name"] = render.attachNewNode( loader.loadModel("path/model") )

Now the scenegraph is holding the only true reference. Removal from the scenegraph should auto-remove the dictionary entry.

my_wkd["unique_name"].removeNode()

# test
assert "unique_name" not in my_wkd

I think. I can’t test this out right now but that seems right.

You could just as easily use a conventional dictionary, group nodes together into lists, or as members of a class, etc. As long as ALL access is done the same way (ie. though the dictionary lookup), without assigning any outside references, then it’s just one extra line to remove the entry after you remove the node. You see with the above weak dictionary there is still nothing stopping you from doing…

model1 = my_wkd["unique_name"]

…at which point you’d have gone back to with the original problem. model1 is a true reference and will keep the object alive even if the dictionary entry is explicitely removed.

This concept is called “ownership semantics” and is an important part of encapsulation in object-oriented programming. It’s about defining what part of your program “owns” the object(s) in question. The owner shouldn’t let any other part of the program get it’s hands on a true reference; all outside access goes through the owner and the owner handles the final destruction. Without adhering to this principle it can become very hard to keep track of where and what all of your living references are.

Dictionary keys must be immutable. I don’t know if that’s what you’re referring to.

Well, in my project I do that, using the Singleton-via-module-import technique:

# shared.py
from Env import Environment

env = Environment()

# game.py
import shared

shared.env.do_stuff()

How would one delete this Singleton?
In the Environment class I could go and delete all attributes by hand. Problem is that there are a lot of attributes going around as you said bundeling them in a class seems like the right idea.

What about “shared.env = None” ?

You can also use the del function instead of setting things to None. For example del(myvar)

shared.env.dict.clear()
del(shared.env)

That makes no difference. Setting an instance to None is the cleanest way to reduce the reference count of a class instance and all its members. Clearing the class’ dictionary explicitly won’t make a difference at all.
There is no added benefit to using “del”, it just makes it more confusing. (because “del” sounds like you’re deleting the instance, whereas in reality you’re just reducing its reference count).