Commit dd914b09 authored by David Gobbi's avatar David Gobbi
Browse files

ENH: Ghost PyVTKObjects with special attrs when deleting them.

To make sure that special PyVTKObject attributes (i.e. dict
entries or custom classes) aren't lost when the object goes
out of scope, make a ghost of the object if: a) references
to it still exist within VTK and b) it has custom attributes.
The ghost is brought back to life if the VTK pointer returns
to python.  If the VTK pointer becomes invalid, the ghost is
erased.  Pointer validity is checked via weak pointers.
parent b6f61659
......@@ -4,6 +4,7 @@
IF (VTK_PYTHON_EXE)
FOREACH ( tfile
PythonSmoke
TestGhost
TestWeakref
TestNumpySupport
TestTerminationCrash
......
"""Test ghost object support in VTK-Python
When PyVTKObject is destroyed, the vtkObjectBase that it
contained often continues to exist because references to
it still exist within VTK. When that vtkObjectBase is
returned to python, a new PyVTKObject is created.
If the PyVTKObject has a custom class or a custom dict,
then we make a "ghost" of the PyVTKObject when it is
destroyed, so that if its vtkObjectBase returns to python,
the PyVTKObject can be restored with the proper class and
dict. Each ghost has a weak pointer to its vtkObjectBase
so that it can be erased if the vtkObjectBase is destroyed.
To be tested:
- make sure custom dicts are restored
- make sure custom classes are restored
Created on Aug 19, 2010 by David Gobbi
"""
import sys
import exceptions
import vtk
from vtk.test import Testing
class vtkCustomObject(vtk.vtkObject):
pass
class TestGhost(Testing.vtkTest):
def testGhostForDict(self):
"""Ghost an object to save the dict"""
o = vtk.vtkObject()
o.customattr = 'hello'
a = vtk.vtkVariantArray()
a.InsertNextValue(o)
i = id(o)
del o
o = vtk.vtkObject()
o = a.GetValue(0).ToVTKObject()
# make sure the id has changed, but dict the same
self.assertEqual(o.customattr, 'hello')
self.assertNotEqual(i, id(o))
def testGhostForClass(self):
"""Ghost an object to save the class"""
o = vtkCustomObject()
a = vtk.vtkVariantArray()
a.InsertNextValue(o)
i = id(o)
del o
o = vtk.vtkObject()
o = a.GetValue(0).ToVTKObject()
# make sure the id has changed, but class the same
self.assertEqual(o.__class__, vtkCustomObject)
self.assertNotEqual(i, id(o))
if __name__ == "__main__":
Testing.main([(TestGhost, 'test')])
......@@ -74,7 +74,7 @@ static PyObject *PyVTKClass_Call(PyObject *op, PyObject *arg, PyObject *kw)
if (initfunc)
{
PyObject *obj = PyVTKObject_New((PyObject *)self, NULL);
PyObject *obj = PyVTKObject_New((PyObject *)self, NULL, NULL);
PyObject *cinitfunc = PyObject_GetAttr(obj, initstr);
PyObject *res = PyEval_CallObjectWithKeywords(cinitfunc, arg, kw);
if (res == NULL)
......@@ -101,7 +101,7 @@ static PyObject *PyVTKClass_Call(PyObject *op, PyObject *arg, PyObject *kw)
}
if (PyArg_ParseTuple(arg,(char*)""))
{
return PyVTKObject_New((PyObject *)self, NULL);
return PyVTKObject_New((PyObject *)self, NULL, NULL);
}
PyErr_Clear();
if (PyArg_ParseTuple(arg,(char*)"O", &arg))
......
......@@ -385,7 +385,8 @@ PyTypeObject PyVTKObject_Type = {
VTK_PYTHON_UTIL_SUPRESS_UNINITIALIZED
};
PyObject *PyVTKObject_New(PyObject *pyvtkclass, vtkObjectBase *ptr)
PyObject *PyVTKObject_New(
PyObject *pyvtkclass, PyObject *pydict, vtkObjectBase *ptr)
{
PyVTKClass *vtkclass = (PyVTKClass *)pyvtkclass;
bool haveRef = false;
......@@ -413,19 +414,38 @@ PyObject *PyVTKObject_New(PyObject *pyvtkclass, vtkObjectBase *ptr)
#endif
self->vtk_ptr = ptr;
PyObject *cls = vtkPythonUtil::FindClass(ptr->GetClassName());
self->vtk_class = (PyVTKClass *)cls;
self->vtk_class = NULL;
// We want to find the class that best matches GetClassName(), since
// the object might have been created by a factory and might therefore
// not be of the type of the class that New() was called on.
// There are two situations where we don't want to do this, though.
// If pydict is set, then the object is being recreated from a ghost
// and we must keep the original class. Also, if vtk_methods is NULL
// then the class is a special custom class and must be kept.
if (pydict == NULL && vtkclass->vtk_methods != NULL)
{
PyObject *cls = vtkPythonUtil::FindClass(ptr->GetClassName());
self->vtk_class = (PyVTKClass *)cls;
}
// If the class was not in the dictionary (i.e. if there is no 'python'
// level class to support the VTK level class) we fall back to this.
if (self->vtk_class == NULL || vtkclass->vtk_methods == NULL)
// Use the class that was passed as an argument
if (self->vtk_class == NULL)
{
self->vtk_class = vtkclass;
}
Py_INCREF(self->vtk_class);
self->vtk_dict = PyDict_New();
if (pydict)
{
Py_INCREF(pydict);
self->vtk_dict = pydict;
}
else
{
self->vtk_dict = PyDict_New();
}
#if PY_VERSION_HEX >= 0x02010000
self->vtk_weakreflist = NULL;
......
......@@ -43,7 +43,8 @@ extern VTK_PYTHON_EXPORT PyTypeObject PyVTKObject_Type;
extern "C"
{
VTK_PYTHON_EXPORT
PyObject *PyVTKObject_New(PyObject *vtkclass, vtkObjectBase *ptr);
PyObject *PyVTKObject_New(
PyObject *vtkclass, PyObject *pydict, vtkObjectBase *ptr);
}
#endif
......@@ -19,6 +19,7 @@
#include "vtkObject.h"
#include "vtkSmartPointerBase.h"
#include "vtkWeakPointerBase.h"
#include "vtkVariant.h"
#include "vtkWindows.h"
#include "vtkToolkits.h"
......@@ -32,14 +33,66 @@
#include "sip.h"
#endif
//--------------------------------------------------------------------
// There are three maps associated with the Python wrappers
// A ghost object, can be used to recreate a deleted PyVTKObject
class PyVTKObjectGhost
{
public:
PyVTKObjectGhost() : vtk_ptr(), vtk_class(0), vtk_dict(0){};
PyVTKObjectGhost(vtkWeakPointerBase &p, PyVTKClass *c, PyObject *d) :
vtk_ptr(p)
{
Py_INCREF(c);
Py_INCREF(d);
this->vtk_class = c;
this->vtk_dict = d;
};
PyVTKObjectGhost(const PyVTKObjectGhost& o)
{
this->vtk_ptr = o.vtk_ptr;
this->vtk_class = o.vtk_class;
this->vtk_dict = o.vtk_dict;
Py_XINCREF(this->vtk_class);
Py_XINCREF(this->vtk_dict);
};
PyVTKObjectGhost &operator=(const PyVTKObjectGhost& o)
{
this->vtk_ptr = o.vtk_ptr;
this->vtk_class = o.vtk_class;
this->vtk_dict = o.vtk_dict;
Py_XINCREF(this->vtk_class);
Py_XINCREF(this->vtk_dict);
return *this;
};
~PyVTKObjectGhost()
{
Py_XDECREF(this->vtk_class);
Py_XDECREF(this->vtk_dict);
};
// copy constructor and assignment operator !!!
// they should steal the references
vtkWeakPointerBase vtk_ptr;
PyVTKClass *vtk_class;
PyObject *vtk_dict;
};
//--------------------------------------------------------------------
// There are four maps associated with the Python wrappers
class vtkPythonObjectMap
: public vtkstd::map<vtkSmartPointerBase, PyObject*>
{
};
class vtkPythonGhostMap
: public vtkstd::map<vtkObjectBase*, PyVTKObjectGhost>
{
};
class vtkPythonClassMap
: public vtkstd::map<vtkstd::string, PyObject*>
{
......@@ -66,6 +119,7 @@ void vtkPythonUtilDelete()
vtkPythonUtil::vtkPythonUtil()
{
this->ObjectMap = new vtkPythonObjectMap;
this->GhostMap = new vtkPythonGhostMap;
this->ClassMap = new vtkPythonClassMap;
this->SpecialTypeMap = new vtkPythonSpecialTypeMap;
}
......@@ -74,6 +128,7 @@ vtkPythonUtil::vtkPythonUtil()
vtkPythonUtil::~vtkPythonUtil()
{
delete this->ObjectMap;
delete this->GhostMap;
delete this->ClassMap;
delete this->SpecialTypeMap;
}
......@@ -1010,16 +1065,48 @@ void vtkPythonUtil::AddObjectToMap(PyObject *obj, vtkObjectBase *ptr)
//--------------------------------------------------------------------
void vtkPythonUtil::RemoveObjectFromMap(PyObject *obj)
{
vtkObjectBase *ptr = ((PyVTKObject *)obj)->vtk_ptr;
PyVTKObject *pobj = (PyVTKObject *)obj;
#ifdef VTKPYTHONDEBUG
vtkGenericWarningMacro("Deleting an object from map obj = " << obj << " "
<< obj->vtk_ptr);
vtkGenericWarningMacro("Deleting an object from map obj = "
<< pobj << " " << pobj->vtk_ptr);
#endif
if (vtkPythonMap)
{
vtkPythonMap->ObjectMap->erase(ptr);
vtkWeakPointerBase wptr;
// check for customized class or dict
if (pobj->vtk_class->vtk_methods == 0 ||
PyDict_Size(pobj->vtk_dict))
{
wptr = pobj->vtk_ptr;
}
vtkPythonMap->ObjectMap->erase(pobj->vtk_ptr);
// if the VTK object still exists, then make a ghost
if (wptr.GetPointer())
{
// Erase ghosts of VTK objects that have been deleted
vtkstd::map<vtkObjectBase*, PyVTKObjectGhost>::iterator i =
vtkPythonMap->GhostMap->begin();
while (i != vtkPythonMap->GhostMap->end())
{
if (!i->second.vtk_ptr.GetPointer())
{
vtkPythonMap->GhostMap->erase(i++);
}
else
{
++i;
}
}
// Add this new ghost to the map
(*vtkPythonMap->GhostMap)[pobj->vtk_ptr] =
PyVTKObjectGhost(wptr, pobj->vtk_class, pobj->vtk_dict);
}
}
}
......@@ -1028,51 +1115,59 @@ PyObject *vtkPythonUtil::GetObjectFromPointer(vtkObjectBase *ptr)
{
PyObject *obj = NULL;
#ifdef VTKPYTHONDEBUG
vtkGenericWarningMacro("Checking into pointer " << ptr);
#endif
if (ptr)
{
vtkstd::map<vtkSmartPointerBase, PyObject*>::iterator i =
vtkPythonMap->ObjectMap->find(ptr);
if(i != vtkPythonMap->ObjectMap->end())
if (i != vtkPythonMap->ObjectMap->end())
{
obj = i->second;
}
if (obj)
{
Py_INCREF(obj);
return obj;
}
}
else
{
Py_INCREF(Py_None);
obj = Py_None;
return Py_None;
}
// search weak list for object, resurrect if it is there
vtkstd::map<vtkObjectBase*, PyVTKObjectGhost>::iterator i =
vtkPythonMap->GhostMap->find(ptr);
if (i != vtkPythonMap->GhostMap->end())
{
if (i->second.vtk_ptr.GetPointer())
{
obj = PyVTKObject_New((PyObject *)i->second.vtk_class,
i->second.vtk_dict, ptr);
}
vtkPythonMap->GhostMap->erase(i);
}
#ifdef VTKPYTHONDEBUG
vtkGenericWarningMacro("Checking into pointer " << ptr << " obj = " << obj);
#endif
if (obj == NULL)
{
// create a new object
PyObject *vtkclass = NULL;
vtkstd::map<vtkstd::string, PyObject*>::iterator i =
vtkPythonMap->ClassMap->find(ptr->GetClassName());
if(i != vtkPythonMap->ClassMap->end())
if (i != vtkPythonMap->ClassMap->end())
{
vtkclass = i->second;
}
// if the class was not in the map, then find the nearest base class
// that is and associate ptr->GetClassName() with that base class
// if the class was not in the map, then find the nearest base class
// that is and associate ptr->GetClassName() with that base class
if (vtkclass == NULL)
{
vtkclass = vtkPythonUtil::FindNearestBaseClass(ptr);
vtkPythonUtil::AddClassToMap(vtkclass, ptr->GetClassName());
}
obj = PyVTKObject_New(vtkclass, ptr);
obj = PyVTKObject_New(vtkclass, NULL, ptr);
}
return obj;
......
......@@ -21,6 +21,7 @@
#include "PyVTKSpecialObject.h"
class vtkPythonObjectMap;
class vtkPythonGhostMap;
class vtkPythonClassMap;
class vtkPythonSpecialTypeMap;
class vtkVariant;
......@@ -187,6 +188,7 @@ private:
void operator=(const vtkPythonUtil&); // Not implemented.
vtkPythonObjectMap *ObjectMap;
vtkPythonGhostMap *GhostMap;
vtkPythonClassMap *ClassMap;
vtkPythonSpecialTypeMap *SpecialTypeMap;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment