Python: Difference between revisions

From kJams Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
<pre>
// 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 "CFUtils.h"
#include "CApp.h"
#include "CThreads.h"
#include <Python/Python.h>
#include "CPython.h"
#include "CPython.h"


#define kEmbeddedModuleName "emb"
bool CPython_UsePython()
#define kCustomPrintFuncName "custom_print"
{
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;
}
};


static PyObject* emb_print(PyObject *self, PyObject *args)
/**********************************************/
{
class CPython;
PyObject *resultObjP = NULL;
class CT_RunScript;
const char *utf8_strZ = NULL;
 
   
typedef std::vector<CT_RunScript *> ScriptVec;
if (PyArg_ParseTuple(args, "s", &utf8_strZ)) {
 
Log(utf8_strZ, false);
/*
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()));
}


Py_INCREF(Py_None);
// now wait until errors have already been shown, if any
resultObjP = Py_None;
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[] = {
     {kCustomPrintFuncName, emb_print, METH_VARARGS, "Calls custom print function."},
     {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 "." kCustomPrintFuncName "(stuff)\n"
" " 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 = CustomPrintClass()\n"
"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);
}


OSStatus CPython_PreAlloc(const char *utf8Z)
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)
{
{
OSStatus err = noErr;
if (s_pythonP == NULL) {
PyObject *pyObjectP = NULL;
CF_ASSERT(appNameZ);
s_pythonP = new CPython(appNameZ);
}
Py_SetProgramName(const_cast<char *>(utf8Z));
return s_pythonP;
Py_Initialize();
}
 
/****************************************************/
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);
}
}
/*****************************************************************/


pyObjectP = Py_InitModule(kEmbeddedModuleName, const_cast<PyMethodDef*>(EmbMethods));
#endif // kUsePython
ERR_NULL(pyObjectP, tsmUnsupScriptLanguageErr);
// don't DECREF this object??  leave it hanging?


ERR(PyRun_SimpleString(s_RedirectPrint));
// 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()
{
{
Py_Finalize();
#if kUsePython
CPython *pyP(CPython::Get());
if (pyP) {
delete pyP;
}
#endif
}
}


// very simple unit test
void CPython_Test()
void CPython_Test()
{
{
CCritical sc(GetSprintfMutex());
#if kUsePython
CPython *pyP(CPython::Get());
Log("---Python START:");
if (pyP) {
pyP->Test();
}
#endif
}


PyRun_SimpleString(
// called when startup is complete
"from time import time, ctime\n"
void CPython_Startup()
"print 'Today is', ctime(time())\n");
{
 
#if kUsePython
Log("---Python END:");
CPython *pyP(CPython::Get());
if (pyP) {
pyP->Startup();
}
#endif
}
}
</pre>

Revision as of 19:54, 30 July 2013

// CPython.cpp

  1. include "kVersion.h"
  1. if kUsePython

#if __WIN32__ #include "python.h" #else #include <Python/Python.h> #endif

  1. endif
  1. include "stdafx.h"
  2. include "CApp.h"
  3. include "CPython.h"

bool CPython_UsePython() { return kUsePython; }

  1. if kUsePython
  1. include "CTaskMgr.h"
  1. define kEmbeddedModuleName "kjams"
  1. define kCustomPrint "custom_print"
  2. define kCustomErr "custom_err"
  3. define kCommand "do_command"
  4. define kStringCommand "do_command_str"
  5. define kDoMenuCommand "do_menu_command"
  6. define kDoMenuName "do_menu_name"
  1. define kStartupScriptName "startup.py"
  2. 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); } } /*****************************************************************/

  1. 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 }