[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [19788] trunk/blender/source/gameengine: BGE Python API

Campbell Barton ideasman42 at gmail.com
Sun Apr 19 14:46:39 CEST 2009


Revision: 19788
          http://projects.blender.org/plugins/scmsvn/viewcvs.php?view=rev&root=bf-blender&revision=19788
Author:   campbellbarton
Date:     2009-04-19 14:46:39 +0200 (Sun, 19 Apr 2009)

Log Message:
-----------
BGE Python API

This changes how the BGE classes and Python work together, which hasnt changed since blender went opensource.
The main difference is PyObjectPlus - the base class for most game engine classes, no longer inherit from PyObject, and cannot be cast to a PyObject.

This has the advantage that the BGE does not have to keep 2 reference counts valid for C++ and Python.

Previously C++ classes would never be freed while python held a reference, however this reference could be problematic eg: a GameObject that isnt in a scene anymore should not be used by python, doing so could even crash blender in some cases.

Instead PyObjectPlus has a member "PyObject *m_proxy" which is lazily initialized when python needs it. m_proxy reference counts are managed by python, though it should never be freed while the C++ class exists since it holds a reference to avoid making and freeing it all the time.
When the C++ class is free'd it sets the m_proxy reference to NULL, If python accesses this variable it will raise a RuntimeError, (check the isValid attribute to see if its valid without raising an error).
- This replaces the m_zombie bool and IsZombie() tests added recently.

In python return values that used to be..
 return value->AddRef();
Are now
 return value->GetProxy();
or...
 return value->NewProxy(true); // true means python owns this C++ value which will be deleted when the PyObject is freed

Modified Paths:
--------------
    trunk/blender/source/gameengine/Converter/BL_BlenderDataConversion.cpp
    trunk/blender/source/gameengine/Expressions/InputParser.cpp
    trunk/blender/source/gameengine/Expressions/ListValue.cpp
    trunk/blender/source/gameengine/Expressions/ListValue.h
    trunk/blender/source/gameengine/Expressions/PyObjectPlus.cpp
    trunk/blender/source/gameengine/Expressions/PyObjectPlus.h
    trunk/blender/source/gameengine/Expressions/Value.cpp
    trunk/blender/source/gameengine/Expressions/Value.h
    trunk/blender/source/gameengine/GameLogic/SCA_ILogicBrick.cpp
    trunk/blender/source/gameengine/GameLogic/SCA_PythonController.cpp
    trunk/blender/source/gameengine/Ketsji/KXNetwork/KX_NetworkMessageSensor.cpp
    trunk/blender/source/gameengine/Ketsji/KX_BlenderMaterial.cpp
    trunk/blender/source/gameengine/Ketsji/KX_CameraActuator.cpp
    trunk/blender/source/gameengine/Ketsji/KX_GameObject.cpp
    trunk/blender/source/gameengine/Ketsji/KX_GameObject.h
    trunk/blender/source/gameengine/Ketsji/KX_MeshProxy.cpp
    trunk/blender/source/gameengine/Ketsji/KX_MouseFocusSensor.cpp
    trunk/blender/source/gameengine/Ketsji/KX_ParentActuator.cpp
    trunk/blender/source/gameengine/Ketsji/KX_PolyProxy.cpp
    trunk/blender/source/gameengine/Ketsji/KX_PolygonMaterial.cpp
    trunk/blender/source/gameengine/Ketsji/KX_PyConstraintBinding.cpp
    trunk/blender/source/gameengine/Ketsji/KX_PythonInit.cpp
    trunk/blender/source/gameengine/Ketsji/KX_PythonInitTypes.cpp
    trunk/blender/source/gameengine/Ketsji/KX_RaySensor.cpp
    trunk/blender/source/gameengine/Ketsji/KX_SCA_AddObjectActuator.cpp
    trunk/blender/source/gameengine/Ketsji/KX_SCA_ReplaceMeshActuator.cpp
    trunk/blender/source/gameengine/Ketsji/KX_Scene.cpp
    trunk/blender/source/gameengine/Ketsji/KX_SceneActuator.cpp
    trunk/blender/source/gameengine/Ketsji/KX_TouchSensor.cpp
    trunk/blender/source/gameengine/Ketsji/KX_TrackToActuator.cpp
    trunk/blender/source/gameengine/Ketsji/KX_VehicleWrapper.cpp
    trunk/blender/source/gameengine/VideoTexture/FilterSource.h
    trunk/blender/source/gameengine/VideoTexture/ImageRender.cpp

Modified: trunk/blender/source/gameengine/Converter/BL_BlenderDataConversion.cpp
===================================================================
--- trunk/blender/source/gameengine/Converter/BL_BlenderDataConversion.cpp	2009-04-19 12:26:31 UTC (rev 19787)
+++ trunk/blender/source/gameengine/Converter/BL_BlenderDataConversion.cpp	2009-04-19 12:46:39 UTC (rev 19788)
@@ -2354,7 +2354,7 @@
 			// Remove the child reference in the local list!
 			// Note: there may be descendents already if the children of the child were processed
 			//       by this loop before the child. In that case, we must remove the children also
-			CListValue* childrenlist = (CListValue*)childobj->PyGetChildrenRecursive(childobj);
+			CListValue* childrenlist = childobj->GetChildrenRecursive();
 			childrenlist->Add(childobj->AddRef());
 			for ( i=0;i<childrenlist->GetCount();i++)
 			{

Modified: trunk/blender/source/gameengine/Expressions/InputParser.cpp
===================================================================
--- trunk/blender/source/gameengine/Expressions/InputParser.cpp	2009-04-19 12:26:31 UTC (rev 19787)
+++ trunk/blender/source/gameengine/Expressions/InputParser.cpp	2009-04-19 12:46:39 UTC (rev 19788)
@@ -649,7 +649,7 @@
 	CExpression* expr = parser.ProcessText(txt);
 	CValue* val = expr->Calculate();
 	expr->Release();
-	return val;
+	return val->GetProxy();
 }
 
 static PyMethodDef	CParserMethods[] = 

Modified: trunk/blender/source/gameengine/Expressions/ListValue.cpp
===================================================================
--- trunk/blender/source/gameengine/Expressions/ListValue.cpp	2009-04-19 12:26:31 UTC (rev 19787)
+++ trunk/blender/source/gameengine/Expressions/ListValue.cpp	2009-04-19 12:46:39 UTC (rev 19788)
@@ -27,45 +27,61 @@
 #define Py_ssize_t int
 #endif
 
-Py_ssize_t listvalue_bufferlen(PyObject* list)
+Py_ssize_t listvalue_bufferlen(PyObject* self)
 {
-	return (Py_ssize_t)( ((CListValue*)list)->GetCount());
+	CListValue *list= static_cast<CListValue *>(BGE_PROXY_REF(self));
+	if (list==NULL)
+		return 0;
+	
+	return (Py_ssize_t)list->GetCount();
 }
 
-PyObject* listvalue_buffer_item(PyObject* list,Py_ssize_t index)
+PyObject* listvalue_buffer_item(PyObject* self, Py_ssize_t index)
 {
-	int count = ((CListValue*) list)->GetCount();
+	CListValue *list= static_cast<CListValue *>(BGE_PROXY_REF(self));
+	if (list==NULL) {
+		PyErr_SetString(PyExc_IndexError, BGE_PROXY_ERROR_MSG);
+		return NULL;
+	}
 	
+	int count = list->GetCount();
+	
 	if (index < 0)
 		index = count+index;
 	
 	if (index >= 0 && index < count)
 	{
-		PyObject* pyobj = ((CListValue*) list)->GetValue(index)->ConvertValueToPython();
+		PyObject* pyobj = list->GetValue(index)->ConvertValueToPython();
 		if (pyobj)
 			return pyobj;
 		else
-			return ((CListValue*) list)->GetValue(index)->AddRef();
+			return list->GetValue(index)->GetProxy();
 
 	}
 	PyErr_SetString(PyExc_IndexError, "Python ListIndex out of range");
 	return NULL;
 }
 
-PyObject* listvalue_mapping_subscript(PyObject* list,PyObject* pyindex)
+PyObject* listvalue_mapping_subscript(PyObject* self, PyObject* pyindex)
 {
+	CListValue *list= static_cast<CListValue *>(BGE_PROXY_REF(self));
+	if (list==NULL) {
+		PyErr_SetString(PyExc_IndexError, BGE_PROXY_ERROR_MSG);
+		return NULL;
+	}
+	
 	if (PyString_Check(pyindex))
 	{
 		STR_String  index(PyString_AsString(pyindex));
 		CValue *item = ((CListValue*) list)->FindValue(index);
 		if (item)
-			return (PyObject*) item;
+			return item->GetProxy();
 			
 	}
 	if (PyInt_Check(pyindex))
 	{
 		int index = PyInt_AsLong(pyindex);
-		return listvalue_buffer_item(list, index);
+		return listvalue_buffer_item(self, index);
 	}
 	
 	PyObject *pyindex_str = PyObject_Repr(pyindex); /* new ref */
@@ -76,10 +92,16 @@
 
 
 /* just slice it into a python list... */
-PyObject* listvalue_buffer_slice(PyObject* list,Py_ssize_t ilow, Py_ssize_t ihigh)
+PyObject* listvalue_buffer_slice(PyObject* self,Py_ssize_t ilow, Py_ssize_t ihigh)
 {
+	CListValue *list= static_cast<CListValue *>(BGE_PROXY_REF(self));
+	if (list==NULL) {
+		PyErr_SetString(PyExc_IndexError, BGE_PROXY_ERROR_MSG);
+		return NULL;
+	}
+	
 	int i, j;
-	PyListObject *newlist;
+	PyObject *newlist;
 
 	if (ilow < 0) ilow = 0;
 
@@ -90,18 +112,18 @@
     if (ihigh < ilow)
         ihigh = ilow;
 
-	newlist = (PyListObject *) PyList_New(ihigh - ilow);
+	newlist = PyList_New(ihigh - ilow);
 	if (!newlist)
 		return NULL;
 
 	for (i = ilow, j = 0; i < ihigh; i++, j++)
 	{
-		PyObject* pyobj = ((CListValue*) list)->GetValue(i)->ConvertValueToPython();
+		PyObject* pyobj = list->GetValue(i)->ConvertValueToPython();
 		if (!pyobj)
-			pyobj = ((CListValue*) list)->GetValue(i)->AddRef();
-		newlist->ob_item[j] = pyobj;
+			pyobj = list->GetValue(i)->GetProxy();
+		PyList_SET_ITEM(newlist, i, pyobj);
 	}	
-	return (PyObject *) newlist;
+	return newlist;
 }
 
 
@@ -109,11 +131,16 @@
 static PyObject *
 listvalue_buffer_concat(PyObject * self, PyObject * other)
 {
+	CListValue *listval= static_cast<CListValue *>(BGE_PROXY_REF(self));
+	if (listval==NULL) {
+		PyErr_SetString(PyExc_IndexError, BGE_PROXY_ERROR_MSG);
+		return NULL;
+	}
+	
 	// for now, we support CListValue concatenated with items
 	// and CListValue concatenated to Python Lists
 	// and CListValue concatenated with another CListValue
-
-	CListValue* listval = (CListValue*) self;
+	
 	listval->AddRef();
 	if (other->ob_type == &PyList_Type)
 	{
@@ -519,8 +546,8 @@
 	int numelem = GetCount();
 	for (int i=0;i<numelem;i++)
 	{
-		if (reinterpret_cast<BGE_ID_TYPE>(static_cast<PyObject*>(m_pValueArray[i])) == id)
-			return GetValue(i);
+		if (reinterpret_cast<BGE_ID_TYPE>(m_pValueArray[i]->m_proxy) == id)
+			return GetValue(i)->GetProxy();
 	
 	}
 	PyErr_SetString(PyExc_IndexError, "from_id(#), id not found in CValueList");

Modified: trunk/blender/source/gameengine/Expressions/ListValue.h
===================================================================
--- trunk/blender/source/gameengine/Expressions/ListValue.h	2009-04-19 12:26:31 UTC (rev 19787)
+++ trunk/blender/source/gameengine/Expressions/ListValue.h	2009-04-19 12:46:39 UTC (rev 19788)
@@ -61,9 +61,11 @@
 
 	virtual PyObject* py_getattro(PyObject* attr);
 	virtual PyObject* py_repr(void) {
-		PyObject *py_list= PySequence_List((PyObject *)this);
+		PyObject *py_proxy= this->GetProxy();
+		PyObject *py_list= PySequence_List(py_proxy);
 		PyObject *py_string= PyObject_Repr(py_list);
 		Py_DECREF(py_list);
+		Py_DECREF(py_proxy);
 		return py_string;
 	}
 

Modified: trunk/blender/source/gameengine/Expressions/PyObjectPlus.cpp
===================================================================
--- trunk/blender/source/gameengine/Expressions/PyObjectPlus.cpp	2009-04-19 12:26:31 UTC (rev 19787)
+++ trunk/blender/source/gameengine/Expressions/PyObjectPlus.cpp	2009-04-19 12:46:39 UTC (rev 19788)
@@ -54,6 +54,7 @@
  * PyObjectPlus Type		-- Every class, even the abstract one should have a Type
 ------------------------------*/
 
+
 PyTypeObject PyObjectPlus::Type = {
 	PyObject_HEAD_INIT(NULL)
 	0,				/*ob_size*/
@@ -74,21 +75,33 @@
 	Methods
 };
 
+
 PyObjectPlus::~PyObjectPlus()
 {
-	if (ob_refcnt)
-	{
-		_Py_ForgetReference(this);
+	if(m_proxy) {
+		Py_DECREF(m_proxy);			/* Remove own reference, python may still have 1 */
+		BGE_PROXY_REF(m_proxy)= NULL;
 	}
 //	assert(ob_refcnt==0);
 }
 
+void PyObjectPlus::PyDestructor(PyObject *self)				// python wrapper
+{
+	PyObjectPlus *self_plus= BGE_PROXY_REF(self);
+	if(self_plus) {
+		if(BGE_PROXY_PYOWNS(self)) { /* Does python own this?, then delete it  */
+			delete self_plus;
+		}
+		
+		BGE_PROXY_REF(self)= NULL; // not really needed
+	}
+	PyObject_DEL( self );
+};
+
 PyObjectPlus::PyObjectPlus(PyTypeObject *T) 				// constructor
 {
 	MT_assert(T != NULL);
-	this->ob_type = T; 
-	_Py_NewReference(this);
-	SetZombie(false);
+	m_proxy= NULL;
 };
   
 /*------------------------------
@@ -131,7 +144,7 @@
 		if (PyCObject_Check(descr)) {
 			return py_get_attrdef((void *)this, (const PyAttributeDef*)PyCObject_AsVoidPtr(descr));
 		} else if (descr->ob_type->tp_descr_get) {
-			return PyCFunction_New(((PyMethodDescrObject *)descr)->d_method, (PyObject *)this);
+			return PyCFunction_New(((PyMethodDescrObject *)descr)->d_method, this->m_proxy);
 		} else {
 			fprintf(stderr, "Unknown attribute type (PyObjectPlus::py_getattro)");
 			return descr;
@@ -794,5 +807,47 @@
 	return pydict;
 }
 
+
+
+PyObject *PyObjectPlus::GetProxy_Ext(PyObjectPlus *self, PyTypeObject *tp)
+{
+	if (self->m_proxy==NULL)
+	{
+		self->m_proxy = reinterpret_cast<PyObject *>PyObject_NEW( PyObjectPlus_Proxy, tp);
+		BGE_PROXY_PYOWNS(self->m_proxy) = false;
+	}
+	//PyObject_Print(self->m_proxy, stdout, 0);
+	//printf("ref %d\n", self->m_proxy->ob_refcnt);
+	
+	BGE_PROXY_REF(self->m_proxy) = self; /* Its possible this was set to NULL, so set it back here */
+	Py_INCREF(self->m_proxy); /* we own one, thos ones fore the return */
+	return self->m_proxy;
+}
+
+PyObject *PyObjectPlus::NewProxy_Ext(PyObjectPlus *self, PyTypeObject *tp, bool py_owns)
+{
+	if (self->m_proxy)
+	{
+		if(py_owns)
+		{	/* Free */
+			BGE_PROXY_REF(self->m_proxy) = NULL;
+			Py_DECREF(self->m_proxy);
+			self->m_proxy= NULL;
+		}
+		else {
+			Py_INCREF(self->m_proxy);
+			return self->m_proxy;
+		}
+		
+	}
+	
+	GetProxy_Ext(self, tp);
+	if(py_owns) {
+		BGE_PROXY_PYOWNS(self->m_proxy) = py_owns;
+		Py_DECREF(self->m_proxy); /* could avoid thrashing here but for now its ok */
+	}
+	return self->m_proxy;
+}
+
 #endif //NO_EXP_PYTHON_EMBEDDING
 

Modified: trunk/blender/source/gameengine/Expressions/PyObjectPlus.h
===================================================================
--- trunk/blender/source/gameengine/Expressions/PyObjectPlus.h	2009-04-19 12:26:31 UTC (rev 19787)
+++ trunk/blender/source/gameengine/Expressions/PyObjectPlus.h	2009-04-19 12:46:39 UTC (rev 19788)
@@ -81,6 +81,20 @@
 	exit(-1);
 };
 
+typedef struct {
+	PyObject_HEAD		/* required python macro   */
+	class PyObjectPlus *ref;
+	bool py_owns;
+} PyObjectPlus_Proxy;
+
+#define BGE_PROXY_ERROR_MSG "Blender Game Engine data has been freed, cannot use this python variable"
+#define BGE_PROXY_REF(_self) (((PyObjectPlus_Proxy *)_self)->ref)
+#define BGE_PROXY_PYOWNS(_self) (((PyObjectPlus_Proxy *)_self)->py_owns)
+
+/* Note, sometimes we dont care what BGE type this is as long as its a proxy */
+#define BGE_PROXY_CHECK_TYPE(_self) ((_self)->ob_type->tp_dealloc == PyDestructor)
+
+
 								// This must be the first line of each 
 								// PyC++ class

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list