Python: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
| Line 106: | Line 106: | ||
void RemoveScript(CT_RunScript *scriptP); | void RemoveScript(CT_RunScript *scriptP); | ||
void IdleScripts(); | void IdleScripts(); | ||
void AllowPendingCalls(); | |||
size_t CountScripts(); | size_t CountScripts(); | ||
}; | }; | ||
| Line 551: | Line 552: | ||
// if you want to use Py_AddPendingCall() to send a message to THIS | // if you want to use Py_AddPendingCall() to send a message to THIS | ||
// thread, then you'd need to uncomment this line | // thread, then you'd need to uncomment this line: | ||
// AllowPendingCalls(); | |||
if (!abortB) { | if (!abortB) { | ||
| Line 591: | Line 587: | ||
return err; | return err; | ||
} | |||
void CPython_RunLoop::AllowPendingCalls() | |||
{ | |||
ScEnsureGIL sc; | |||
if (PyRun_SimpleString(s_AllowPendingCalls) != 0) { | |||
PostAlert("Python: s_AllowPendingCalls failed"); | |||
i_abortB.Set(); | |||
} | |||
} | } | ||
Revision as of 20:07, 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();
void AllowPendingCalls();
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:
// AllowPendingCalls();
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::AllowPendingCalls()
{
ScEnsureGIL sc;
if (PyRun_SimpleString(s_AllowPendingCalls) != 0) {
PostAlert("Python: s_AllowPendingCalls failed");
i_abortB.Set();
}
}
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
}