Si bien hay varios intérpretes de Lua para el PSP, y la scene ya no se mueve tanto como antaño, siempre es interesante saber cómo se hacen las cosas. En esta ocasión les daré una introducción al mundo de Lua mostrándoles cómo se programan los intérpretes. No sé, quizá alguien nos sorprenda y se anime a crear uno.
Durante el desarrollo del tutorial usaremos funciones propias del entorno de Lua, y aunque algunas las explicaremos es conveniente revisar esta página (allí están las funciones check que se mencionan en el tutorial) y esta otra (funciones push) para referencias y más funciones útiles.
También es buena idea familiarizarse con la documentación de PSPSDK (viene incluida en el instalador, pero también la pueden consultar online).
Necesitaremos instalar unas cuantas cosillas para poder llevar a buen puerto nuestro proyecto. Cualquier instalación de PSPSDK debería funcionar, pero se recomienda ampliamente usar Minimalistic PSPSDK en su versión 0.10.0 (aquí encuentran los instaladores). La instalación no tiene nada de especial, Next a todo.
Una vez instalado, descargamos este paquete (Sólo usuarios registrados) que incluye un ejemplo muy sencillo para iniciar (con muchos comentarios para saber que hace cada cosa), un MAKEFILE y un ejecutable para compilar fácilmente.
Copiamos los archivos que contiene a alguna carpeta, procurando que la ruta no contenga caracteres especiales ni espacios.
Para probar que todo está en su lugar, ejecutamos el archivo compilador.bat. No debe aparecer ningún error y debe generarse el archivo EBOOT.PBP en la carpeta de nuestro proyecto (si no es así, revisamos que todo esté bien instalado).
Nótese que compilador.bat guarda toda la información relativa a la compilación (errores, debug y demás) en un archivo de texto llamado log.txt, mismo que podremos consultar con toda comodidad para comprobar que no hemos cometido errores.
El archivo main.c se encarga de registrar las funciones que creamos para poderlas usar desde Lua, así como de abrir y ejecutar nuestro script. No es necesario que todo el código esté en ese archivo, podemos usar otros para mantener organizado nuestro proyecto, pero en tal caso debemos pasarle al main.c al menos la función que inicializa y registra el resto de nuestras funciones. En el tutorial usaremos archivos separados.
En Lua existe un concepto muy básico: el stack. Básicamente, el stack es como un contenedor donde se alojan tanto los argumentos de las funciones que llamamos desde Lua como el contenido que mandamos a Lua desde C.
Cada vez que se intercambian datos entre Lua y C, el stack cambia. Muchas de las funciones que usaremos leen o modifican el stack, y para acceder al contenido que nos interesa deberemos indicarle un índice o posición donde se ubica dicho contenido, como si se tratara de un array.
En una forma más simple, el stack se parece a una recta numérica centrada en el 0. A la derecha se ubican los argumentos de las funciones llamadas desde Lua, mientras que a la izquierda encontramos el contenido que C envía de regreso a Lua.
Resulta evidente, pues, que para acceder a los argumentos que Lua envía, usamos índices positivos: 1 para el primer argumento, 2 para el segundo y así sucesivamente (ojo, que esto es a nivel local. Cada que llamamos a una función, su primer argumento siempre está en el índice 1, ya que los índices no se acumulan entre llamadas, lo cual resulta muy práctico).
Siempre que mandamos valores de regreso a Lua se colocan al tope del stack, pero del lado izquierdo. Así, el último valor que hayamos mandado estará en la posición -1, el penúltimo en -2 y así sucesivamente. Esto es de especial importancia cuando se manejan tablas (como lo veremos más adelante) y también para mostrar errores en el script (Lua automáticamente manda al tope del stack cualquier error que tenga el script, es por eso que en el main.c para mostrar el error se usa el índice -1 y la función lua_tostring(), para convertir en string el error que Lua colocó allí).
Este concepto del stack es lo más complicado de comprender cuando se programa un intérprete, pero es de vital importancia entenderlo al menos en esencia. Por ello, trataré de ahondar un poco más con ejemplos conforme vayamos avanzando en el tutorial.
Para poder registrar funciones desde otros archivos que no sean el main.c, requerimos añadir las librerías de Lua al inicio de ellos. Podemos hacerlo como se ve en el propio main.c, pero por motivos de organización vamos a crear una librería auxiliar para añadir esas librerías:
#ifndef AUXLIB_H #define AUXLIB_H #include "lua.h" #include "lualib.h" #include "lauxlib.h" #endif
Guardamos como auxlib.h. Ahora, podremos añadir las librerías de Lua muy fácil, con sólo escribir
#include "auxlib.h"
Iniciaremos registrando una sencilla función para imprimir un mensaje de texto. Para tal efecto creamos un nuevo archivo y procedemos de esta forma:
static int nombre_funcion(){ //el nombre no importa return 0; }
#include "auxlib.h" #include <pspdebug.h> static int test_printf(){ pspDebugScreenPrintf("Hello world :D"); return 0; }
static const luaL_reg test_functions[] = { //En este array pondré todas las funciones que quiera registrar {"printf",test_printf}, //declaro que la función de C test_printf se llamará printf en Lua {0,0} //Esto SIEMPRE debe estar al final del array, de lo contrario no funciona el registro }; void testInit(lua_State *L){ //aquí tampoco importa el nombre, esta función será la que inicialize el módulo //Con la siguiente línea todas las funciones que estén en el array que declaramos antes quedarán registradas en el contenedor "test" luaL_register(L,"test",test_functions); }
void testInit(lua_State *L){ //aquí tampoco importa el nombre, esta función será la que inicialize el módulo //Con la siguiente línea la función de C test_printf quedará registrada en Lua como printf, sin contenedor lua_register(L, "printf", test_printf); }
#ifndef TEST_H #define TEST_H void testInit(lua_State *L); #endif
//Aquí incluimos los archivos.h de nuestros módulos #include "test.h"
// En esta parte vamos a inicializar nuestros módulos :) testInit(L); //
## Aqui especificamos los archivos que deben compilarse y agregarse
OBJS = main.o test.o
test.printf() while true do --De momento esto detendrá el script, para ver si funciona o no el código xD v=1 end
Pan comido, verdad? xD
La función que registramos anteriormente no es de mucha utilidad que digamos, así que le agregaremos la posibilidad de cambiarle el texto que imprime (algo por demás lógico).
static int test_printf(lua_State *L){ //Aqui está el argumento que hay que agregar pspDebugScreenPrintf("Hello world :D"); return 0; }
static int test_printf(lua_State *L){ pspDebugScreenPrintf(luaL_checkstring(L,1)); return 0; }
static int test_suma(lua_State *L){ return 1; }
static int test_suma(lua_State *L){ float res = luaL_checkint(L,1) + luaL_checknumber(L,2); return 1; }
static int test_suma(lua_State *L){ float res = luaL_checkint(L,1) + luaL_checknumber(L,2); //aquí calculo la suma lua_pushnumber(L,res); //aquí envío el resultado return 1; //retorno 1 valor }
static const luaL_reg test_functions[] = { {"printf",test_printf}, //declaro que la función de C test_printf se llamará printf en Lua {"suma",test_suma}, //Mi suma :D {0,0} //Este SIEMPRE debe estar aqui };
Fácil no? check para recibir, push para enviar.
Ahora nos adentraremos a algo más complicado: devolver tablas. Sin importar cuántos elementos contenga, Lua trata la tabla como un solo valor de retorno. Para devolver una tabla debemos:
Para índices no numéricos también se puede proceder como si fueran numéricos, con la diferencia de que los índices no serán números sino strings. Personalmente prefiero el segundo método, se desgasta menos el teclado xD.
Aquí ya podríamos terminar la función con return 1 y sería suficiente para devolver la tabla llena, o podríamos seguir mandando más contenido además de la tabla, dependiendo de nuestra función.
Veamos algunos ejemplos rápidos:
/* Resultado esperado tabla={111,"dos"} */ static int newtabla(lua_State *L){ lua_newtable(L); //Creamos una tabla lua_pushnumber(L,1); //Mandamos el índice lua_pushnumber(L,111); //Mandamos el elemento a guardar lua_settable(L,-3); //Guardamos en la tabla la pareja lua_pushnumber(L,2); //Mandamos el siguiente índice lua_pushstring(L,"dos"); //Mandamos el siguiente elemento a guardar lua_settable(L,-3); //Guardamos return 1; } /* Resultado esperado tabla={var=1,var2="dos"} Se muestran 2 métodos, el 1ro igual al anterior, el segundo con 1 línea menos usar el más entendible... */ static int newtabla(lua_State *L){ lua_newtable(L); //Creamos una tabla lua_pushstring(L,"var"); //Mandamos el índice lua_pushnumber(L,1); //Mandamos el elemento a guardar lua_settable(L,-3); //Guardamos en la tabla la pareja lua_pushstring(L,"dos"); //Mandamos el siguiente elemento a guardar lua_setfield(L,-2,"var2"); //Guardamos, dando nombre al índice directamente return 1; } /* Resultado esperado tabla={ {1,var="dos"}, tab2={"tres",var=4} } Tablas dentro de tablas es más largo, pero igual de sencillo. El procedimiento básico es: -crear la tabla "general" -mandar el índice (obligatorio en caso de ser numérico, caso contrario se puede omitir) -crear la 2da tabla, de igual forma que la general -llenar la tabla según se vió arriba -asignar la tabla ya llena a la general. -repetir si es necesario */ static int newtabla(lua_State *L){ lua_newtable(L); //Creamos la tabla general lua_pushnumber(L,1); //Quiero la 1ra tabla con índice numérico, así que lo mando primero lua_newtable(L) //Y luego creo la tabla //vamos a llenarla antes de añadirla a la general... lua_pushnumber(L,1); //índice lua_pushnumber(L,1); //elemento a guardar lua_settable(L,-3); //Guardamos en la tabla la pareja lua_pushstring(L,"dos"); //siguiente elemento... lua_setfield(L,-2,"var"); //Lo guardo, y le asigno su índice lua_settable(L,-3); //Guardo la 1ra tabla, ya llena lua_newtable(L); //La siguiente es con índice no numérico, así que la creo directamente //La lleno... lua_pushnumber(L,1); //índice lua_pushstring(L,"tres"); //elemento a guardar lua_settable(L,-3); //Guardamos en la tabla la pareja lua_pushnumber(L,4); //elemento lua_setfield(L,-2,"var"); //Lo guardo y asigno índice lua_setfield(L,-2,"tab2") //Y guardo la 2da tabla ya llena return 1; }
Las funciones check levantan errores automáticamente cuando no pueden obtener el valor indicado por una u otra razón. Por ejemplo, si queremos obtener un string y en el lugar que indicamos hay una tabla, saltaría este error:
bla, bla, bla: string expected, got table.
Es un mensaje que la propia librería de Lua maneja, pero nada nos detiene de revisar el tipo de dato que contiene un argumento, usando las funciones lua_isXXXX (que puede ser number, table, nil, etc. Revisen los links). Esto nos podría servir para que nuestra función haga diferentes cosas dependiendo de los argumentos que se le entregan.
Aprovechando que ya sabemos devolver tablas, modificaré la función que creamos para sumar para que devuelva una tabla con los números que le entregué y la suma total. Además, el código verificará si los argumentos son números antes de sumarlos, y en caso de que no lo sean los tomará como 0. Nada más sencillo:
static int test_suma(lua_State *L){ int n1=0; //Declaro algunas variables... float n2=0; if(lua_isnumber(L,1)) //Reviso si el primer argumento es un número n1 = luaL_checkint(L,1); //Lo obtengo if(lua_isnumber(L,2)) //Ahora reviso si el segundo argumento también es un número n2 = luaL_checkint(L,2); //y de ser así lo obtengo //Ya tenemos lo necesario, toca armar la tabla: lua_newtable(L); //La creamos primero //Y la llenamos: lua_pushnumber(L,1); //índice lua_pushnumber(L,n1); //Valor lua_settable(L,-3); //Guardo lua_pushnumber(L,2); //índice lua_pushnumber(L,n2); //Valor lua_settable(L,-3); //Guardo lua_pushnumber(L,3); //índice lua_pushnumber(L,n1+n2); //Valor lua_settable(L,-3); //Guardo return 1; //retorno 1 valor }
Y comprobamos que funciona con otro script:
test.printf("Ahora imprimire 25 + 4.5 con una tabla xD\n") res=test.suma(25,4.5) test.printf("N1="..res[1].."\nN2="..res[2].."\nResultado="..res[3]) test.printf("\n\nAhora tratare de sumar 25 + false...\n") res=test.suma(25,false) test.printf("N1="..res[1].."\nN2="..res[2].."\nResultado="..res[3]) while true do --De momento este detendrá el script, para ver si funciona o no el código xD v=1 end
Otra función muy útil es lua_gettop(). Sólo lleva L como argumento, y nos regresa el número de argumentos que le fueron entregados a la función cuando fue llamada en Lua. Parece algo trivial, pero resulta sumamente útil para lograr funciones con número variable de argumentos. Voy a retomar el printf que hicimos para ejemplificarlo:
static int test_printf(lua_State *L){ int i; //Para el for for (i=1; i<=lua_gettop(L); i++) //Obtengo uno por uno los argumentos pspDebugScreenPrintf("%s\n", lua_tostring(L,i)); //Los convierto a string, sean lo que sean, y los imprimo (añadiendo tambien un salto de linea) return 0; }
test.printf("Arg1","Arg2",550+5,false,nil,noExisto) --entrego unos cuantos argumentos, algunos no existen a ver que pasa while true do --De momento este detendrá el script, para ver si funciona o no el código xD v=1 end
Para terminar esta sección, veremos cómo se registran las constantes. Realmente es algo muy sencillo, muy parecido a como se trabaja con las tablas.
Básicamente:
Veamos un par de ejemplos:
// Para crear constantes sueltas, es decir, que no están en ningún contenedor lua_pushstring(L,"Rober Galarga"); //Mandamos el valor de la constante (un número, un boolean, una tabla... no importa) lua_setglobal(L,"_NICK"); /* script de prueba: test.printf(_NICK) --uso la constante directamente */ //Ahora, para crear constantes dentro de algún contenedor //Digamos que la misma constante de antes la quiero dentro del modulo test... lua_getglobal(L,"test"); //Mando el contenedor "test" al tope del stack lua_pushstring(L,"Rober Galarga"); //mando el valor lua_setfield(L,-2,"nick"); //Y le asigno su nombre /* script de prueba: test.printf("Mi nick es: "..test.nick) --imprimo la constante */
En caso de querer constante dentro de un contenedor, éste debe existir antes de poder crear la constante. Por esta razón, y por motivos de organización, suelo registrar las constantes al final de la función que inicializa el módulo donde se requiere la constante, así sin importar si es una constante suelta o dentro del contenedor, funcionará sin problemas.
Hasta ahora todo es color de rosa, las funciones no usan más que datos nativos de Lua. Pero, que pasa si queremos trabajar con tipos de datos que no existen en Lua? Cómo se hace?
Pues, increíblemente de manera similar a lo que ya vimos, con un par de modificaciones. Para ejemplificarlo, crearé un nuevo tipo de dato para almacenar colores y cambiarle el color al texto de las impresiones.
// Para crear nuevos datos #define newUserData(NAME, VTYPE) \ VTYPE *check_##NAME (lua_State *L, int index){ \ VTYPE* NAME = (VTYPE*)lua_touserdata(L, index); \ if (NAME == NULL) luaL_typerror(L, index, #NAME); \ return NAME; \ } \ VTYPE* push_##NAME(lua_State *L){ \ VTYPE * newvalue = (VTYPE*)lua_newuserdata(L, sizeof(VTYPE)); \ luaL_getmetatable(L, #NAME); \ lua_setmetatable(L, -2); \ return newvalue; \ } #define userDataRegister(NAME, FCNS, META) \ int NAME##_register(lua_State *L) { \ luaL_openlib(L, #NAME, FCNS, 0); \ luaL_newmetatable(L, #NAME); \ luaL_openlib(L, 0, META, 0); \ lua_pushstring(L, "__index"); \ lua_pushvalue(L, -3); \ lua_rawset(L, -3); \ lua_pushstring(L, "__metatable"); \ lua_pushvalue(L, -3); \ lua_rawset(L, -3); \ lua_pop(L, 1); \ return 1; \ }
#include "auxlib.h" //esta que no falte #include <pspdebug.h> newUserData(color,u32); //Declaro el nuevo tipo de dato "color" como u32
static int color_new(lua_State *L){ //Para crear colores int r,g,b; //Componentes del color red, green, blue (rojo, verde, azul) r=luaL_checkint(L,1); //Obtengo cada componente como entero... g=luaL_checkint(L,2); b=luaL_checkint(L,3); u32 RGB = (r*65536)+(g*256)+b; //Y "armo" el color *push_color(L)=RGB; //Así es como se manda el nuevo tipo de dato que creamos return 1; //retorno 1 valor } static int color_settxtcolor(lua_State *L){ //Con esta recibo un color ya creado y se lo aplico al texto pspDebugScreenSetTextColor(*check_color(L,1)); //Los datos los obtenemos casi igual, sólo que ahora son punteros return 0; }
static const luaL_reg color_functions[] = { //Igual un array para registro {"new",color_new}, //mis funciones {"settxtcolor",color_settxtcolor}, {0,0} }; static const luaL_reg meta[] = { //Esto es nuevo! un array de metadatos. No tengo mucha idea de cómo funciona, pero se requiere xD {0,0} }; userDataRegister(color, color_functions, meta); //Registramos las funciones en el contenedor "color" void colorInit(lua_State *L){ //Usaremos esta función para inicializar este módulo color_register(L); //Esta función se genera automáticamente cuando usamos newUserData() }
test.printf("Yo soy una linea blanca") verde = color.new(0,255,0) --probemos un color verde color.settxtcolor(verde) test.printf("Y yo soy verde!") while true do --De momento este detendrá el script, para ver si funciona o no el código xD v=1 end
Poco que decir aquí, el compilador nos avisa del error que impide que nuestro código compile. En ocasiones resulta muy fácil repararlo, otras veces no tanto. Si la solución no es evidente, la manera de solucionarlo es buscar el error en internet, muchas veces veremos que ya está solucionado o encontraremos no la solución a nuestro problema específico pero sí a uno muy parecido, lo que nos dará pistas de cómo solucionar el nuestro.
Y para terminar (al fin!), les diré que un intérprete de Lua es un gran reto, que consume muchísimo tiempo y que puede ser ridículamente sencillo o ridículamente difícil hacer que alguna función haga lo que se supone debe hacer.
Pues bien, ya tenemos el conocimiento necesario para iniciar nuestro propio intérprete. Texto bonito, imágenes, sonidos, funciones útiles... todo eso son derivaciones de lo que aprendimos aquí, es cuestión de integrar código o librerías, aplicar conocimiento y practicar mucho. No dejen de revisar los samples que vienen con PSPSDK, seguro que les serán de mucha utilidad.
Aquí podemos descargar (Sólo usuarios registrados) los archivos con todas las modificaciones que hicimos en el tutorial, para que les sirvan de referencia.
Comentarios
winshell
...si, me lo voy a leer para ver que podemos hacerle al winshell.
Osea
osea que con lua es posible hacer juegos para la psp?
Claro,
Si, Lua es para crear aplicaciones de manera sencilla y versatil! :D
Hasta que veo algo
Hasta que veo algo interezante