Anuncios

Bienvenidos sean a este post, continuando con lo iniciado en el post anterior supongamos que queremos configurar un color de fondo para la ventana.

Anuncios

Asumiremos que el color final esta compuesto de tres numeros donde cada color es un componente de RGB (Rojo, Verde y Azul por sus siglas en ingles), por lo habitual en C estos numeros son de tipo entero en el rango entre 0 y 255, en Lua como los numeros son reales podemos usar mas el rango natural [0, 1], un enfoque inicial seria preguntar al usuario para setear cada componente en una variable global diferente:

-- archivo de configuracion
ancho = 300
alto = 300
fondo_rojo = 0.30
fondo_verde = 0.10
fondo_azul = 0
Anuncios

Este enfoque tiene dos inconvenientes: es demasiado explicito (programas reales necesitan decenas de colores diferentes para las distintas opciones de una ventana), y no hay forma de predefinir colores comunes, para que luego el usuario pueda simplemente usar algo como:

fondo = BLANCO
Anuncios

Una forma de evitar estos inconvenientes es a traves de una tabla para representar este color:

fondo = { r = 0.30, v = 0.10, a = 0 }
Anuncios

El uso de tablas da mas estructura al script ya que ahora es mas facil para el usuario (o la aplicacion) predefinir colores para usar despues en el archivo de configuracion:

AZUL = { r=0, v=0, a=1 }
VERDE = { r=0, v=1, a=0 }
ROJO = { r=1, v=0, a=0 }
BLANCO = { r=1, v=1, a=1 }
NEGRO = { r=0, v=0, a=0 }

fondo = BLANCO
Anuncios

Para conseguir estos valores en C podemos hacer lo siguiente:

lua_getglobal(L, "fondo");
if (!lua_istable(L, -1))
	error(L, "'fondo' no es una tabla!\n");
rojo = getfield(L, "r");
verde = getfield(L, "v");
azul = getfield(L, "a");
Anuncios

Primero obtendremos el valor de la variable global fondo y nos aseguramos que es una tabla, nuestro siguiente paso sera usar getfield para conseguir cada componente del color, sin embargo esta funcion no es parte de la API y debemos definirla, una vez mas nos enfrentamos al problema del polimorfismo. Hay muchas versiones de funciones getfield dependiendo del tipo de clave, tipo de valor, manipulador de error, etc pero la API de Lua ofrece una funcion, lua_gettable, que funciona para todos los tipos, toma la posicion de la tabla en la pila, quita la clave de la pila y empuja el valor correspondiente, veamos el siguiente codigo:

#define MAX_COLOR	255

int getfield (lua_State *L, const char *clave)
{
	int resultado;
	lua_pushstring(L, clave);
	lua_gettable(L, -2);
	if (!lua_isnumber(L, -1))
		error(L, "componente invalido en color de fondo");
	resultado = (int)lua_tonumber(L, -1) * MAX_COLOR;
	lua_pop(L, 1);
	return resultado;
}
Anuncios

En este codigo definimos nuestra funcion getfield privada, primero definimos una constante llamada MAX_COLOR con el valor 255, despues asumimos que nuestra tabla esta en la parte superior de la pila asi que despues de empujar la clave correspondiente con lua_pushstring, la tabla estara en la posicion -2, recuperamos la tabla, despues chequeamos si es una tabla, en caso de ser falso devolvemos un mensaje de error, de lo contrario lo asignamos a la variable resultado, convirtiendolo en un numero entero multiplicandolo por la constante que antes definimos, antes de devolver a resultado quitamos el valor resultante de la pila para dejar a la pila al mismo nivel que estaba antes de la llamada porque indexar una tabla con una cadena como clave es muy comun a partir de la version 5.1 ya que Lua nos ofrece una version especializada de lua_gettable para estos casos: lua_getfield, gracias a esta funcion podemos reescribir estas dos lineas:

lua_pushstring(L, clave);
lua_gettable(L, -2);

Por la siguiente:

lua_getfield(L, -1, clave);
Anuncios

Como no empujamos una cadena dentro de la pila, el indice de la tabla es aun -1 cuando llamamos a lua_getfield, extenderemos nuestro ejemplo un poco mas e introduciremos nombres de colores para el usuario, el usuario puede usar aun la tabla de colores pero tambien puede usar nombres predefinidos para los colores mas comunes, para implementar esta caracteristica necesitamos una tabla de color en nuestra aplicacion de C:

struct TablaColor {
	char *nombre;
	unsigned char rojo, verde, azul;
}
tablaColor = {
	{"BLANCO", MAX_COLOR, MAX_COLOR, MAX_COLOR},
	{"ROJO",   MAX_COLOR, 0, 0},
	{"VERDE",  0, MAX_COLOR, 0},
	{"AZUL",   0, 0, MAX_COLOR},
	< ... otros colores ... >
	{NULL,     0, 0, 0}
};
Anuncios

Nuestra implementacion creara variables globales con los nombres de los colores e iniciara estas variables usando las tablas de colores, el resultado es similar a lo visto en nuestro codigo anterior de las tablas con los colores, para setear los campos de la tabla definiremos una funcion auxiliar, setfield, la cual empuja el indice y el valor del campo en la pila y luego llama a lua_settable:

void setfield (lua_State *L, const char *indice, int valor)
{
	lua_pushstring(L, indice);
	lua_pushnumber(L, (double)valor/MAX_COLOR);
	lua_settable(L, -3);
}
Anuncios

Como otras funcione de la API, lua_settable, trabaja para muchos tipos diferentes asi que obtiene todos los operandos de la pila, toma el indice de la tabla como un argumento y quita la clave y el valor, la funcion setfield asume que antes de la llamada la tabla esta en la parte superior de la pila (indice -1) y despues de empujado el indice y el valor la tabla estara en el indice -3, a partir de la version 5.1 Lua ofrece una version especializada de lua_settable para claves de cadenas llamada lua_setfield, usando esta nueva funcion podemos reescribir nuestra previa definicion de setfield de la siguiente manera:

void setfield (lua_State *L, const char *indice, int valor)
{
	lua_pushnumber(L, (double)valor/MAX_COLOR);
	lua_settable(L, -2, indice);
}
Anuncios

La siguiente funcion, setcolor, define un unico color, setea los campos apropiados y asigna la tabla en la variable global correspondiente:

void setcolor(lua_State *L, struct ColorTable *ct)
{
	lua_newtable(L);
	setfield(L, "r", ct-rojo);
	setfield(L, "v", ct-verde);
	setfield(L, "a", ct-azul);
	lua_setglobal(L, ct->nombre);
}
Anuncios

La funcion lua_newtable crea una tabla vacia y la empuja en la pila, las llamadas a setfield setean los campos de la tabla, y lua_setglobal quita la tabla y la setea como el valor de la global con el nombre informado, con estas funciones previas el siguiente bucle registrara todos los colores para el script de configuracion:

int i =0;
while (tablacolor[i].nombre != NULL)
	setcolor(L, &tablacolor[i++]);
Nota: Recuerden que la aplicacion debe ejecutar este bucle antes de correr el script.
Anuncios

Hay otra opcion para implementar colores con nombres, veamos el siguiente codigo:

lua_getglobal(L, "fondo");
if (lua_isstrint(L, -1))
{
	const char *nombre = lua_tostring(L, -1);
	int i;
	for(i = 0; tablacolor[i].nombre != NULL; i++)
	{
		if (if (strcmp(nombrecolor, tablacolor[i].nombre) == 0)
			break;
	}
	if (tablacolor[i].nombre == NULL)
	{
		error(L, "nombre de color invalido (%s)", nombrecolor);
	} else {
		rojo = tablacolor[i].rojo;
		verde = tablacolor[i].verde;
		azul = tablacolor[i].azul;
	}
} else if (lua_istable(L, -1)) {
	rojo = getfield(L, "r");
	verde = getfield(L, "v");
	azul = getfield(L, "a");
} else {
	error(L, "valor invalido para 'fondo'");
}
Anuncios

En lugar de variables globales el usuario puede denotar nombres de colores con cadenas escribiendo sus configuraciones como:

fondo = "AZUL"
Anuncios

Por lo tanto fondo puede ser una tabla o una cadena, con esta implementacion la aplicacion no necesita hacer algo antes de correr el script del usuario, en cambio necesita mas trabajo para obtener un color, cuando lo consigue el valor de la variable fondo, debe chequear si el tiene el tipo cadena y luego mira la cadena en la tabla de color, en cambio si es de tipo tabla lo sacara directamente de la tabla de nuestro archivo de configuracion y si no es de ninguno de los dos tipos nos devolvera un error.

Anuncios

Cual es la mejor opcion? En programas de C el uso de cadenas para denotar opciones no es una buena practica porque el compilador no puede detectar errores de ortografia, en Lua las variables globales no necesitan declaraciones asi que Lua no señala ningun error cuando el usuario escribe mal el nombre de un color, si el usuario escribe BANCO en lugar de BLANCO la variable fondo recibe el valor nil (el valor de BANCO, una variable no iniciada) y eso es todo lo que la aplicacion sabe que:

fondo = nil

No hay otra informacion acerca de que estaba mal, con cadenas (por el otro lado) el valor de fondo seria la cadena mal escrita, asi que la aplicacion puede agregar esta informacion al mensaje de error, la aplicacion puede comparar tambien cadenas sin que le importe si son mayusculas o minusculas, asi que un usuario puede escribir “blanco”, “BLANCO” o incluso “Blanco” ademas si el script del usuario es pequeño y hay muchos colores esto podria ocasionar el registro de cientos de colores, y crear cientos de tablas y variables globales, solo para que el usuario elija unas pocas, con cadenas se evita esta sobrecarga.

Anuncios

En resumen, hoy hemos visto como extender nuestra aplicacion por medio de tablas, como se puede usar tambien cadenas gracias a struct de C, tambien hemos visto cuales son los pros y contras de estos metodos, hemos creado algunas funciones para implementarlos, y un metodo que puede ser un equilibrio para uno como el otro, espero les haya sido util sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios

Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00