BGE alternative run mode for python controllers.
Option to run a function in a module rather then a script from a python controller, this has a number of advantages. - No allocating and freeing the namespace dictionary for every time its triggered (hard to measure the overhead here, but in a test with calling 42240 scripts a second each defining 200 vars, using modules was ~25% faster) - Ability to use external python scripts for game logic. - Convenient debug option that lets you edit scripts while the game engine runs.
This commit is contained in:
@@ -43,6 +43,9 @@ typedef struct bExpressionCont {
|
||||
|
||||
typedef struct bPythonCont {
|
||||
struct Text *text;
|
||||
char module[64];
|
||||
int mode;
|
||||
int flag; /* only used for debug now */
|
||||
} bPythonCont;
|
||||
|
||||
typedef struct bController {
|
||||
@@ -77,5 +80,8 @@ typedef struct bController {
|
||||
#define CONT_NEW 4
|
||||
#define CONT_MASK 8
|
||||
|
||||
/* pyctrl->flag */
|
||||
#define CONT_PY_DEBUG 1
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1578,7 +1578,16 @@ static short draw_controllerbuttons(bController *cont, uiBlock *block, short xco
|
||||
glRects(xco, yco-ysize, xco+width, yco);
|
||||
uiEmboss((float)xco, (float)yco-ysize, (float)xco+width, (float)yco, 1);
|
||||
|
||||
uiDefIDPoinBut(block, test_scriptpoin_but, ID_SCRIPT, 1, "Script: ", xco+45,yco-24,width-90, 19, &pc->text, "");
|
||||
|
||||
uiBlockBeginAlign(block);
|
||||
uiDefButI(block, MENU, B_REDR, "Execution Method%t|Script%x0|Module%x1", xco+24,yco-24, 66, 19, &pc->mode, 0, 0, 0, 0, "Python script type (textblock or module)");
|
||||
if(pc->mode==0)
|
||||
uiDefIDPoinBut(block, test_scriptpoin_but, ID_SCRIPT, 1, "", xco+90,yco-24,width-90, 19, &pc->text, "Blender textblock to run as a script");
|
||||
else {
|
||||
uiDefBut(block, TEX, 1, "", xco+90,yco-24,(width-90)-25, 19, pc->module, 0, 63, 0, 0, "Module name and function to run eg \"someModule.main\"");
|
||||
uiDefButBitI(block, TOG, CONT_PY_DEBUG, B_REDR, "D", (xco+width)-25, yco-24, 19, 19, &pc->flag, 0, 0, 0, 0, "Continuously reload the module from disk for editing external modules without restrting, (slow)");
|
||||
}
|
||||
uiBlockEndAlign(block);
|
||||
|
||||
yco-= ysize;
|
||||
break;
|
||||
|
||||
@@ -154,29 +154,38 @@ void BL_ConvertControllers(
|
||||
}
|
||||
case CONT_PYTHON:
|
||||
{
|
||||
|
||||
// we should create a Python controller here
|
||||
|
||||
SCA_PythonController* pyctrl = new SCA_PythonController(gameobj);
|
||||
gamecontroller = pyctrl;
|
||||
|
||||
bPythonCont* pycont = (bPythonCont*) bcontr->data;
|
||||
SCA_PythonController* pyctrl = new SCA_PythonController(gameobj, pycont->mode);
|
||||
gamecontroller = pyctrl;
|
||||
|
||||
pyctrl->SetDictionary(pythondictionary);
|
||||
|
||||
if (pycont->text)
|
||||
{
|
||||
char *buf;
|
||||
// this is some blender specific code
|
||||
buf= txt_to_buf(pycont->text);
|
||||
if (buf)
|
||||
|
||||
if(pycont->mode==SCA_PythonController::SCA_PYEXEC_SCRIPT) {
|
||||
if (pycont->text)
|
||||
{
|
||||
pyctrl->SetScriptText(STR_String(buf));
|
||||
pyctrl->SetScriptName(pycont->text->id.name+2);
|
||||
MEM_freeN(buf);
|
||||
char *buf;
|
||||
// this is some blender specific code
|
||||
buf= txt_to_buf(pycont->text);
|
||||
if (buf)
|
||||
{
|
||||
pyctrl->SetScriptText(STR_String(buf));
|
||||
pyctrl->SetScriptName(pycont->text->id.name+2);
|
||||
MEM_freeN(buf);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
/* let the controller print any warnings here when importing */
|
||||
pyctrl->SetScriptText(STR_String(pycont->module));
|
||||
pyctrl->SetScriptName(pycont->module); /* will be something like module.func so using it as the name is OK */
|
||||
}
|
||||
|
||||
if(pycont->flag & CONT_PY_DEBUG) {
|
||||
printf("\nDebuging \"%s\", module for object %s\n\texpect worse performance.\n", pycont->module, blenderobject->id.name+2);
|
||||
pyctrl->SetDebug(true);
|
||||
}
|
||||
|
||||
LinkControllerToActuators(gamecontroller,bcontr,logicmgr,converter);
|
||||
break;
|
||||
}
|
||||
@@ -202,9 +211,13 @@ void BL_ConvertControllers(
|
||||
converter->RegisterGameController(gamecontroller, bcontr);
|
||||
|
||||
if (bcontr->type==CONT_PYTHON) {
|
||||
SCA_PythonController *pyctrl= static_cast<SCA_PythonController*>(gamecontroller);
|
||||
/* not strictly needed but gives syntax errors early on and
|
||||
* gives more pradictable performance for larger scripts */
|
||||
(static_cast<SCA_PythonController*>(gamecontroller))->Compile();
|
||||
if(pyctrl->m_mode==SCA_PythonController::SCA_PYEXEC_SCRIPT)
|
||||
pyctrl->Compile();
|
||||
else
|
||||
pyctrl->Import();
|
||||
}
|
||||
|
||||
//done with gamecontroller
|
||||
|
||||
@@ -48,12 +48,17 @@ SCA_PythonController* SCA_PythonController::m_sCurrentController = NULL;
|
||||
|
||||
|
||||
SCA_PythonController::SCA_PythonController(SCA_IObject* gameobj,
|
||||
int mode,
|
||||
PyTypeObject* T)
|
||||
: SCA_IController(gameobj, T),
|
||||
m_bytecode(NULL),
|
||||
m_function(NULL),
|
||||
m_bModified(true),
|
||||
m_debug(false),
|
||||
m_mode(mode),
|
||||
m_pythondictionary(NULL)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -74,15 +79,12 @@ int SCA_PythonController::Release()
|
||||
|
||||
SCA_PythonController::~SCA_PythonController()
|
||||
{
|
||||
if (m_bytecode)
|
||||
{
|
||||
//
|
||||
//printf("released python byte script\n");
|
||||
Py_DECREF(m_bytecode);
|
||||
}
|
||||
//printf("released python byte script\n");
|
||||
|
||||
if (m_pythondictionary)
|
||||
{
|
||||
Py_XDECREF(m_bytecode);
|
||||
Py_XDECREF(m_function);
|
||||
|
||||
if (m_pythondictionary) {
|
||||
// break any circular references in the dictionary
|
||||
PyDict_Clear(m_pythondictionary);
|
||||
Py_DECREF(m_pythondictionary);
|
||||
@@ -95,7 +97,7 @@ CValue* SCA_PythonController::GetReplica()
|
||||
{
|
||||
SCA_PythonController* replica = new SCA_PythonController(*this);
|
||||
// Copy the compiled bytecode if possible.
|
||||
Py_XINCREF(replica->m_bytecode);
|
||||
Py_XINCREF(replica->m_function); // this is ok since its not set to NULL
|
||||
replica->m_bModified = replica->m_bytecode == NULL;
|
||||
|
||||
// The replica->m_pythondictionary is stolen - replace with a copy.
|
||||
@@ -267,58 +269,109 @@ PyAttributeDef SCA_PythonController::Attributes[] = {
|
||||
{ NULL } //Sentinel
|
||||
};
|
||||
|
||||
bool SCA_PythonController::Compile()
|
||||
void SCA_PythonController::ErrorPrint(const char *error_msg)
|
||||
{
|
||||
// didn't compile, so instead of compile, complain
|
||||
// something is wrong, tell the user what went wrong
|
||||
printf("%s - controller \"%s\":\n", error_msg, GetName().Ptr());
|
||||
//PyRun_SimpleString(m_scriptText.Ptr());
|
||||
PyErr_Print();
|
||||
|
||||
/* Added in 2.48a, the last_traceback can reference Objects for example, increasing
|
||||
* their user count. Not to mention holding references to wrapped data.
|
||||
* This is especially bad when the PyObject for the wrapped data is free'd, after blender
|
||||
* has alredy dealocated the pointer */
|
||||
PySys_SetObject( (char *)"last_traceback", NULL);
|
||||
PyErr_Clear(); /* just to be sure */
|
||||
}
|
||||
|
||||
bool SCA_PythonController::Compile()
|
||||
{
|
||||
//printf("py script modified '%s'\n", m_scriptName.Ptr());
|
||||
m_bModified= false;
|
||||
|
||||
// if a script already exists, decref it before replace the pointer to a new script
|
||||
if (m_bytecode)
|
||||
{
|
||||
if (m_bytecode) {
|
||||
Py_DECREF(m_bytecode);
|
||||
m_bytecode=NULL;
|
||||
}
|
||||
|
||||
// recompile the scripttext into bytecode
|
||||
m_bytecode = Py_CompileString(m_scriptText.Ptr(), m_scriptName.Ptr(), Py_file_input);
|
||||
m_bModified=false;
|
||||
|
||||
if (m_bytecode)
|
||||
{
|
||||
|
||||
if (m_bytecode) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// didn't compile, so instead of compile, complain
|
||||
// something is wrong, tell the user what went wrong
|
||||
printf("Python compile error from controller \"%s\": \n", GetName().Ptr());
|
||||
//PyRun_SimpleString(m_scriptText.Ptr());
|
||||
PyErr_Print();
|
||||
|
||||
/* Added in 2.48a, the last_traceback can reference Objects for example, increasing
|
||||
* their user count. Not to mention holding references to wrapped data.
|
||||
* This is especially bad when the PyObject for the wrapped data is free'd, after blender
|
||||
* has alredy dealocated the pointer */
|
||||
PySys_SetObject( (char *)"last_traceback", NULL);
|
||||
PyErr_Clear(); /* just to be sure */
|
||||
|
||||
} else {
|
||||
ErrorPrint("Python error compiling script");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SCA_PythonController::Import()
|
||||
{
|
||||
//printf("py module modified '%s'\n", m_scriptName.Ptr());
|
||||
m_bModified= false;
|
||||
|
||||
/* incase we re-import */
|
||||
Py_XDECREF(m_function);
|
||||
m_function= NULL;
|
||||
|
||||
vector<STR_String> module_func = m_scriptText.Explode('.');
|
||||
|
||||
if(module_func.size() != 2 || module_func[0].Length()==0 || module_func[1].Length()==0) {
|
||||
printf("Python module name formatting error \"%s\":\n\texpected \"SomeModule.Func\", got \"%s\"", GetName().Ptr(), m_scriptText.Ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
PyObject *mod = PyImport_ImportModule(module_func[0]);
|
||||
if(mod && m_debug) {
|
||||
Py_DECREF(mod); /* getting a new one so dont hold a ref to the old one */
|
||||
mod= PyImport_ReloadModule(mod);
|
||||
}
|
||||
|
||||
if(mod==NULL) {
|
||||
ErrorPrint("Python module not found");
|
||||
return false;
|
||||
}
|
||||
Py_DECREF(mod); /* will be added to sys.modules so no need to keep a ref */
|
||||
|
||||
|
||||
PyObject *dict= PyModule_GetDict(mod);
|
||||
m_function= PyDict_GetItemString(dict, module_func[1]); /* borrow */
|
||||
|
||||
if(m_function==NULL) {
|
||||
printf("Python module error \"%s\":\n \"%s\" module fount but function missing\n", GetName().Ptr(), m_scriptText.Ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!PyCallable_Check(m_function)) {
|
||||
printf("Python module function error \"%s\":\n \"%s\" not callable", GetName().Ptr(), m_scriptText.Ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
Py_INCREF(m_function);
|
||||
Py_INCREF(mod);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SCA_PythonController::Trigger(SCA_LogicManager* logicmgr)
|
||||
{
|
||||
m_sCurrentController = this;
|
||||
m_sCurrentLogicManager = logicmgr;
|
||||
|
||||
if (m_bModified)
|
||||
{
|
||||
if (Compile()==false) // sets m_bModified to false
|
||||
return;
|
||||
}
|
||||
if (!m_bytecode) {
|
||||
return;
|
||||
}
|
||||
|
||||
PyObject *excdict= NULL;
|
||||
PyObject* resultobj= NULL;
|
||||
|
||||
switch(m_mode) {
|
||||
case SCA_PYEXEC_SCRIPT:
|
||||
{
|
||||
if (m_bModified)
|
||||
if (Compile()==false) // sets m_bModified to false
|
||||
return;
|
||||
if (!m_bytecode)
|
||||
return;
|
||||
|
||||
/*
|
||||
* This part here with excdict is a temporary patch
|
||||
* to avoid python/gameengine crashes when python
|
||||
@@ -337,10 +390,28 @@ void SCA_PythonController::Trigger(SCA_LogicManager* logicmgr)
|
||||
* should always ensure excdict is cleared).
|
||||
*/
|
||||
|
||||
PyObject *excdict= PyDict_Copy(m_pythondictionary);
|
||||
PyObject* resultobj = PyEval_EvalCode((PyCodeObject*)m_bytecode,
|
||||
excdict, excdict);
|
||||
|
||||
excdict= PyDict_Copy(m_pythondictionary);
|
||||
resultobj = PyEval_EvalCode((PyCodeObject*)m_bytecode, excdict, excdict);
|
||||
/* PyRun_SimpleString(m_scriptText.Ptr()); */
|
||||
break;
|
||||
}
|
||||
case SCA_PYEXEC_MODULE:
|
||||
{
|
||||
if (m_bModified || m_debug)
|
||||
if (Import()==false) // sets m_bModified to false
|
||||
return;
|
||||
if (!m_function)
|
||||
return;
|
||||
|
||||
resultobj = PyObject_CallObject(m_function, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
} /* end switch */
|
||||
|
||||
|
||||
|
||||
/* Free the return value and print the error */
|
||||
if (resultobj)
|
||||
{
|
||||
Py_DECREF(resultobj);
|
||||
@@ -357,14 +428,16 @@ void SCA_PythonController::Trigger(SCA_LogicManager* logicmgr)
|
||||
* has alredy dealocated the pointer */
|
||||
PySys_SetObject( (char *)"last_traceback", NULL);
|
||||
PyErr_Clear(); /* just to be sure */
|
||||
|
||||
//PyRun_SimpleString(m_scriptText.Ptr());
|
||||
}
|
||||
|
||||
// clear after PyErrPrint - seems it can be using
|
||||
// something in this dictionary and crash?
|
||||
PyDict_Clear(excdict);
|
||||
Py_DECREF(excdict);
|
||||
|
||||
if(excdict) /* Only for SCA_PYEXEC_SCRIPT types */
|
||||
{
|
||||
/* clear after PyErrPrint - seems it can be using
|
||||
* something in this dictionary and crash? */
|
||||
PyDict_Clear(excdict);
|
||||
Py_DECREF(excdict);
|
||||
}
|
||||
|
||||
m_triggeredSensors.erase(m_triggeredSensors.begin(), m_triggeredSensors.end());
|
||||
m_sCurrentController = NULL;
|
||||
}
|
||||
|
||||
@@ -42,23 +42,35 @@ class SCA_IObject;
|
||||
class SCA_PythonController : public SCA_IController
|
||||
{
|
||||
Py_Header;
|
||||
struct _object * m_bytecode;
|
||||
struct _object * m_bytecode; /* SCA_PYEXEC_SCRIPT only */
|
||||
PyObject* m_function; /* SCA_PYEXEC_MODULE only */
|
||||
bool m_bModified;
|
||||
|
||||
bool m_debug; /* use with SCA_PYEXEC_MODULE for reloading every logic run */
|
||||
int m_mode;
|
||||
|
||||
protected:
|
||||
STR_String m_scriptText;
|
||||
STR_String m_scriptName;
|
||||
PyObject* m_pythondictionary;
|
||||
PyObject* m_pythondictionary; /* for SCA_PYEXEC_SCRIPT only */
|
||||
PyObject* m_pythonfunction; /* for SCA_PYEXEC_MODULE only */
|
||||
|
||||
std::vector<class SCA_ISensor*> m_triggeredSensors;
|
||||
|
||||
public:
|
||||
enum SCA_PyExecMode
|
||||
{
|
||||
SCA_PYEXEC_SCRIPT = 0,
|
||||
SCA_PYEXEC_MODULE,
|
||||
SCA_PYEXEC_MAX
|
||||
};
|
||||
|
||||
public:
|
||||
static SCA_PythonController* m_sCurrentController; // protected !!!
|
||||
|
||||
//for debugging
|
||||
//virtual CValue* AddRef();
|
||||
//virtual int Release(); // Release a reference to this value (when reference count reaches 0, the value is removed from the heap)
|
||||
|
||||
SCA_PythonController(SCA_IObject* gameobj,PyTypeObject* T = &Type);
|
||||
SCA_PythonController(SCA_IObject* gameobj, int mode, PyTypeObject* T = &Type);
|
||||
virtual ~SCA_PythonController();
|
||||
|
||||
virtual CValue* GetReplica();
|
||||
@@ -67,10 +79,14 @@ class SCA_PythonController : public SCA_IController
|
||||
void SetScriptText(const STR_String& text);
|
||||
void SetScriptName(const STR_String& name);
|
||||
void SetDictionary(PyObject* pythondictionary);
|
||||
void SetDebug(bool debug) { m_debug = debug; }
|
||||
void AddTriggeredSensor(class SCA_ISensor* sensor)
|
||||
{ m_triggeredSensors.push_back(sensor); }
|
||||
int IsTriggered(class SCA_ISensor* sensor);
|
||||
bool Compile();
|
||||
bool Import();
|
||||
void ErrorPrint(const char *error_msg);
|
||||
|
||||
|
||||
static const char* sPyGetCurrentController__doc__;
|
||||
static PyObject* sPyGetCurrentController(PyObject* self);
|
||||
|
||||
Reference in New Issue
Block a user