Pickling Problem

I created simple class named TerrainModel that is not derived from any other class. This class has 2 NodePath instances in it that I constructed from GeomNode instances derived from a PNMImage instance. Note … no Audio is involved.

Now, when I go to pickle a class instance I get the following:

What gives here?

Thx,
Paul

Pickle, unlike the faster marshal module, can do a lot of fancy things like resolving circular or shared references. But it can’t do magic. What pickle saves is basically what obj.getstate returns.

Pickle works fine with pure python classes. But if you have C/C++ objects somewhere then you might run into problems. For example pickle can’t store open files, sockets or windows.

AudioManager is a C++ object. All the members defined in audioManager.h are not visible to Python, so I would expect problems.

enn0x

Btw. cPickle is almost the same as Pickle, but cPickle is 100x faster.

You can use python’s copy_reg module to register the AudioManager as pickable object yourself. Google for it.

I guess I don’t understand why I need to do this since none of my class properties involves any audio.

Neither do I, but you could try to work around:

The plan is to let Python pickle anything that is not a problem, and provide information on how to restore those attributes of your class that are a problem for pickle.

Implement the two methods getstate and setstate for your class (if getstate/setstate are not implemented then Python pickles the objects dict ).

getstate should return the instance’s dict, minus whatever causes problem. I suspect then two NodePath instances to be the problem), plus some information on how to restore the missing attributes (e.g. the path-string you used to create the PNMImage).

setstate could re-construct the NodePath then, the same way you created them initially.

I didn’t test this approach with Panda3D, but it did work for me some years ago when I had a similar problem.

enn0x

Thats what I am trying to avoid by doing this. When I create a GeomNode from a 64x64 image it isn’t an issue but when I try to do it with large images (512+) it takes forever. I wanted to create the object once, serialize it to disk, and then just reload it when I needed it instead of re-creating it (which is the long arduous process).

Ok, understand.

One way would be to save a bam file containing the NodePath data in the same directory as you would store the pickled data. So you don’t have to re-build it but can load it directly. Filenames could be created with id( obj ).

From my understanding the C++ data contained in the Geom’s has to be stored somewhere, or the object can’t be restored in exactly the same state that it has been before pickling. And bam write/load is fast. Probably faster than pickle/cPickle/marshal.

But perhaps I am wrong and it is not the NodePath instances that cause the problem, but something else. Using getstate would allow you to find out what attributes (aka dict keys) cause the problem.

enn0x

I suspected that creating a BAM would be the solution but I wanted to avoid it because, as I understand it, BAM files are not necessarily compatible between versions of Panda3D. Pickles wouldn’t have that problem but I understand the problem with them now. I am going to give the BAM solution a shot now and I’ll monkey around with the getstate later on when I am not under the gun to produce something.

Thanks for the help,
Paul

I have put this short code together. I demonstrates one way to pickle a class instance with one NodePath attribute. ‘X’ will pickle/save, and ‘Y’ will unpickle/load.

Please notice that I didn’t store the NodePath’s position, orientation or parent. Also pickling the object deletes the NodePath instance.

I wonder if this is the best way to make Panda3D objects persistent. If anybody knows other/better way, please tell.

enn0x

import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
import sys
import cPickle as pickle
import time

class Foo:

    def __init__( self ):
        self.x = 999
        self.z = 'abc'
        self.np = loader.loadModel( 'models/box' )
        self.np.reparentTo( render )
        self.np.setPos( 0, 10, 0 )

    def __getstate__( self ):
        filename = '%i.bam' % id( self.np )
        self.np.writeBamFile( filename )
        self.np.removeNode( )

        dict = self.__dict__
        del dict[ 'np' ]
        dict[ 'filename' ] = filename

        return dict

    def __setstate__( self, dict ):
        filename = dict[ 'filename' ]
        del dict[ 'filename' ]

        self.__dict__ = dict

        self.np = loader.loadModel( filename )
        self.np.reparentTo( render )
        self.np.setPos( 0, 10, 0 )

class World( DirectObject ):

    def __init__( self ):
        self.accept( 'escape', self.exit )
        self.accept( 'x', self.save )
        self.accept( 'y', self.load )

        self.foo = Foo( )

    def save( self ):
        print 'SAVE'
        pickle.dump( self.foo, file( 'foo.p', 'wb' ) )

    def load( self ):
        print 'LOAD'
        self.foo = pickle.load( file( 'foo.p', 'rb' ) )

    def exit( self ):
        sys.exit( 1 )

world = World( )
run( )