Utilizando Swig para embeber Python en un motor de juegos

download Utilizando Swig para embeber Python en un motor de juegos

of 9

Transcript of Utilizando Swig para embeber Python en un motor de juegos

  • 8/7/2019 Utilizando Swig para embeber Python en un motor de juegos

    1/9

  • 8/7/2019 Utilizando Swig para embeber Python en un motor de juegos

    2/9

    Primero lo primero

    Cada lenguaje de script posee su propio juego de tipos de datos y su propia sintaxis, por lotanto es necesario generar cdigo extra, llamado glue-code, para permitirle al mismo acceder almodelo de objetos de nuestro motor.

    Existe una excelente herramienta que nos facilita esta labor, llamada Swig(http://www.swig.org). Swig no slo nos ayuda a embeber Python en nuestro motor sinoadems Perl, Tcl/Tk, Guile, MzScheme, Ruby y Java.

    Adems nos permite acceder a las siguientes funcionalidades del ANSI C++:

    Todos los tipos de datos del C++. Referencias. Punteros a miembros. Clases. Herencia y herencia mltiple. Sobrecarga de funciones y mtodos. Sobrecarga de operadores. Miembros estticos. Espacios de nombre. Plantillas.

    PeroQu es concretamente el SWIG?

    El Swig no es una librera, sino un precompilador. Tomando como entrada un archivo definidopor nosotros (.i) generar el cdigo de necesario para permitir a Python (en nuestro caso)acceder al motor. Este cdigo generado ser cdigo C++ (wrapper functions) que luego sercompilado normalmente junto con el resto de la librera. Tambin, para acceder a ciertasfuncionalidades, podemos requerir la generacin de clases especiales en Python llamadasShadow Classes, estas clases son la contra parte de las clases C++ a la cuales queremosacceder. De este modo podremos hacer referencia a ellas desde Python como si fuesen nativas.

    Como configuramos el compilador para utilizar Swig?

  • 8/7/2019 Utilizando Swig para embeber Python en un motor de juegos

    3/9

    Si utilizamos el compilador de Microsoft (Visual C++), podremos especificar que para cierto tipode archivos invoque un comando arbitrario. En nuestro caso, para el archivo AnaCondaSwig.iespecificaremos que ejecute el swig con los siguientes parmetros:

    swig -c++ -python -shadow -o $(ProjDir)\$(InputName)_wrap.cxx $(InputPath)

    El modificador -c++ indica que el cdigo a generar es C++, el -python indica que el lenguaje

    objetivo es Python, luego especificamos que genere shadow classes con -shadow y finalmenteespecificamos el archivo de salida con -o.

    Qu contienen los archivos .i?

    Como mencionamos anteriormente, debemos especificar un archivo .i para indicarle a Swig queparte de nuestro cdigo queremos exportar para ser visto desde Python.

    Supongamos que escribimos la siguiente clase en C++:class foo

    {

    public:

    void Print(const char * pszText);

    };

    Entonces, deberemos crear un archivo foo.i que indique:

    %module foo

    %{

    #include foo.h

    %}

    %include foo.h

    Hecho esto podremos compilar el archivo foo.i, a lo que el compilador C++ invocar Swig ygenerar un extenso archivo llamado foo_wrap.cxx en lenguaje C++ y un archivo llamadofoomodule.py con la shadow class.

    y ahora que?

    Bueno, por el momento vimos como crear la interfaz de nuestro modelo de objetos que graciasa Swig es muy sencillo. Si vimos en detalle el cdigo del archivo i notamos que hay una

    sentencia llamada module foo, esto significa que existir un mdulo con este nombre quedeberemos importar desde Python para poder hacer uso de l. Es necesario crear un mdulo

  • 8/7/2019 Utilizando Swig para embeber Python en un motor de juegos

    4/9

    por clase?, respuesta: NO. Quizs nos convenga crear un nico mdulo por API o quizs unmdulo por subsistema de nuestro motor, AnaCondaGL hace uso de esta ltima opcin, ya quecuando cargamos un mdulo no utilizamos recursos por los mdulos no cargados.

    Llegando a esta instancia quizs queramos saber como invocar un script Python desde nuestromotor. Bueno para esto deberemos hacer uso del API C/Python. Pero antes es conveniente

    hacer una distincin entre embeber y extender.Embeber es utilizar el API C/Python para poder invocar scripts Python desde nuestro cdigoC/C++. Estos scripts podrn hacer uso o no de nuestro modelo de objetos.

    Extender es crear mdulos en C/C++ para poder ser utilizados desde Python.

    La diferencia es o no sutil en funcin de por donde se mire pero principalmente radica en pordonde se comience a ejecutar el programa. Cuando extendemos el programa principal seencuentra en Python que invoca mdulo escritos en C/C++, cuando embebemos el programaprincipal es C/C++ y utiliza scripts Python.

    AnaCondaGL embebe Python. Es por esto que requerimos el API C/Python para poder cargar elintrprete y llamar a los scripts correspondientes.

    Para esto implement la posibilidad de correr scripts Python desde la clase principal del motor yadems cre una clase llamada PyScript que permite la ejecucin de scripts en su propiothread. Veamos la cabecera de dicha clase:

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// AnaCondaGL : Librera para la programacin de juegos.// http://www.dedalus-software.com.ar//// AnaCondaGL es libre, puede utilizarla y/o modificarla bajo los// trminos de la licencia LGPL. Los trminos completos de esta// licencia puede encontrarlo en un archivo adjunto llamado lgpl.txt.// Diego G. Ruiz - Dedalus Software// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    #ifndef _PYSCRIPT_H_#define _PYSCRIPT_H_

    #include

    namespace AnaCondaGL{

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// PyScript// Esta clase envuelve la ejecucin de un mtodo Python. Es posible realizar// la ejecucin dentro del mismo thread o crear un thread especial para el// script.// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++class __declspec(dllexport) PyScript{

    // Handle al ThreadHANDLE m_hThreadEngine;unsigned long m_ulThreadId;

    // Nombre del archivo donde se encuentra el script a ejecutarstd::string m_strScriptFileName;// Nombre del mtodo a ejecutarstd::string m_strMethodName;

    // Indica si el script debe terminarvolatile bool m_bExitThread;

    public:PyScript();PyScript(const char * pszScriptFileName, char * pszMethodName);

    virtual ~PyScript();

  • 8/7/2019 Utilizando Swig para embeber Python en un motor de juegos

    5/9

    // Ejecuta un script en el mism hilo de ejecucinbool Run();bool Run(const char * pszScriptFileName, char * pszMethodName);

    // Ejecuta un script en un hilo propio de ejecucinbool BeginScript();bool BeginScript(const char * pszScriptFileName, char * pszMethodName);// Detiene la ejecucin del script

    bool EndScript();

    // Indica si el script se encuentra corriendobool IsRunning() { return m_bExitThread; };static bool IsRunning(long pPyScript);

    friend unsigned long WINAPI ThreadScriptFn(LPVOID pParam);};

    } // end namespace

    #endif // _PYSCRIPT_H_

    Ahora veamos el cuerpo de la clase:

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// AnaCondaGL : Librera para la programacin de juegos.

    // http://www.dedalus-software.com.ar//// AnaCondaGL es libre, puede utilizarla y/o modificarla bajo los// trminos de la licencia LGPL. Los trminos completos de esta// licencia puede encontrarlo en un archivo adjunto llamado lgpl.txt.// Diego G. Ruiz - Dedalus Software// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    #include "PyScript.h"#include "./include/python/python.h"

    // Funcin creada por Swig para registrar el mdulo Pythonextern "C" void init_anacondagl(void);

    namespace AnaCondaGL{

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// Funcin del thrad// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++unsigned long WINAPI ThreadScriptFn(LPVOID pParam){

    PyScript * pPyScript = (PyScript *) pParam;

    pPyScript->Run();

    return 1;}

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++PyScript::PyScript(){

    m_strScriptFileName = "";m_strMethodName = "";

    }

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++PyScript::PyScript(const char * pszScriptFileName, char * pszMethodName){

    m_strScriptFileName = pszScriptFileName;m_strMethodName = pszScriptFileName;

    }

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++PyScript::~PyScript()

    {

    }

  • 8/7/2019 Utilizando Swig para embeber Python en un motor de juegos

    6/9

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++bool PyScript::Run(const char * pszScriptFileName, char * pszMethodName){

    m_strScriptFileName = pszScriptFileName;m_strMethodName = pszScriptFileName;

    return Run();

    }

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++bool PyScript::Run(){

    if (m_strScriptFileName == "" || m_strMethodName == "")return false;

    PyObject * pName = NULL;PyObject * pModule = NULL;PyObject * pDict = NULL;PyObject * pFunc = NULL;PyObject * pValue = NULL;PyObject * pArgs = NULL;

    Py_Initialize();

    init_anacondagl();

    pName = PyString_FromString(m_strScriptFileName.c_str());

    // Cargo el modulo correspondiente al scriptpModule = PyImport_Import(pName);

    if (pModule != NULL){

    // Obtengo el diccionario del mdulopDict = PyModule_GetDict(pModule);

    // Busco la funcin solicitada dentro del diccionario

    pFunc = PyDict_GetItemString(pDict, (char *) m_strMethodName.c_str());

    // Verifico que la funcin es llamableif (pFunc && PyCallable_Check(pFunc)){

    // Ejecuto la funcin Python pasandole// un objeto como referenciapArgs = PyTuple_New(1);

    pValue = PyInt_FromLong((long) this);PyTuple_SetItem(pArgs, 0, pValue);

    pValue = PyObject_CallObject(pFunc, pArgs);

    if (pValue != NULL){

    Py_DECREF(pValue);}

    }

    Py_DECREF(pModule);

    }

    Py_DECREF(pName);

    Py_Finalize();

    return true;

    }// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++bool PyScript::BeginScript(){

  • 8/7/2019 Utilizando Swig para embeber Python en un motor de juegos

    7/9

    m_bExitThread = false;

    if (m_strScriptFileName == "" || m_strMethodName == "")return false;

    m_hThreadEngine = CreateThread(NULL,0,ThreadScriptFn,

    (void *) this,0,&m_ulThreadId);

    if (m_hThreadEngine)return true;

    elsereturn false;

    }

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++bool PyScript::BeginScript(const char * pszScriptFileName, char * pszMethodName){

    m_strScriptFileName = pszScriptFileName;m_strMethodName = pszScriptFileName;

    return BeginScript();}

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// Detiene la ejecucin del script// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++bool PyScript::EndScript(){

    // Determino que finalice el threadm_bExitThread = true;

    return true;}

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// Detiene la ejecucin del script

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++bool PyScript::IsRunning(long pPyScript){

    return ((PyScript *) pPyScript)->IsRunning();}

    } // end namespace

    No nos perdamos en detalles. Analicemos que es lo que pretende hacer la clase viendo comopodramos utilizarla:

    AnaCondaGL::PyScript script;

    script.Run(miscript, foo);

    En este caso creamos un objeto del tipo PyScript y lo ejecutamos (indicando que funcin del

    script queremos correr). La ejecucin se realizar en el mismo thread que el programa que hacreado el objeto PyScript, sin embargo en ocasiones desearemos que esto no sea as, y que elscript corra en su propio thread, entonces tendremos que escribir:

    AnaCondaGL::PyScript script;

    script.BeginScript(miscript, foo);

    Hecho esto la ejecucin del thread principal continuar inmediatamente despus de pasar porBeginScript, para saber si est o no corriendo an podremos hacer uso de IsRunning en suversin de clase (esttica) o de objeto. Tambin podremos finalizar la ejecucin del script conEndScript pero esto quedar supeditado a que dicho script lea peridicamente el flagm_bExitThread.

    Ahora inspeccionemos un poco como est implementada la clase. Su parte mas interesante se

    encuentra en la codificacin del mtodo Run, ya que all es donde hacemos uso del APIC/Python, veamos:

  • 8/7/2019 Utilizando Swig para embeber Python en un motor de juegos

    8/9

    Primeramente invocamos el mtodo Py_Initialize() para inicializar el interprete Python,luego invocamos una funcin llamada init_anacondagl, esta funcin se encuentra en el

    wrapper creado por Swig y lo que hace es inicializar el mdulo de anacondagl para que estdisponible a los scripts que deseen utilizarlo.

    La lnea

    pName = PyString_FromString(m_strScriptFileName.c_str());

    lo que hace es una conversin de datos, de string a pystring (que es un objeto Python), luego

    pModule = PyImport_Import(pName);

    importa el mdulo especificado por parmetro. Si lo encuentra intenta tomar el diccionario delmismo:

    pDict = PyModule_GetDict(pModule);

    y

    pFunc = PyDict_GetItemString(pDict, (char *) m_strMethodName.c_str());

    busca la funcin especificada por parmetro dentro de l:

    if (pFunc && PyCallable_Check(pFunc))

    Si la encuentra y si verifica que dicha entrada del diccionario corresponde a un objetoinvocable, armo una tupla de un valor con el address al objeto this en su interior

    pArgs = PyTuple_New(1);

    pValue = PyInt_FromLong((long) this);

    PyTuple_SetItem(pArgs, 0, pValue);

    y finalmente realizo la llamada al script:

    pValue = PyObject_CallObject(pFunc, pArgs);

    El resto del cdigo de la clase es trivial, BeginScript crea un thread que invoca una funcin(amiga de la clase) que a su vez invoca al mtodo Run. EndScript cambia el flag m_bExitThreada true y luego IsRunning devuelve el estado de este flag.

    En que momento invocar scripts?

    Quizs ahora nos preguntemos esto, Cundo utilizamos python?. Prefiero dejar librado alprogramador del juego esta decisin, ofreciendo todos los elementos necesarios para quepueda hacerlo del modo que mas le plazca. En los tutoriales del motor utilizo Python paradefinir el comportamiento de los personajes u objetos mviles.

    En el futuro planeo realizar un plugin que permita crear GUIs y los scripts de comportamientode cada control tambin se definirn podrn ser escritos Python.

    Es posible, que realizando un juego sencillo, alguien quiera utilizar 100% Python, siendo as elejecutable lo nico que har ser crear una clase que herede AnaCondaGL::Game y luego deinicializado el motor invoque un script pseudo principal donde a partir de all comience a correrel juego.

    Conclusiones

    Python es poderoso, incrementa muchsimo las prestaciones de nuestro motor, Swig nos lashace fcil y nos permite publicar rpidamente nuestra API a los scripts.

    Gran parte del cdigo de un juego no requiere gran performance, el cdigo que debe hacer eltrabajo duro permanece en C/C++ y hasta quizs Assembler pero la lgica, la inteligenciaartificial, las acciones del GUI, y un largo etctera no requieren sacar el mximo provecho del

    CPU, en estos casos es mejor correr la sbana hacia la cabeza y disfrutar de las bondades de

  • 8/7/2019 Utilizando Swig para embeber Python en un motor de juegos

    9/9

    un lenguaje de script acelerando tiempos de desarrollo y permitindonos concentrar en lajugabilidad que es al fin al cabo el componente mas importante de cualquier juego.

    Diego G. Ruiz

    [email protected]

    Referencia

    Dedalus Software: http://www.dedalus-software.com.ar

    Python: http://www.python.org

    Swig: http://www.swig.org