Destroying and re-creating ShowBase

I have an application that needs to run Panda3D, then close it, then re-open it.

There seems to be some issues with calling ShowBase.destroy() and then re-creating it. Here’s an example showing the error:

>>> from direct.showbase.ShowBase import ShowBase
>>> p3dApp = ShowBase()
Known pipe types:
  glxGraphicsPipe
(all display modules loaded.)
>>> taskMgr.step()
>>> p3dApp.destroy()
>>> p3dApp = ShowBase()
Known pipe types:
  glxGraphicsPipe
(all display modules loaded.)
:task(warning): Creating implicit AsyncTaskChain default for AsyncTaskManager TaskManager
>>> taskMgr.step()
Traceback (most recent call last):
  File "/usr/share/panda3d/direct/showbase/EventManager.py", line 61, in eventLoopTask
    self.doEvents()
  File "/usr/share/panda3d/direct/showbase/EventManager.py", line 55, in doEvents
    processFunc(self.eventQueue.dequeueEvent())
  File "/usr/share/panda3d/direct/showbase/EventManager.py", line 122, in processEvent
    messenger.send(eventName, paramList)
  File "/usr/share/panda3d/direct/showbase/Messenger.py", line 325, in send
    self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
  File "/usr/share/panda3d/direct/showbase/Messenger.py", line 410, in __dispatch
    method (*(extraArgs + sentArgs))
  File "/usr/share/panda3d/direct/showbase/ShowBase.py", line 2379, in windowEvent
    if win == self.win:
AttributeError: ShowBase instance has no attribute 'win'
:task(error): Exception occurred in PythonTask eventManager
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/share/panda3d/direct/task/Task.py", line 454, in step
    self.mgr.poll()
  File "/usr/share/panda3d/direct/showbase/EventManager.py", line 61, in eventLoopTask
    self.doEvents()
  File "/usr/share/panda3d/direct/showbase/EventManager.py", line 55, in doEvents
    processFunc(self.eventQueue.dequeueEvent())
  File "/usr/share/panda3d/direct/showbase/EventManager.py", line 122, in processEvent
    messenger.send(eventName, paramList)
  File "/usr/share/panda3d/direct/showbase/Messenger.py", line 325, in send
    self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
  File "/usr/share/panda3d/direct/showbase/Messenger.py", line 410, in __dispatch
    method (*(extraArgs + sentArgs))
  File "/usr/share/panda3d/direct/showbase/ShowBase.py", line 2379, in windowEvent
    if win == self.win:
AttributeError: ShowBase instance has no attribute 'win'
>>>

Anyone know how to properly shut down Panda3D and then re-open it without quitting python? Thanks.

I ended up just forking to get this to work. Something like this:

def myfunc():
    forkid = os.fork()
    if forkid == 0:
        panda3dstuff()
        sys.exit(0)
    else:
        (childid, status) = os.waitpid(forkid, 0)
        if status != 0:
            print "failed"
        else:
            print "success"

os.fork() isn’t available on Windows.
Not sure if that’s a problem for you, so I thought I’d just let you know.

Good point. Anyone have any idea how to fix this?

The ShowBase object is not meant to be destructed and recreated. It’s intended to be an atomic object that exists for the lifetime of your Panda app.

The intended design is that your app cleans up whatever your app created. If you follow that design, then there is no need to destroy ShowBase, because when your app cleans itself up, there won’t be anything left other than ShowBase.

That being said, forking a process to get around this design is not a bad solution. To avoid using os.fork(), use the subprocess module instead.

David

Even when I remove all of my scene nodes, I still notice some memory that doesn’t get cleaned up.

I want to have a long-lived application that can open up a ShowBase window, close it, and open it up again later. Is there any way to close the window?

The fork/subprocess works, but it’s inconvenient to deal with interprocess communication. It would be really nice if I could at least destroy/hide the window. I’ve tried TaskManager.stop, TaskManager.destroy, GraphicsWindow.removeAllDisplayRegions, GraphicsWindow.closeIme, and a few others without any success.

Don’t confuse the window with the ShowBase class. By default, the ShowBase class creates a window when it is instantiated, but this is just a default behavior that can be changed; and there is not a one-to-one association between windows and the ShowBase instance.

You can create any number of windows and destroy them again with base.graphicsEngine.removeWindow(). This does not destroy the ShowBase instance, with the exception that if you the window you destroy happens to be base.win, then the default behavior is to exit the program–but the easy way to avoid this is to reassign base.win to None before you destroy the window.

To open another window again, use base.openMainWindow() or just base.openWindow().

Note that opening and closing a window may not appear to free all of the memory back down to the level at initial application start. This is normal, and generally corresponds to caches filling up. However, some have reported an actual memory leak with regards to closing windows; we haven’t had a chance to investigate this further.

You can also minimize the window by requesting the minimized property in win.requestProperties().

David

I’m not quite sure what you are after, but maybe this helps you in a way:
[userExit without kill the python interpreter)

The method there basically creates a subprocess and waits for its return code, which you can control. Depending on the RC you can actually do whatever you want, restarting the program included.

On the other hand, if you only want to hide the appliocation window and “pause” panda, that works fine from within the app itself. David gave you some very good points for that already.

Thanks, I didn’t realize that ShowBase was separate from windowing. This at least lets me close the window now.

Here’s essentially what I’m doing in a loop:

if 'base' not in __builtin__.__dict__: 
   ShowBase()
base.openMainWindow()

#run my app

myBaseNode.removeNode()
base.graphicsEngine.removeAllWindows()

I’m still seeing a lot of memory leaking though, to the point where I still have to use a subprocess or else my program runs out of memory very quickly. Anything else I should be doing to release memory?

I turned on memory tracking, and I use MemoryUsagePointers to take a look at memory usage. The geometries that I am programmatically adding to the scene each time are not being cleaned up.

I printed out the top 10 classes that are instantiated, and these are increasing each time:

('CopyOnWriteObject', 3192)
('BoundingSphere', 2234)
('ShaderInput', 1960)
('TransformState', 743)
('NodeVertexTransform', 693)
('GeomNode', 693)
('Geom', 686)
('GeomTriangles', 686)
('GeomVertexArrayData', 665)
('GeomVertexData', 525)

I also notice these messages are being printed:

and the number of these increase each time I create and destroy the scene/window.

I’m fairly certain that I’m not keeping a python reference to any of the geometries I add to the scene. I also set geom-cache-size to 0. Any idea what I’m doing wrong?

Hmm, I’m not sure. Is this something you can post a simple example to reproduce?

David

In my program, I’m seeing all of the above mentioned types increase, but in this sample below, I’m only seeing CopyOnWriteObject and NodeReferenceCount increase. My program is much larger, but it doesn’t have any global variables, so I’m not sure why it’s different here. You can still see memory going up indefinitely though. Does this help?

from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor
from panda3d.core import *
import operator
import time
import pprint

ShowBase()

def setup():
    base.disableMouse()
    base.camera.setPos(50, 50, 200)
    base.camera.lookAt(50, 50, 0)
    base.openMainWindow()

def createScene():
    for i in range(10):
        for j in range(10):
            a = Actor("models/panda")
            a.reparentTo(render)
            a.setPos(i*10,j*10,0)

def step():
    base.taskMgr.step()
    base.taskMgr.step()
    time.sleep(1)

def destroy():
    for child in render.getChildren():
        if child != base.camera:
            child.removeNode()
    base.graphicsEngine.removeAllWindows()

last_top = {}
def printTopMemory():
    global last_top
    
    mup = MemoryUsagePointers() 
    MemoryUsage.getPointers(mup) 

    type_count = {}
    for i in range(mup.getNumPointers()): 
        obj = mup.getPythonPointer(i) 
        count = type_count.get(obj.__class__, 0)
        count += 1
        type_count[obj.__class__] = count

    type_count = sorted(type_count.iteritems(), key=operator.itemgetter(1), reverse=True)
    for type, count in type_count:
        if last_top.get(type, 0) != count:
            print 'type %s changed to %d' % (type, count)
    last_top = dict(type_count)

while True:
    setup()
    createScene()
    step()
    destroy()
    printTopMemory()

Just to follow up on this for people who find this thread on Google: as of Panda3D 1.9.0 it is possible to cleanly shut down ShowBase as long as destroy() is called. All references should get cleaned up properly now.

1 Like

I have done the panda3d script below and I got an error message when showbase is launched the second time when the “escape” key is used.

  File "C:\Panda3D-1.9.0-x64\direct\showbase\Loader.py", line 170, in loadModel
    raise IOError, message
IOError: Could not load model file(s): ['smiley']
from pandac.PandaModules import WindowProperties
from direct.showbase.ShowBase import ShowBase

class Panda3dApp(object):
    def __init__(self):

        self.oShowbase = ShowBase()
        wp = WindowProperties()

        wp.setSize(500,500)
        base.openDefaultWindow(props=wp)

        self.loadSmiley()

        self.oShowbase.accept('escape', self.mReload)

        self.oShowbase.run()

    def mReload(self):

        self.oShowbase.destroy()

        self.oShowbase = ShowBase()

        wp = WindowProperties()
        wp.setSize(500,500)
        base.openDefaultWindow(props=wp)

        self.loadSmiley()

        self.oShowbase.run()

    def loadSmiley(self):
        s = loader.loadModel("smiley")
        s.reparentTo(render)
        s.setY(5)

if __name__ == "__main__":
    app = Panda3dApp()

Ah, I see the problem. ShowBase.destroy() calls VirtualFileSystem.getGlobalPtr().umountAll(), which should not be done. I’ll remove that call.

However, you’ll get a different error if you try this: you cannot call run() inside a task, since this would make the task manager run inside the task manager. You’ll have to refactor your code such that this isn’t the case.

I spotted you’re using range. This is just be example code,
but it hints that you might be using it all the time.

Instead of range, use xrange.
See here: stackoverflow.com/questions/9493 … python-2-x

That most likely won’t help, but saves you time and ram.