mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-29 16:20:11 -04:00
stdpy: Fix pickle sometimes duplicating Panda objects
We have to unify multiple Python wrappers pointing to the same C++ object.
This commit is contained in:
parent
ac991e4c5e
commit
a5557bc38d
@ -22,7 +22,7 @@ Unfortunately, cPickle cannot be supported, because it does not
|
|||||||
support extensions of this nature. """
|
support extensions of this nature. """
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from panda3d.core import BamWriter, BamReader
|
from panda3d.core import BamWriter, BamReader, TypedObject
|
||||||
|
|
||||||
if sys.version_info >= (3, 0):
|
if sys.version_info >= (3, 0):
|
||||||
from copyreg import dispatch_table
|
from copyreg import dispatch_table
|
||||||
@ -47,6 +47,7 @@ class _Pickler(BasePickler):
|
|||||||
|
|
||||||
def __init__(self, *args, **kw):
|
def __init__(self, *args, **kw):
|
||||||
self.bamWriter = BamWriter()
|
self.bamWriter = BamWriter()
|
||||||
|
self._canonical = {}
|
||||||
BasePickler.__init__(self, *args, **kw)
|
BasePickler.__init__(self, *args, **kw)
|
||||||
|
|
||||||
# We have to duplicate most of the save() method, so we can add
|
# We have to duplicate most of the save() method, so we can add
|
||||||
@ -62,6 +63,21 @@ class _Pickler(BasePickler):
|
|||||||
self.save_pers(pid)
|
self.save_pers(pid)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Check if this is a Panda type that we've already saved; if so, store
|
||||||
|
# a mapping to the canonical copy, so that Python's memoization system
|
||||||
|
# works properly. This is needed because Python uses id(obj) for
|
||||||
|
# memoization, but there may be multiple Python wrappers for the same
|
||||||
|
# C++ pointer, and we don't want that to result in duplication.
|
||||||
|
t = type(obj)
|
||||||
|
if issubclass(t, TypedObject.__base__):
|
||||||
|
canonical = self._canonical.get(obj.this)
|
||||||
|
if canonical is not None:
|
||||||
|
obj = canonical
|
||||||
|
else:
|
||||||
|
# First time we're seeing this C++ pointer; save it as the
|
||||||
|
# "canonical" version.
|
||||||
|
self._canonical[obj.this] = obj
|
||||||
|
|
||||||
# Check the memo
|
# Check the memo
|
||||||
x = self.memo.get(id(obj))
|
x = self.memo.get(id(obj))
|
||||||
if x:
|
if x:
|
||||||
@ -69,7 +85,6 @@ class _Pickler(BasePickler):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Check the type dispatch table
|
# Check the type dispatch table
|
||||||
t = type(obj)
|
|
||||||
f = self.dispatch.get(t)
|
f = self.dispatch.get(t)
|
||||||
if f:
|
if f:
|
||||||
f(self, obj) # Call unbound method with explicit self
|
f(self, obj) # Call unbound method with explicit self
|
||||||
@ -157,25 +172,9 @@ class Unpickler(BaseUnpickler):
|
|||||||
BaseUnpickler.dispatch[pickle.REDUCE] = load_reduce
|
BaseUnpickler.dispatch[pickle.REDUCE] = load_reduce
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 8):
|
Pickler = _Pickler
|
||||||
# In Python 3.8 and up, we can use the C implementation of Pickler, which
|
|
||||||
# supports a reducer_override method.
|
|
||||||
class Pickler(pickle.Pickler):
|
|
||||||
def __init__(self, *args, **kw):
|
|
||||||
self.bamWriter = BamWriter()
|
|
||||||
pickle.Pickler.__init__(self, *args, **kw)
|
|
||||||
|
|
||||||
def reducer_override(self, obj):
|
if sys.version_info < (3, 0):
|
||||||
reduce = getattr(obj, "__reduce_persist__", None)
|
|
||||||
if reduce:
|
|
||||||
return reduce(self)
|
|
||||||
|
|
||||||
return NotImplemented
|
|
||||||
else:
|
|
||||||
# Otherwise, we have to use our custom version that overrides save().
|
|
||||||
Pickler = _Pickler
|
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
del _Pickler
|
del _Pickler
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,6 +12,20 @@ def test_reduce_persist():
|
|||||||
assert tuple(parent2.children) == (child2,)
|
assert tuple(parent2.children) == (child2,)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pickle_copy():
|
||||||
|
from panda3d.core import PandaNode, NodePath
|
||||||
|
|
||||||
|
# Make two Python wrappers pointing to the same node
|
||||||
|
node1 = PandaNode("node")
|
||||||
|
node2 = NodePath(node1).node()
|
||||||
|
assert node1.this == node2.this
|
||||||
|
assert id(node1) != id(node2)
|
||||||
|
|
||||||
|
# Test that pickling and loading still results in the same node object.
|
||||||
|
node1, node2 = loads(dumps([node1, node2]))
|
||||||
|
assert node1 == node2
|
||||||
|
|
||||||
|
|
||||||
def test_pickle_error():
|
def test_pickle_error():
|
||||||
class ErroneousPickleable(object):
|
class ErroneousPickleable(object):
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user