Game Development Community

TSE Python module

by Prairie Games · in Torque Game Engine Advanced · 05/10/2006 (1:42 am) · 4 replies

I'm in the process of refining our Python support. Below are the basics for getting TSE into a python module, getting/setting fields, and calling script methods. It's quite short and sweet.

I'll soon add exporting Python functions to the console system and evaluating generated TorqueScript from the Python side. That's pretty much the commonly used functionality from our previous system.

I've also been playing around with Stackless Python. It's pretty darn neat.

-Josh Ritter
Prairie Games, Inc

//Python Support

#include "platform/platform.h"
#include "platform/event.h"
#include "platform/gameInterface.h"
#include "platformWin32/platformWin32.h"
#include "console/simBase.h"
#include "console/compiler.h"
#include "console/consoleInternal.h"

int TSE_Initialize();
int TSE_Tick();
int TSE_Shutdown();

extern "C" {

#ifdef _DEBUG
#undef _DEBUG
#include <Python.h>
#define _DEBUG 
#else
#include <Python.h>
#endif

static PyObject* gTSEException = NULL;

//TSEObject

typedef struct {
   PyObject_HEAD      
   SimObject* simObject;
} PyTSEObject;

//PyTSEObject Allocator (used when wrapping/finding an existing TSEObject)
static PyObject* PyTSEObject_new(PyTypeObject *typeobject,PyObject *pargs, PyObject *kwargs)
{
   PyObject* arg = NULL;
   SimObject* simObject = NULL;

   if (PyTuple_Size(pargs)!=1)
   {
      PyErr_SetString(gTSEException, "TSEObject(name|id)");
      return NULL;
   }

   arg = PyTuple_GetItem(pargs,0);

   if (PyString_Check(arg))
   {
      simObject = Sim::findObject(PyString_AsString(arg));
      if (!simObject)
      {
         PyErr_Format(gTSEException, "NEW: Unable to find a TSEObject with name '%s'",PyString_AsString(arg));
         return NULL;
      }
   }
   else if (PyInt_Check(arg))
   {
      simObject = Sim::findObject(PyInt_AsLong(arg));
      if (!simObject)
      {
         PyErr_Format(gTSEException, "NEW: Unable to find a TSEObject with id: %i",PyInt_AsLong(arg));
         return NULL;
      }

   }
   else
   {
      PyErr_SetString(gTSEException, "TSEObject(name|id)");
      return NULL;
   }

   PyTSEObject* nobj = PyObject_New(PyTSEObject, typeobject);   //new reference
   nobj->simObject = simObject;
   Py_INCREF(nobj);
   return (PyObject*)nobj;
}

//PyTSEObject Field Accessors

//Python to TSE function calling
static StringTableEntry gFunctionString;
static Namespace* gFunctionNameSpace;
static Namespace::Entry *gFunctionEntry;

static PyObject* PyTSEObject_call(PyTSEObject *self, PyObject *args, PyObject *kw)
{

   const char* argv[32];
   S32 argc = 2;

   argv[0]=gFunctionString;
   argv[1]=self->simObject->getIdString();

   for (int i =0;i<PyTuple_Size(args) && i<32;i++)
   {
      if (gFunctionEntry->mMaxArgs>0 && argc>gFunctionEntry->mMaxArgs)
      {
         PyErr_Format(gTSEException, "TSEObject of class %s function %s max arguments exceeded",self->simObject->getClassName(),gFunctionString);
         return NULL;
      }
      
      PyObject* o = PyTuple_GetItem(args,i);
      if (PyString_Check(o))
         argv[i+2]=PyString_AsString(o);
      else
         argv[i+2]=PyString_AsString(PyObject_Str(o));
   }

   if (argc<gFunctionEntry->mMinArgs)
   {
      PyErr_Format(gTSEException, "TSEObject of class %s function %s not enough arguments",self->simObject->getClassName(),gFunctionString);
      return NULL;
   }

   SimObject *save = gEvalState.thisObject;
   gEvalState.thisObject = self->simObject;
   const char *ret = gFunctionEntry->execute(argc, argv, &gEvalState);
   gEvalState.thisObject = save;

   return PyString_FromString(ret);
}

#1
05/10/2006 (1:42 am)
static PyObject* PyTSEObject_getattr(PyTSEObject *self, char *attr)
{
   StringTableEntry fieldName = StringTable->insert(attr);

   const AbstractClassRep::Field* field = self->simObject->getClassRep()->findField(fieldName);

   if (field)
   {
      //XXX: Handle Arrays
      return PyString_FromString(self->simObject->getDataField(fieldName,NULL));
   }

   //not a field, could be a function
   gFunctionNameSpace=self->simObject->getClassRep()->getNameSpace();
   AssertISV(gFunctionNameSpace,"SimObject with no namespace");

   gFunctionString = fieldName;

   gFunctionEntry = gFunctionNameSpace->lookup(gFunctionString);
   if (!gFunctionEntry)
   {
      PyErr_Format(gTSEException, "TSEObject of class %s has no field or function %s",self->simObject->getClassName(),gFunctionString);
      return NULL;
   }

   //XXX: increase ref count on self here?
   return (PyObject *)self;
}

static int PyTSEObject_setattr(PyTSEObject* self, char* attr, PyObject* value)
{
   StringTableEntry fieldName = StringTable->insert(attr);

   const AbstractClassRep::Field* field = self->simObject->getClassRep()->findField(fieldName);

   if (!field)
   {
      PyErr_Format(gTSEException, "TSEObject of class %s has no field %s",self->simObject->getClassName(),fieldName);
      return NULL;
   }

   if (PyString_Check(value))
   {
      self->simObject->setDataField(fieldName,NULL,PyString_AsString(value));
      return 0;
   }

   self->simObject->setDataField(fieldName,NULL,PyString_AsString(PyObject_Str(value)));

   return 0;
}

static PyTypeObject PyTSEObject_Type = {
   PyObject_HEAD_INIT(NULL)
      0,                         /*ob_size*/
      "pytse.TSEObject",         /*tp_name*/
      sizeof(PyTSEObject),       /*tp_basicsize*/
      0,                         /*tp_itemsize*/
      0,                         /*tp_dealloc*/
      0,                         /*tp_print*/
      (getattrfunc) PyTSEObject_getattr, /*tp_getattr*/
      (setattrfunc) PyTSEObject_setattr,                         /*tp_setattr*/
      0,                         /*tp_compare*/
      0,                         /*tp_repr*/
      0,                         /*tp_as_number*/
      0,                         /*tp_as_sequence*/
      0,    /*tp_as_mapping*/
      0,                         /*tp_hash */
      (ternaryfunc) PyTSEObject_call,          /*tp_call*/
      0,                         /*tp_str*/
      0,                         /*tp_getattro*/
      0,                         /*tp_setattro*/
      0,                         /*tp_as_buffer*/
      Py_TPFLAGS_DEFAULT,        /*tp_flags*/
      "TSEObject",               /* tp_doc */
};

//pytse Module

static PyObject* pytse_init(PyObject* self,PyObject* args)
{
   TSE_Initialize();
   Py_INCREF(Py_None);
   return Py_None;
}

static PyObject* pytse_tick(PyObject* self,PyObject* args)
{
   if (TSE_Tick())
   {
      //still going
      Py_INCREF(Py_True);
      return Py_True;
   }

   Py_INCREF(Py_False);
   return Py_False;
}

static PyObject* pytse_shutdown(PyObject* self,PyObject* args)
{
   TSE_Shutdown();
   Py_INCREF(Py_None);
   return Py_None;
}

static PyMethodDef pytse_methods[] = {
   {"initialize", pytse_init, METH_VARARGS, NULL},
   {"tick", pytse_tick, METH_VARARGS, NULL},
   {"shutdown", pytse_shutdown, METH_VARARGS, NULL},

   {NULL,NULL,0,NULL}    /* Sentinel */
};

extern "C" __declspec(dllexport) void initpytse(void)
{

   //initialize the python module
   PyObject *m;  

   m = Py_InitModule("pytse",  pytse_methods);

   //initialize the TSEObject type
   PyTSEObject_Type.tp_new = PyTSEObject_new;
   if (PyType_Ready(&PyTSEObject_Type) < 0)
      AssertISV(0,"There was an error reading the PyTSEObject_Type");

   Py_INCREF(&PyTSEObject_Type);

   PyModule_AddObject(m, "TSEObject", (PyObject *)&PyTSEObject_Type);

   gTSEException = PyErr_NewException("pytse.exception", NULL, NULL);
   Py_INCREF(gTSEException);

   PyModule_AddObject(m, "TSEException", gTSEException);

}

} //extern "C" 

//TSE 

extern void createFontInit();
extern void createFontShutdown();
extern bool LinkConsoleFunctions;

//--------------------------------------
static int TSE_Initialize()
{

   Vector<char *> argv;
   char moduleName[256];

   LPSTR lpszCmdLine =GetCommandLineA();

   GetModuleFileNameA(NULL, moduleName, sizeof(moduleName));
   argv.push_back(moduleName);

   //ensure that console functions link
   LinkConsoleFunctions = true;

   for (const char* word,*ptr = lpszCmdLine; *ptr; )  {
      // Eat white space
      for (; dIsspace(*ptr) && *ptr; ptr++)
         ;
      // Pick out the next word
      for (word = ptr; !dIsspace(*ptr) && *ptr; ptr++)
         ;
      // Add the word to the argument list.
      if (*word) {
         int len = ptr - word;
         char *arg = (char *) dMalloc(len + 1);
         dStrncpy(arg, word, len);
         arg[len] = 0;
         argv.push_back(arg);
      }
   }

   winState.appInstance = GetModuleHandle(NULL);

   //S32 retVal = run(argv.size(), (const char **) argv.address());

   createFontInit();

   S32 ret = Game->initialize(argv.size(), (const char **) argv.address());

   for(U32 j = 1; j < argv.size(); j++)
      dFree(argv[j]);

   return 1;
}

static int TSE_Tick()
{
   return Game->tick();
}

static int TSE_Shutdown()
{
   int ret = Game->shutdown();
   createFontShutdown();
   return ret;
}
#2
05/10/2006 (1:43 am)
Wow, very cool, this should be a resource
#3
05/10/2006 (1:52 am)
I'll see about making a resource.

This will also work in TGE 1.3 and 1.4

-JR
#4
05/10/2006 (12:06 pm)
I added exporting functions and other goodies. Here's a blog post with some example usage:

Example Usage

-JR