"Removing models at once" tip (for beginners)

Return to Code Snippets

"Removing models at once" tip (for beginners)

Postby loblão » Wed Jul 11, 2012 12:01 pm

Hello! Here's a very simple tip: how to easily "destroy" a level.

A sample program that loads 2 models:

Code: Select all
(...)
   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:

Code: Select all
 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:

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


So, in the middle:

Code: Select all
#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 :D

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:
Code: Select all
def destroy(self):
   for m in self.set1.getChildren():
       m.destroy()
   self.set1.removeNode()
Last edited by loblão on Thu Sep 27, 2012 3:32 pm, edited 1 time in total.
loblão
 
Posts: 4
Joined: Sat Jun 02, 2012 11:26 am
Location: Brasil

Postby kurohyou » Wed Jul 11, 2012 10:08 pm

Hi! Nice tip :) 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?
kurohyou
 
Posts: 209
Joined: Tue Jun 29, 2010 9:59 pm

Postby rdb » Sat Jul 14, 2012 1:21 am

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.
rdb
 
Posts: 8565
Joined: Mon Dec 04, 2006 5:58 am
Location: Netherlands

Postby Bradamante » Thu Jul 19, 2012 11:31 am

Hm I did not know that you have to do

Code: Select all
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:

Code: Select all
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.
iMac (2009), Mac OS X.8.1 - MacBookPro (2007), Mac OS X.8.2
@ YouTube
User avatar
Bradamante
 
Posts: 303
Joined: Tue Nov 25, 2008 10:58 am
Location: Leipzig, Germany

Postby Hollower » Thu Jul 19, 2012 6:10 pm

Bradamante wrote:Hm I did not know that you have to do

Code: Select all
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:

Code: Select all
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()



No, it is when the original is deleted that the weak ref disappears, not the other way around.
Perhaps this?
Code: Select all
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.

Code: Select all
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...
Code: Select all
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.

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

Dictionary keys must be immutable. I don't know if that's what you're referring to.
Hollower
 
Posts: 64
Joined: Sat Dec 11, 2010 1:18 am

Postby Bradamante » Sun Aug 19, 2012 8:39 am

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

Code: Select all
# 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.

rdb wrote: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.
iMac (2009), Mac OS X.8.1 - MacBookPro (2007), Mac OS X.8.2
@ YouTube
User avatar
Bradamante
 
Posts: 303
Joined: Tue Nov 25, 2008 10:58 am
Location: Leipzig, Germany

Postby rdb » Sun Aug 19, 2012 11:02 am

What about "shared.env = None" ?
rdb
 
Posts: 8565
Joined: Mon Dec 04, 2006 5:58 am
Location: Netherlands

Postby teedee » Sun Aug 19, 2012 2:51 pm

You can also use the del function instead of setting things to None. For example del(myvar)
teedee
 
Posts: 782
Joined: Tue May 12, 2009 11:33 pm
Location: Kepler-22b

Postby ynjh_jo » Sun Aug 19, 2012 9:50 pm

shared.env.__dict__.clear()
del(shared.env)
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby rdb » Mon Aug 20, 2012 2:18 am

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).
rdb
 
Posts: 8565
Joined: Mon Dec 04, 2006 5:58 am
Location: Netherlands


Return to Code Snippets

Who is online

Users browsing this forum: No registered users and 1 guest