diff options
author | Campbell Barton <ideasman42@gmail.com> | 2009-06-29 16:06:46 +0400 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2009-06-29 16:06:46 +0400 |
commit | 9a7ea9664e27679935adaea3093630d7a6a30b5d (patch) | |
tree | 96d706f511e571c490f33a4b1702a48c53033723 /source/gameengine/Expressions | |
parent | 7ca31bb171b2c94f8e9b928990363a42af8716fb (diff) |
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.
Diffstat (limited to 'source/gameengine/Expressions')
-rw-r--r-- | source/gameengine/Expressions/BoolValue.cpp | 1 | ||||
-rw-r--r-- | source/gameengine/Expressions/ListValue.cpp | 10 | ||||
-rw-r--r-- | source/gameengine/Expressions/PyObjectPlus.cpp | 111 | ||||
-rw-r--r-- | source/gameengine/Expressions/PyObjectPlus.h | 4 | ||||
-rw-r--r-- | source/gameengine/Expressions/Value.cpp | 21 |
5 files changed, 108 insertions, 39 deletions
diff --git a/source/gameengine/Expressions/BoolValue.cpp b/source/gameengine/Expressions/BoolValue.cpp index 1102b12fdc4..6779c2ea780 100644 --- a/source/gameengine/Expressions/BoolValue.cpp +++ b/source/gameengine/Expressions/BoolValue.cpp @@ -29,7 +29,6 @@ const STR_String CBoolValue::sTrueString = "TRUE"; const STR_String CBoolValue::sFalseString = "FALSE"; - CBoolValue::CBoolValue() /* pre: false diff --git a/source/gameengine/Expressions/ListValue.cpp b/source/gameengine/Expressions/ListValue.cpp index 34a357aa5f1..38b00dcc8fb 100644 --- a/source/gameengine/Expressions/ListValue.cpp +++ b/source/gameengine/Expressions/ListValue.cpp @@ -225,7 +225,7 @@ static int listvalue_buffer_contains(PyObject *self_v, PyObject *value) 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 @@ PyTypeObject CListValue::Type = { 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[] = { diff --git a/source/gameengine/Expressions/PyObjectPlus.cpp b/source/gameengine/Expressions/PyObjectPlus.cpp index 863390f209d..475953749de 100644 --- a/source/gameengine/Expressions/PyObjectPlus.cpp +++ b/source/gameengine/Expressions/PyObjectPlus.cpp @@ -74,10 +74,7 @@ PyTypeObject PyObjectPlus::Type = { 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 @@ PyObjectPlus::~PyObjectPlus() // 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 @@ void PyObjectPlus::py_base_dealloc(PyObject *self) // python wrapper 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,20 +216,8 @@ PyAttributeDef PyObjectPlus::Attributes[] = { 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(); + return PyBool_FromLong(self_v ? 1:0); } /* note, this is called as a python 'getset, where the PyAttributeDef is the closure */ diff --git a/source/gameengine/Expressions/PyObjectPlus.h b/source/gameengine/Expressions/PyObjectPlus.h index ee69e7d3b07..a18df9d36a9 100644 --- a/source/gameengine/Expressions/PyObjectPlus.h +++ b/source/gameengine/Expressions/PyObjectPlus.h @@ -135,7 +135,7 @@ typedef struct { #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 == PyObjectPlus::py_base_dealloc) +#define BGE_PROXY_CHECK_TYPE(_type) ((_type)->tp_dealloc == PyObjectPlus::py_base_dealloc) // This must be the first line of each @@ -439,6 +439,8 @@ public: * they take the C++ reference from the PyObjectPlus_Proxy and call * its own virtual py_repr, py_base_dealloc ,etc. functions. */ + + static PyObject* py_base_new(PyTypeObject *type, PyObject *args, PyObject *kwds); /* allows subclassing */ static void py_base_dealloc(PyObject *self); static PyObject* py_base_repr(PyObject *self); diff --git a/source/gameengine/Expressions/Value.cpp b/source/gameengine/Expressions/Value.cpp index a9b44495495..d8c81f56f66 100644 --- a/source/gameengine/Expressions/Value.cpp +++ b/source/gameengine/Expressions/Value.cpp @@ -54,15 +54,17 @@ PyTypeObject CValue::Type = { py_base_repr, 0, 0,0,0,0,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, - &PyObjectPlus::Type + &PyObjectPlus::Type, + 0,0,0,0,0,0, + py_base_new }; PyMethodDef CValue::Methods[] = { @@ -613,18 +615,9 @@ CValue* CValue::ConvertPythonToValue(PyObject* pyobj, const char *error_prefix) { vallie = new CStringValue(_PyUnicode_AsString(pyobj),""); } else - if (BGE_PROXY_CHECK_TYPE(pyobj)) /* Note, dont let these get assigned to GameObject props, must check elsewhere */ + if (PyObject_TypeCheck(pyobj, &CValue::Type)) /* Note, dont let these get assigned to GameObject props, must check elsewhere */ { - if (BGE_PROXY_REF(pyobj) && PyObject_TypeCheck((PyObject *)BGE_PROXY_REF(pyobj), &CValue::Type)) - { - vallie = (static_cast<CValue *>(BGE_PROXY_REF(pyobj)))->AddRef(); - } else { - - if(BGE_PROXY_REF(pyobj)) /* this is not a CValue */ - PyErr_Format(PyExc_TypeError, "%sgame engine python type cannot be used as a property", error_prefix); - else /* PyObjectPlus_Proxy has been removed, cant use */ - PyErr_Format(PyExc_SystemError, "%s"BGE_PROXY_ERROR_MSG, error_prefix); - } + vallie = (static_cast<CValue *>(BGE_PROXY_REF(pyobj)))->AddRef(); } else { /* return an error value from the caller */ |