Python: Difference between revisions
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
// CPython.cpp | // CPython.cpp | ||
#include "kVersion.h" | |||
#if kUsePython | |||
#if __WIN32__ | |||
#include "python.h" | |||
#else | |||
#include <Python/Python.h> | |||
#endif | |||
#endif | |||
#include "stdafx.h" | #include "stdafx.h" | ||
#include " | #include "CApp.h" | ||
#include "CPython.h" | #include "CPython.h" | ||
#define kEmbeddedModuleName " | bool CPython_UsePython() | ||
#define | { | ||
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; | |||
const char * | |||
typedef std::vector<CT_RunScript *> ScriptVec; | |||
if ( | |||
/* | |||
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)); | |||
} | } | ||
return resultObjP; | 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[] = { | 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} | {NULL, NULL, 0, NULL} | ||
}; | }; | ||
| Line 36: | Line 469: | ||
"class CustomPrintClass:\n" | "class CustomPrintClass:\n" | ||
" def write(self, stuff):\n" | " def write(self, stuff):\n" | ||
" " kEmbeddedModuleName "." | " " kEmbeddedModuleName "." kCustomPrint "(stuff)\n" | ||
"\n" | "class CustomErrClass:\n" | ||
" def write(self, stuff):\n" | |||
" " kEmbeddedModuleName "." kCustomErr "(stuff)\n" | |||
"sys.stdout = CustomPrintClass()\n" | "sys.stdout = CustomPrintClass()\n" | ||
"sys.stderr = | "sys.stderr = CustomErrClass()\n"; | ||
"\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; | return err; | ||
} | } | ||
// called on shutdown | |||
void CPython_PostDispose() | void CPython_PostDispose() | ||
{ | { | ||
#if kUsePython | |||
CPython *pyP(CPython::Get()); | |||
if (pyP) { | |||
delete pyP; | |||
} | |||
#endif | |||
} | } | ||
// very simple unit test | |||
void CPython_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 | |||
} | } | ||
Revision as of 19:54, 30 July 2013
// 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 }