Python

From kJams Wiki
Revision as of 19:54, 30 July 2013 by Dave (talk | contribs)
Jump to navigation Jump to search

// 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 }