domingo, 25 de mayo de 2008

Colecciones de punteros a funciones

Seguimos elaborando la estructura base de la aplicación y para ello, explicaremos hoy que colecciones de objetos utilizar.


En la entrada anterior vimos que tipos de eventos tiene Irrlicht. Haremos uso de contenedores template o plantilla de la conocida familia STL de C++.

La elección del contenedor

Analicemos la situación con detenimiento. Necesitamos un contenedor de objetos que sea eficiente tanto en la inserción como en la consulta de datos, y ademas, que esten indexados por una clave, p. ej. el tipo de evento. En mi opinión, la tabla hash o array asociativo es el mas indicado (si sabes de uno mejor, por favor, dímelo ;)

Qué vamos a almacenar

Está claro, que almacenaremos punteros a funciones, pero, ¿qué declaración van a tener? Necesitamos un puntero a función que nos valga para cualquier evento. No es que me quiera copiar de .Net y Java, pero creo que uno de los parametros de la función será el propio SEvent de Irrlicht. ¿Y el retorno? en principio void, dado que el evento no suele llamase directamente, puede que me equivoque, es más, no me extrañaría, aún no conocemos Irrlicht lo sifuciente.

Los punteros a funciones tienen un inconveniente. Y es que no se puede referencia a un método de instancia. Si queremos referenciar al método de una clase, éste debe ser estático, con lo que perdemos el puntero this y cualquier atributo o campo del objeto. Pero solventar este problema es facil. Basta con que el metodo estático reciba un parametro adicional, un puntero al Objeto de la propia clase o mejor, para evitar problemas de autoreferencia, un puntero a void. Dentro de la funcion solo hay que hacer un cast al objeto de la clase y podremos acceder a todos sus atributos y campos.

He aquí el puntero a función que usaremos:

void (*puntero_a_funcion)(void*, const SEvent&)

No se tú, pero yo me canso facilmente de escribir este chorizo en cada sitio que deba usarlo, asi que me creo un define como este:

#define AppCB(var) void (*var)(void*, const SEvent&)

El define anterior es una macro que recibe el nombre del puntero a funcion, para mayor comodidad, puesto que no siempre necesito escribirlo.

Qué clave usaremos para indexar

Como ya vimos, tenemos 5 tipos de eventos, aunque, de momento el tipo de evento de usuario no lo usamos (o aún no le hemos visto utilidad). Podríamos tener una sola tabla hash para albergar a todos los eventos, pero si particularizamos en 4 contenedores separados, podremos identificar mejor los eventos. Además, de este modo no tendremos problemas con los enumerados o defines.

Si nos fijamos en el tipo de la interfaz gráfica de usuario, SGUIEvent, vemos que tiene un segundo nivel de tipo de evento, mediante gui::EGUI_EVENT_TYPE. Así que para indexar los eventos del GUI, usaremos un Pair como clave, compuesta por el gui::EGUI_EVENT_TYPE y el id del control que provocó el evento.

#define parGUIE std::pair< EGUI_EVENT_TYPE, int >

Para las teclas también usaremos un Pair que contrendrá la tecla presionada y un booleano que indicará si ha sido presionada o dejada de presionar.

#define parKEYE std::pair< EKEY_CODE, bool >

En lo referente a los eventos de raton y log, nos basta con indexarlos por el SMouseInput y SLogEvent respectivamente.

Los contenedores

Tenemos ya, todo lo necesario para definir los contenedores:


#define mapGUI std::map< parGUIE, AppCB() >
#define mapKEY std::map< parKEYE, AppCB() >
#define mapMOUSE std::map< EMOUSE_INPUT_EVENT, AppCB() >
#define mapLOG std::map< ELOG_LEVEL, AppCB() >


En la proxima entrega veremos finalmente la nueva clase que usaremos en adelante en cualquier ventana, el nuevo EventReceiver.

No hay comentarios: