[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [21234] branches/blender2.5/blender/source : BGE PyAPI support for subclassing any BGE game type from python, scripters define extra functions on gameObjects.

Campbell Barton ideasman42 at gmail.com
Mon Jun 29 14:06:46 CEST 2009


Revision: 21234
          http://projects.blender.org/plugins/scmsvn/viewcvs.php?view=rev&root=bf-blender&revision=21234
Author:   campbellbarton
Date:     2009-06-29 14:06:46 +0200 (Mon, 29 Jun 2009)

Log Message:
-----------
BGE PyAPI support for subclassing any BGE game type from python, scripters define extra functions on gameObjects.
Adding a UI to set the type on startup can be added easily.

# ----
class myPlayer(GameTypes.KX_GameObject):
  def die(self):
    # ... do stuff ...
    self.endObject()

# make an instance
player = myPlayer(gameOb) # gameOb is made invalid now.
player.die()

# ----

One limitation (which could also be an advantage), is making the subclass instance will return that subclass everywhere, you cant have 2 different subclasses of the same BGE data at once.

Modified Paths:
--------------
    branches/blender2.5/blender/source/blender/python/intern/bpy_rna.c
    branches/blender2.5/blender/source/blender/windowmanager/WM_api.h
    branches/blender2.5/blender/source/blender/windowmanager/intern/wm_keymap.c
    branches/blender2.5/blender/source/gameengine/Converter/BL_ActionActuator.cpp
    branches/blender2.5/blender/source/gameengine/Converter/BL_ShapeActionActuator.cpp
    branches/blender2.5/blender/source/gameengine/Expressions/BoolValue.cpp
    branches/blender2.5/blender/source/gameengine/Expressions/ListValue.cpp
    branches/blender2.5/blender/source/gameengine/Expressions/PyObjectPlus.cpp
    branches/blender2.5/blender/source/gameengine/Expressions/PyObjectPlus.h
    branches/blender2.5/blender/source/gameengine/Expressions/Value.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_2DFilterActuator.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_ANDController.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_ActuatorSensor.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_AlwaysSensor.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_DelaySensor.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_IController.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_ILogicBrick.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_IObject.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_ISensor.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_JoystickSensor.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_KeyboardSensor.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_LogicManager.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_MouseSensor.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_NANDController.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_NORController.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_ORController.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_PropertyActuator.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_PropertySensor.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_PythonController.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_RandomActuator.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_RandomSensor.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_XNORController.cpp
    branches/blender2.5/blender/source/gameengine/GameLogic/SCA_XORController.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/BL_Shader.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KXNetwork/KX_NetworkMessageActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KXNetwork/KX_NetworkMessageSensor.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_BlenderMaterial.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_CDActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_Camera.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_CameraActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_ConstraintActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_ConstraintWrapper.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_GameActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_GameObject.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_IpoActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_Light.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_MeshProxy.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_MouseFocusSensor.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_NearSensor.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_ObjectActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_ParentActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_PhysicsObjectWrapper.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_PolyProxy.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_PolygonMaterial.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_PyMath.h
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_PythonInitTypes.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_RadarSensor.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_RaySensor.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_SCA_AddObjectActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_SCA_DynamicActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_SCA_EndObjectActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_SCA_ReplaceMeshActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_Scene.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_SceneActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_SoundActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_StateActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_TouchSensor.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_TrackToActuator.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_VehicleWrapper.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_VertexProxy.cpp
    branches/blender2.5/blender/source/gameengine/Ketsji/KX_VisibilityActuator.cpp

Modified: branches/blender2.5/blender/source/blender/python/intern/bpy_rna.c
===================================================================
--- branches/blender2.5/blender/source/blender/python/intern/bpy_rna.c	2009-06-29 11:29:52 UTC (rev 21233)
+++ branches/blender2.5/blender/source/blender/python/intern/bpy_rna.c	2009-06-29 12:06:46 UTC (rev 21234)
@@ -204,13 +204,13 @@
 /* use our own dealloc so we can free a property if we use one */
 static void pyrna_struct_dealloc( BPy_StructRNA * self )
 {
-	/* Note!! for some weired reason calling PyObject_DEL() directly crashes blender! */
 	if (self->freeptr && self->ptr.data) {
 		IDP_FreeProperty(self->ptr.data);
 		MEM_freeN(self->ptr.data);
 		self->ptr.data= NULL;
 	}
 
+	/* Note, for subclassed PyObjects we cant just call PyObject_DEL() directly or it will crash */
 	Py_TYPE(self)->tp_free(self);
 	return;
 }

Modified: branches/blender2.5/blender/source/blender/windowmanager/WM_api.h
===================================================================
--- branches/blender2.5/blender/source/blender/windowmanager/WM_api.h	2009-06-29 11:29:52 UTC (rev 21233)
+++ branches/blender2.5/blender/source/blender/windowmanager/WM_api.h	2009-06-29 12:06:46 UTC (rev 21234)
@@ -79,7 +79,7 @@
 ListBase	*WM_keymap_listbase	(struct wmWindowManager *wm, const char *nameid, 
 								 int spaceid, int regionid);
 
-char		*WM_key_event_string(short type);
+const char	*WM_key_event_string(short type);
 char		*WM_key_event_operator_string(const struct bContext *C, const char *opname, int opcontext, struct IDProperty *properties, char *str, int len);
 
 			/* handlers */

Modified: branches/blender2.5/blender/source/blender/windowmanager/intern/wm_keymap.c
===================================================================
--- branches/blender2.5/blender/source/blender/windowmanager/intern/wm_keymap.c	2009-06-29 11:29:52 UTC (rev 21233)
+++ branches/blender2.5/blender/source/blender/windowmanager/intern/wm_keymap.c	2009-06-29 12:06:46 UTC (rev 21234)
@@ -154,7 +154,7 @@
 
 /* ***************** get string from key events **************** */
 
-char *WM_key_event_string(short type)
+const char *WM_key_event_string(short type)
 {
 	const char *name= NULL;
 	if(RNA_enum_name(event_type_items, (int)type, &name))

Modified: branches/blender2.5/blender/source/gameengine/Converter/BL_ActionActuator.cpp
===================================================================
--- branches/blender2.5/blender/source/gameengine/Converter/BL_ActionActuator.cpp	2009-06-29 11:29:52 UTC (rev 21233)
+++ branches/blender2.5/blender/source/gameengine/Converter/BL_ActionActuator.cpp	2009-06-29 12:06:46 UTC (rev 21234)
@@ -1005,19 +1005,17 @@
 		0,
 		0,
 		py_base_repr,
-		0,0,0,0,0,0,
-		NULL, //py_base_getattro,
-		NULL, //py_base_setattro,
-		0,
+		0,0,0,0,0,0,0,0,0,
 		Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
 		0,0,0,0,0,0,0,
 		Methods,
 		0,
 		0,
-		&SCA_IActuator::Type
+		&SCA_IActuator::Type,
+		0,0,0,0,0,0,
+		py_base_new
 };
 
-
 PyMethodDef BL_ActionActuator::Methods[] = {
 	//Deprecated ----->
 	{"setAction", (PyCFunction) BL_ActionActuator::sPySetAction, METH_VARARGS, (PY_METHODCHAR)SetAction_doc},

Modified: branches/blender2.5/blender/source/gameengine/Converter/BL_ShapeActionActuator.cpp
===================================================================
--- branches/blender2.5/blender/source/gameengine/Converter/BL_ShapeActionActuator.cpp	2009-06-29 11:29:52 UTC (rev 21233)
+++ branches/blender2.5/blender/source/gameengine/Converter/BL_ShapeActionActuator.cpp	2009-06-29 12:06:46 UTC (rev 21234)
@@ -427,16 +427,15 @@
 		0,
 		0,
 		py_base_repr,
-		0,0,0,0,0,0,
-		NULL, //py_base_getattro,
-		NULL, //py_base_setattro,
-		0,
+		0,0,0,0,0,0,0,0,0,
 		Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
 		0,0,0,0,0,0,0,
 		Methods,
 		0,
 		0,
-		&SCA_IActuator::Type
+		&SCA_IActuator::Type,
+		0,0,0,0,0,0,
+		py_base_new
 };
 
 

Modified: branches/blender2.5/blender/source/gameengine/Expressions/BoolValue.cpp
===================================================================
--- branches/blender2.5/blender/source/gameengine/Expressions/BoolValue.cpp	2009-06-29 11:29:52 UTC (rev 21233)
+++ branches/blender2.5/blender/source/gameengine/Expressions/BoolValue.cpp	2009-06-29 12:06:46 UTC (rev 21234)
@@ -29,7 +29,6 @@
 const STR_String CBoolValue::sTrueString  = "TRUE";
 const STR_String CBoolValue::sFalseString = "FALSE";
 
-
 CBoolValue::CBoolValue()
 /*
 pre: false

Modified: branches/blender2.5/blender/source/gameengine/Expressions/ListValue.cpp
===================================================================
--- branches/blender2.5/blender/source/gameengine/Expressions/ListValue.cpp	2009-06-29 11:29:52 UTC (rev 21233)
+++ branches/blender2.5/blender/source/gameengine/Expressions/ListValue.cpp	2009-06-29 12:06:46 UTC (rev 21234)
@@ -225,7 +225,7 @@
 			return 1;
 		}
 	}
-	else if (BGE_PROXY_CHECK_TYPE(value)) { /* not dict like at all but this worked before __contains__ was used */
+	else if (PyObject_TypeCheck(value, &CValue::Type)) { /* not dict like at all but this worked before __contains__ was used */
 		CValue *item= static_cast<CValue *>(BGE_PROXY_REF(value));
 		for (int i=0; i < self->GetCount(); i++)
 			if (self->GetValue(i) == item) // Com
@@ -289,15 +289,17 @@
 	0,			        /*tp_hash*/
 	0,				/*tp_call */
 	0,
-	NULL, //py_base_getattro,
-	NULL, //py_base_setattro,
+	NULL,
+	NULL,
 	0,
 	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
 	0,0,0,0,0,0,0,
 	Methods,
 	0,
 	0,
-	&CValue::Type
+	&CValue::Type,
+	0,0,0,0,0,0,
+	py_base_new
 };
 
 PyMethodDef CListValue::Methods[] = {

Modified: branches/blender2.5/blender/source/gameengine/Expressions/PyObjectPlus.cpp
===================================================================
--- branches/blender2.5/blender/source/gameengine/Expressions/PyObjectPlus.cpp	2009-06-29 11:29:52 UTC (rev 21233)
+++ branches/blender2.5/blender/source/gameengine/Expressions/PyObjectPlus.cpp	2009-06-29 12:06:46 UTC (rev 21234)
@@ -74,10 +74,7 @@
 	0,
 	0,
 	py_base_repr,
-	0,0,0,0,0,0,
-	NULL, //py_base_getattro,
-	NULL, //py_base_setattro,
-	0,
+	0,0,0,0,0,0,0,0,0,
 	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
 	0,0,0,0,0,0,0,
 	Methods,
@@ -96,6 +93,88 @@
 //	assert(ob_refcnt==0);
 }
 
+
+PyObject *PyObjectPlus::py_base_repr(PyObject *self)			// This should be the entry in Type.
+{
+	PyObjectPlus *self_plus= BGE_PROXY_REF(self);
+	if(self_plus==NULL) {
+		PyErr_SetString(PyExc_SystemError, BGE_PROXY_ERROR_MSG);
+		return NULL;
+	}
+	
+	return self_plus->py_repr();  
+}
+
+
+PyObject * PyObjectPlus::py_base_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+	PyTypeObject *base_type;
+	PyObjectPlus_Proxy *base = NULL;
+
+	if (!PyArg_ParseTuple(args, "O:Base PyObjectPlus", &base))
+		return NULL;
+
+	/* the 'base' PyObject may be subclassed (multiple times even)
+	 * we need to find the first C++ defined class to check 'type'
+	 * is a subclass of the base arguments type.
+	 *
+	 * This way we can share one tp_new function for every PyObjectPlus
+	 *
+	 * eg.
+	 *
+	 * # CustomOb is called 'type' in this C code
+	 * class CustomOb(GameTypes.KX_GameObject):
+	 *     pass
+	 *
+	 * # this calls py_base_new(...), the type of 'CustomOb' is checked to be a subclass of the 'cont.owner' type
+	 * ob = CustomOb(cont.owner)
+	 *
+	 * */
+	base_type= Py_TYPE(base);
+	while(base_type && !BGE_PROXY_CHECK_TYPE(base_type))
+		base_type= base_type->tp_base;
+
+	if(base_type==NULL || !BGE_PROXY_CHECK_TYPE(base_type)) {
+		PyErr_SetString(PyExc_TypeError, "can't subclass from a blender game type because the argument given is not a game class or subclass");
+		return NULL;
+	}
+
+	/* use base_type rather then Py_TYPE(base) because we could alredy be subtyped */
+	if(!PyType_IsSubtype(type, base_type)) {
+		PyErr_Format(PyExc_TypeError, "can't subclass blender game type <%s> from <%s> because it is not a subclass", base_type->tp_name, type->tp_name);
+		return NULL;
+	}
+
+	/* invalidate the existing base and return a new subclassed one,
+	 * this is a bit dodgy in that it also attaches its self to the existing object
+	 * which is not really 'correct' python OO but for our use its OK. */
+
+	PyObjectPlus_Proxy *ret = (PyObjectPlus_Proxy *) type->tp_alloc(type, 0); /* starts with 1 ref, used for the return ref' */
+	ret->ref= base->ref;
+	base->ref= NULL;		/* invalidate! disallow further access */
+
+	ret->py_owns= base->py_owns;
+
+	ret->ref->m_proxy= NULL;
+
+	/* 'base' may be free'd after this func finished but not necessarily
+	 * there is no reference to the BGE data now so it will throw an error on access */
+	Py_DECREF(base);
+
+	ret->ref->m_proxy= (PyObject *)ret; /* no need to add a ref because one is added when creating. */
+	Py_INCREF(ret); /* we return a new ref but m_proxy holds a ref so we need to add one */
+
+
+	/* 'ret' will have 2 references.
+	 * - One ref is needed because ret->ref->m_proxy holds a refcount to the current proxy.
+	 * - Another is needed for returning the value.
+	 *
+	 * So we should be ok with 2 refs, but for some reason this crashes. so adding a new ref...
+	 * */
+
+	return (PyObject *)ret;
+}
+
 void PyObjectPlus::py_base_dealloc(PyObject *self)				// python wrapper
 {
 	PyObjectPlus *self_plus= BGE_PROXY_REF(self);
@@ -104,17 +183,23 @@
 			self_plus->m_proxy = NULL; /* Need this to stop ~PyObjectPlus from decrefing m_proxy otherwise its decref'd twice and py-debug crashes */
 			delete self_plus;
 		}
-		
+
 		BGE_PROXY_REF(self)= NULL; // not really needed
 	}
+
+#if 0
+	/* is ok normally but not for subtyping, use tp_free instead. */
 	PyObject_DEL( self );
+#else
+	Py_TYPE(self)->tp_free(self);
+#endif
 };
 
 PyObjectPlus::PyObjectPlus() : SG_QList()				// constructor
 {
 	m_proxy= NULL;
 };
-  
+
 /*------------------------------
  * PyObjectPlus Methods 	-- Every class, even the abstract one should have a Methods
 ------------------------------*/
@@ -131,22 +216,10 @@
 
 
 PyObject* PyObjectPlus::pyattr_get_invalid(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
-{	
+{
 	return PyBool_FromLong(self_v ? 1:0);
 }
 
-PyObject *PyObjectPlus::py_base_repr(PyObject *self)			// This should be the entry in Type.
-{
-	
-	PyObjectPlus *self_plus= BGE_PROXY_REF(self);
-	if(self_plus==NULL) {
-		PyErr_SetString(PyExc_SystemError, BGE_PROXY_ERROR_MSG);
-		return NULL;
-	}
-	
-	return self_plus->py_repr();  
-}
-
 /* note, this is called as a python 'getset, where the PyAttributeDef is the closure */

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list