Python
// CPython.cpp
- include "kVersion.h"
- if kUsePython
#if __WIN32__ #include "python.h" #else #include <Python/Python.h> #endif
- endif
- include "stdafx.h"
- include "CApp.h"
- include "CPython.h"
bool CPython_UsePython() { return kUsePython; }
- if kUsePython
- include "CTaskMgr.h"
- define kEmbeddedModuleName "kjams"
- define kCustomPrint "custom_print"
- define kCustomErr "custom_err"
- define kCommand "do_command"
- define kStringCommand "do_command_str"
- define kDoMenuCommand "do_menu_command"
- define kDoMenuName "do_menu_name"
- define kStartupScriptName "startup.py"
- define kUserAbortStr "User Abort"
/**********************************************/ class ScEnsureGIL { PyGILState_STATE i_state;
public: ScEnsureGIL() : i_state(PyGILState_Ensure()) { }
~ScEnsureGIL() { PyGILState_Release(i_state); } };
class ScReleaseGIL {
public: PyThreadState *i_stateP;
ScReleaseGIL() : i_stateP(PyEval_SaveThread()) { }
~ScReleaseGIL() { PyEval_RestoreThread(i_stateP); } };
class ScPyObject { PyObject *i_objP;
public: ScPyObject(PyObject *objP) : i_objP(objP) {} ~ScPyObject() { Py_DECREF(i_objP); }
operator PyObject*() { return i_objP; } };
/**********************************************/ class CPython; class CT_RunScript;
typedef std::vector<CT_RunScript *> ScriptVec;
/* this thread runs in the background, serving as the "main event loop" for all python scripts it runs an "idle" on each script every 1/4 second to look for user-aborted threads, and if found, causes an exception to be thrown within that thread
- /
class CPython_RunLoop : public CT_Preemptive { public: CPython *thiz; CMutex_bool i_abortB; CMutexT<ScriptVec *> i_scriptVecP;
CPython_RunLoop(CPython *in_thiz);
virtual OSStatus operator()(OSStatus err); void RunScript(const char *unf8NameZ, const char *utf8ScriptZ);
void AddScript(CT_RunScript *scriptP); void RemoveScript(CT_RunScript *scriptP); void IdleScripts(); size_t CountScripts(); };
/* this is the main Python object the app uses to
communicate with the python runloop above
- /
class CPython { friend class CPython_RunLoop; static CPython *s_pythonP; CMutexT<bool> i_inittedB; std::string i_appName; CMutexT<CPython_RunLoop*> i_runLoopP;
public: CPython(const char *appNameZ); ~CPython();
static CPython* Get(const char *appNameZ = NULL);
/****************************************************/ void Startup(); void Test(); };
//static CPython* CPython::s_pythonP = NULL;
/*********************************************************************/ struct ThisRec { CT_RunScript *i_scriptP; ThisRec(CT_RunScript *scriptP) : i_scriptP(scriptP) {} };
boost::thread_specific_ptr<ThisRec> g_threadP;
/* this is a script-running thread
if any "error" statements are "printed", they are gathered up into a single string and presented to the user as a dialog */
class CT_RunScript : public CT_Preemptive { public: SuperString i_name; SuperString i_script; CPython_RunLoop *i_runLoopP; long i_thread_id;
class CShowErrorTimer; CMutexT<CShowErrorTimer *> i_errTimerP;
/***************************** this gathers all errors printed out: if it gets some error text, it waits a half second. if more error prints come in within that time, they are appended when the timer expires, all the error messages gathered are shown to the user in a dialog */ class CShowErrorTimer : public CT_Timer { friend class CT_RunScript; SuperString i_errStr; CMutex_bool i_abortB; CT_RunScript *i_scriptP; bool i_doneB;
public: CShowErrorTimer(CT_RunScript *scriptP) : i_doneB(false), i_scriptP(scriptP), CT_Timer(NO_KILL "CShowErrorTimer", kEventDurationSecond / 2) { call(); }
~CShowErrorTimer() { i_scriptP->i_errTimerP.Set(NULL); }
virtual OSStatus operator()() { CCritical sc(&i_scriptP->i_errTimerP);
i_doneB = true; if (!i_errStr.Contains(kUserAbortStr)) {
if (i_errStr.GetIndCharR() == '\r') { i_errStr.pop_back(); }
i_errStr.Replace("<string>", i_scriptP->i_name); PostAlert("Python Error:", i_errStr.utf8Z()); }
return threadTimerTerminate; }
void append(const char *utf8Z) {
if (!i_doneB) { i_errStr.append(utf8Z); prime(); } } }; /*****************************/
CPW_TaskRec *i_taskRecP; CPW_ProgData i_progData;
CT_RunScript( CPython_RunLoop *runLoopP, const SuperString& name, const SuperString& script ) : i_runLoopP(runLoopP), i_name(name), i_script(script) { SuperString verb1("Python: ");
verb1.append(name); i_taskRecP = gApp->NewTask(verb1.ref(), NULL); i_runLoopP->AddScript(this); call(NO_KILL "CPython::CT_RunScript"); }
~CT_RunScript() { CF_ASSERT(i_errTimerP.Get() == NULL); i_taskRecP->Delete(); i_runLoopP->RemoveScript(this); }
// called from CPython_RunLoop thread, NOT from "this" thread void Idle() { OSStatus err = noErr;
ERR(i_runLoopP->i_abortB.Get()); ERR(i_taskRecP->MT_UpdateData(&i_progData));
// an error here means the user has aborted the script if (err) { err = noErr;
ERR_XTE_START { ScEnsureGIL sc; ScPyObject exceptionP(PyString_FromString(kUserAbortStr)); int countI(PyThreadState_SetAsyncExc(i_thread_id, exceptionP));
// during shut down it is reasonable that countI may be 0 // but it should never be greater than 1 CF_ASSERT(countI == 0 || countI == 1); } ERR_XTE_END;
if (err) { ReportErr("Python: Exception when attempting to kill thread", err); } } }
virtual OSStatus operator()(OSStatus err) { SetThis(this);
{ ScEnsureGIL sc;
// gather my thread ID so i can be killed later if // the user hits cancel on my thread i_thread_id = PyThreadState_Get()->thread_id; ERR(PyRun_SimpleString(i_script.utf8Z())); }
// now wait until errors have already been shown, if any while (i_errTimerP.Get()) { IdleDuration(0.1f, kDurationForever_Idle); }
return err; }
/***************************/ // extensions to python for use within scripts static CT_RunScript* GetThis() { CT_RunScript *thiz = NULL; ThisRec *thisRecP = g_threadP.get();
if (thisRecP) { thiz = thisRecP->i_scriptP; }
return thiz; }
static void SetThis(CT_RunScript *scriptP) { g_threadP.reset(new ThisRec(scriptP)); }
static PyObject* emb_print_err(PyObject *self, PyObject *args) { return GetThis()->print_err(args); }
PyObject* print_err(PyObject *args) { PyObject *resultObjP = NULL; const char *utf8_strZ = NULL; static SuperString s_err_str;
if (PyArg_ParseTuple(args, "s", &utf8_strZ)) { CCritical sc(&i_errTimerP);
if (i_errTimerP.Get() == NULL) { i_errTimerP.Set(new CShowErrorTimer(this)); }
i_errTimerP.Get()->append(utf8_strZ);
resultObjP = Py_None; Py_INCREF(resultObjP); }
return resultObjP; }
/***************************/ static PyObject* emb_print(PyObject *self, PyObject *args) { return GetThis()->print(args); }
PyObject* print(PyObject *args) { PyObject *resultObjP = NULL; const char *utf8_strZ = NULL;
if (PyArg_ParseTuple(args, "s", &utf8_strZ)) { Log(utf8_strZ, false);
resultObjP = Py_None; Py_INCREF(resultObjP); }
return resultObjP; }
/***************************/ static PyObject* emb_do_command(PyObject *self, PyObject *args) { return GetThis()->do_command(args); }
PyObject* do_command(PyObject *args) { PyObject *resultObjP = NULL; int commandID = kScriptCommand_NONE;
if (PyArg_ParseTuple(args, "i", &commandID)) { double resultF = Scripting_Command((SInt32)commandID);
resultObjP = PyFloat_FromDouble(resultF); }
return resultObjP; }
/***************************/ static PyObject* emb_do_command_str(PyObject *self, PyObject *args) { return GetThis()->do_command_str(args); }
PyObject* do_command_str(PyObject *args) { PyObject *resultObjP = NULL; int commandID = kScriptCommand_NONE;
if (PyArg_ParseTuple(args, "i", &commandID)) { SuperString resultStr(Scripting_CommandStr((SInt32)commandID), true);
resultObjP = PyString_FromString(resultStr.utf8Z()); }
return resultObjP; }
/***************************/ static PyObject* emb_do_menu_command(PyObject *self, PyObject *args) { return GetThis()->do_menu_command(args); }
PyObject* do_menu_command(PyObject *args) { PyObject *resultObjP = NULL; short menuI = 0; short menu_itemI = 0; short sub_menu_itemI = 0;
if (PyArg_ParseTuple(args, "hh|h", &menuI, &menu_itemI, &sub_menu_itemI)) { SInt16Vec intVec;
intVec.push_back(menuI); intVec.push_back(menu_itemI);
if (sub_menu_itemI) { intVec.push_back(sub_menu_itemI); }
DoMenuCommand(intVec);
resultObjP = Py_None; Py_INCREF(resultObjP); }
return resultObjP; }
/***************************/ static PyObject* emb_do_menu_name(PyObject *self, PyObject *args) { return GetThis()->do_menu_name(args); }
PyObject* do_menu_name(PyObject *args) { PyObject *resultObjP = NULL; const char *menuZ = NULL; const char *menu_itemZ = NULL; const char *sub_menu_itemZ = NULL;
if (PyArg_ParseTuple(args, "ss|s", &menuZ, &menu_itemZ, &sub_menu_itemZ)) { SStringVec stringVec;
stringVec.push_back(menuZ); stringVec.push_back(menu_itemZ);
if (sub_menu_itemZ) { stringVec.push_back(sub_menu_itemZ); }
DoMenuCommand(stringVec);
resultObjP = Py_None; Py_INCREF(resultObjP); }
return resultObjP; } };
/*****************************************************/
static const PyMethodDef EmbMethods[] = {
{kCustomPrint, CT_RunScript::emb_print, METH_VARARGS, "Calls custom print function."},
{kCustomErr, CT_RunScript::emb_print_err, METH_VARARGS, "Calls custom error function."},
{kCommand, CT_RunScript::emb_do_command, METH_VARARGS, "calls scripting command (float result)."},
{kStringCommand, CT_RunScript::emb_do_command_str, METH_VARARGS, "calls scripting command (string result)."},
{kDoMenuCommand, CT_RunScript::emb_do_menu_command, METH_VARARGS, "calls menu command by index"},
{kDoMenuName, CT_RunScript::emb_do_menu_name, METH_VARARGS, "calls menu command by name"},
{NULL, NULL, 0, NULL}
};
static const char *s_RedirectPrint = "import " kEmbeddedModuleName "\n" "import sys\n" "\n" "class CustomPrintClass:\n" " def write(self, stuff):\n" " " kEmbeddedModuleName "." kCustomPrint "(stuff)\n" "class CustomErrClass:\n" " def write(self, stuff):\n" " " kEmbeddedModuleName "." kCustomErr "(stuff)\n" "sys.stdout = CustomPrintClass()\n" "sys.stderr = CustomErrClass()\n";
static const char *s_PrintTime = "import time\n" "print 'Today is', time.ctime(time.time())\n";
static const char *s_AllowPendingCalls = "pass\n";
/*****************************************************************/ CPython_RunLoop::CPython_RunLoop(CPython *in_thiz) : thiz(in_thiz) { i_scriptVecP.Set(new ScriptVec()); call(NO_KILL "CPython_RunLoop"); }
OSStatus CPython_RunLoop::operator()(OSStatus err) { XTE_START { bool continueB = true;
#if OPT_WINOS { // don't crash if python is not installed, simply bail HMODULE pythonH = LoadLibrary(L"pywintypes27");
continueB = pythonH != NULL; } #endif
if (continueB) { Log("Python: about to set program name"); Py_SetProgramName(const_cast<char *>(thiz->i_appName.c_str()));
Log("Python: about to init"); Py_Initialize();
{ Log("Python: about to create " kEmbeddedModuleName " module");
PyObject *myModuleP = Py_InitModule( kEmbeddedModuleName, const_cast<PyMethodDef*>(EmbMethods)); ETX(myModuleP == NULL);
// the owner of myModuleP is now the python interpreter // it will auto-decref during Py_Finalize() }
// redirect stdout and stderr to my own logging functions Log("Python: about to run log redirect script"); ETX(PyRun_SimpleString(s_RedirectPrint)); PyEval_InitThreads(); thiz->i_inittedB.Set(true); } } XTE_END;
LogYesOrNo("Python Initted", thiz->i_inittedB.Get());
if (thiz->i_inittedB.Get()) { bool abortB = false; bool doneB = false;
{ ScReleaseGIL sc;
/* run the "loop" that will handle killing of any scripts canceled by the user */
do { // IdleScripts will kill any scripts that the user has hit cancel on IdleScripts();
// if you want to use Py_AddPendingCall() to send a message to THIS // thread, then you'd need to uncomment this line #if 0 if (PyRun_SimpleString(s_AllowPendingCalls) != 0) { PostAlert("Python: s_AllowPendingCalls failed"); i_abortB.Set(); } #endif
if (!abortB) { // this gets set when quitting the app abortB = i_abortB.Get(); }
if (abortB) { doneB = CountScripts() == 0; }
if (!doneB) { IdleDuration(kQuarterSecond); } } while (!doneB); }
Py_Finalize(); thiz->i_inittedB.Set(false); }
thiz->i_runLoopP.Set(NULL);
{ ScriptVec *vecP = i_scriptVecP.Get();
CF_ASSERT(vecP); CF_ASSERT(vecP->empty()); delete vecP; }
i_scriptVecP.Set(NULL);
return err; }
void CPython_RunLoop::RunScript(const char *unf8NameZ, const char *utf8ScriptZ) { new CT_RunScript(this, unf8NameZ, utf8ScriptZ); }
void CPython_RunLoop::AddScript(CT_RunScript *scriptP) { CCritical sc(&i_scriptVecP);
i_scriptVecP.Get()->push_back(scriptP); }
void CPython_RunLoop::RemoveScript(CT_RunScript *scriptP) { CCritical sc(&i_scriptVecP); ScriptVec& scriptVec(*i_scriptVecP.Get()); ScriptVec::iterator it(std::find(scriptVec.begin(), scriptVec.end(), scriptP));
CF_ASSERT(it != scriptVec.end()); if (it != scriptVec.end()) { scriptVec.erase(it); } }
void CPython_RunLoop::IdleScripts() { ScriptVec iter_scriptVec;
{ CCritical sc(&i_scriptVecP); ScriptVec& orig_scriptVec(*i_scriptVecP.Get());
// make a copy to iterate over, cuz during the iterate // we may actually delete the current script iter_scriptVec = orig_scriptVec; }
BOOST_FOREACH(CT_RunScript *scriptP, iter_scriptVec) { { CCritical sc(&i_scriptVecP); ScriptVec& scriptVec(*i_scriptVecP.Get()); ScriptVec::iterator it(std::find(scriptVec.begin(), scriptVec.end(), scriptP));
// we still have to check to see if this script still exists before calling it if (it != scriptVec.end()) { scriptP->Idle(); } } } }
size_t CPython_RunLoop::CountScripts() { CCritical sc(&i_scriptVecP);
return i_scriptVecP.Get()->size(); }
/**************************************************************/ CPython::CPython(const char *appNameZ) : i_appName(appNameZ) { i_runLoopP.Set(new CPython_RunLoop(this)); }
CPython::~CPython() { s_pythonP = NULL;
{ CCritical sc(&i_runLoopP); CPython_RunLoop *runLoopP(i_runLoopP.Get());
if (runLoopP) { runLoopP->i_abortB.Set(true); } }
while (i_runLoopP.Get()) { IdleDuration(0.1f, kDurationForever_Idle); } }
// static CPython* CPython::Get(const char *appNameZ) { if (s_pythonP == NULL) { CF_ASSERT(appNameZ); s_pythonP = new CPython(appNameZ); }
return s_pythonP; }
/****************************************************/ void CPython::Startup() { if (i_inittedB.Get()) XTE_START { CharVec charVec; CFileRef pythonRef(kFolder_KJAMS);
ETX(pythonRef.Descend("Python/" kStartupScriptName)); pythonRef.Load(&charVec); charVec.push_back(0); i_runLoopP.Get()->RunScript(kStartupScriptName, &charVec[0]); } XTE_END; }
void CPython::Test() { if (i_inittedB.Get()) { i_runLoopP.Get()->RunScript("print_time.py", s_PrintTime); } } /*****************************************************************/
- endif // kUsePython
// the entire public interface is here // called on startup to init OSStatus CPython_PreAlloc(const char *utf8Z) { OSStatus err = noErr;
#if kUsePython if (CPython::Get(utf8Z) == NULL) { ERR(tsmUnsupScriptLanguageErr); } #endif
return err; }
// called on shutdown void CPython_PostDispose() { #if kUsePython CPython *pyP(CPython::Get());
if (pyP) { delete pyP; } #endif }
// very simple unit test void CPython_Test() { #if kUsePython CPython *pyP(CPython::Get());
if (pyP) { pyP->Test(); } #endif }
// called when startup is complete void CPython_Startup() { #if kUsePython CPython *pyP(CPython::Get());
if (pyP) { pyP->Startup(); } #endif }