Revision: 19974
          
http://projects.blender.org/plugins/scmsvn/viewcvs.php?view=rev&root=bf-blender&revision=19974
Author:   campbellbarton
Date:     2009-04-29 14:43:09 +0200 (Wed, 29 Apr 2009)

Log Message:
-----------
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.

Modified Paths:
--------------
    trunk/blender/source/blender/makesdna/DNA_controller_types.h
    trunk/blender/source/blender/src/buttons_logic.c
    trunk/blender/source/gameengine/Converter/KX_ConvertControllers.cpp
    trunk/blender/source/gameengine/GameLogic/SCA_PythonController.cpp
    trunk/blender/source/gameengine/GameLogic/SCA_PythonController.h

Modified: trunk/blender/source/blender/makesdna/DNA_controller_types.h
===================================================================
--- trunk/blender/source/blender/makesdna/DNA_controller_types.h        
2009-04-29 11:20:07 UTC (rev 19973)
+++ trunk/blender/source/blender/makesdna/DNA_controller_types.h        
2009-04-29 12:43:09 UTC (rev 19974)
@@ -43,6 +43,9 @@
 
 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 @@
 #define CONT_NEW               4
 #define CONT_MASK              8
 
+/* pyctrl->flag */
+#define CONT_PY_DEBUG  1
+
 #endif
 

Modified: trunk/blender/source/blender/src/buttons_logic.c
===================================================================
--- trunk/blender/source/blender/src/buttons_logic.c    2009-04-29 11:20:07 UTC 
(rev 19973)
+++ trunk/blender/source/blender/src/buttons_logic.c    2009-04-29 12:43:09 UTC 
(rev 19974)
@@ -1578,7 +1578,16 @@
                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;

Modified: trunk/blender/source/gameengine/Converter/KX_ConvertControllers.cpp
===================================================================
--- trunk/blender/source/gameengine/Converter/KX_ConvertControllers.cpp 
2009-04-29 11:20:07 UTC (rev 19973)
+++ trunk/blender/source/gameengine/Converter/KX_ConvertControllers.cpp 
2009-04-29 12:43:09 UTC (rev 19974)
@@ -154,29 +154,38 @@
                        }
                        case CONT_PYTHON:
                        {
-                                       
-                               // we should create a Python controller here
-                                                       
-                               SCA_PythonController* pyctrl = new 
SCA_PythonController(gameobj);
+                               bPythonCont* pycont = (bPythonCont*) 
bcontr->data;
+                               SCA_PythonController* pyctrl = new 
SCA_PythonController(gameobj, pycont->mode);
                                gamecontroller = pyctrl;
-                                       
-                               bPythonCont* pycont = (bPythonCont*) 
bcontr->data;
+                               
                                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 @@
                        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

Modified: trunk/blender/source/gameengine/GameLogic/SCA_PythonController.cpp
===================================================================
--- trunk/blender/source/gameengine/GameLogic/SCA_PythonController.cpp  
2009-04-29 11:20:07 UTC (rev 19973)
+++ trunk/blender/source/gameengine/GameLogic/SCA_PythonController.cpp  
2009-04-29 12:43:09 UTC (rev 19974)
@@ -48,12 +48,17 @@
 
 
 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 @@
 
 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 @@
 {
        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,41 +269,90 @@
        { NULL }        //Sentinel
 };
 
+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 {
+               ErrorPrint("Python error compiling script");
+               return false;
        }
-       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 */
-               
+}
+
+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)
@@ -309,16 +360,18 @@
        m_sCurrentController = this;
        m_sCurrentLogicManager = logicmgr;
        
-       if (m_bModified)
+       PyObject *excdict=              NULL;
+       PyObject* resultobj=    NULL;
+       
+       switch(m_mode) {
+       case SCA_PYEXEC_SCRIPT:
        {
-               if (Compile()==false) // sets m_bModified to false
+               if (m_bModified)
+                       if (Compile()==false) // sets m_bModified to false
+                               return;
+               if (!m_bytecode)
                        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 @@
                 * 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);

@@ Diff output truncated at 10240 characters. @@

_______________________________________________
Bf-blender-cvs mailing list
[email protected]
http://lists.blender.org/mailman/listinfo/bf-blender-cvs

Reply via email to