domingo, 12 de junio de 2016

Extremos y exploración

Hay mucho escrito sobre cómo diseñar orientado a objetos; reglas, buenos usos y cosas a evitar. Todo en pos de que el código sea reutilizable, fácil de entender y fácil de modificar.
El problema surge cuando nos damos cuenta que estas técnicas no sólo no cumplen con lo que prometen, sino que muchas veces tienen el efecto contrario, difíciles de entender, difíciles de modificar y poco reutilizables.
Entonces, cuando nos alejamos de esas prácticas, tenemos si o si que inventar unas nuevas.
A continuación presento 2 técnicas que uso a diario, y como las use mientras programaba un rasterizer de fuentes TrueType.

 

Exploración

Cuando tengo que programar algo que nunca hice antes, en vez de perder tiempo diseñando o diagramando clases, prefiero escribir código que me vaya acercando a la solución.


Es importante mantener un objetivo claro e intentar alcanzarlo programando lo mínimo necesario. El mio era poder entender un archivo TTF y obtener información para dibujar texto en pantalla.

Comencé con una función que sólo recibía el nombre del archivo y me iba mostrando información en la consola. Es recomendable poner esta función en un lugar que sea fácil de invocar, en mi caso, se ejecuta siempre que el código se compila. Si no hubiese tenido implementado hot reloading, hubiese puesto esta función al inicio de todo, por ejemplo.

Es muy común tener dudas sobre cual es la mejor manera de avanzar, ¿es performante mi código? ¿es reutilizable? ¿Es la idea A es mejor que la B?
Todas estas dudas nos impiden avanzar y este no es el mejor momento para responderlas; en modo exploración uno tiene que programar hasta tener el problema resuelto, recién ahí se pueden evaluar alternativas.
Evaluar alternativas sin conocer la solución al problema es una completa pérdida de tiempo.

Unir extremos

Por un lado tengo el API que estoy creando, lo que va a usar el programador cada vez que quiera dibujar texto en pantalla, tiene que ser fácil de entender y de usar.

DrawText("Blah", X, Y);

En el otro extremo, esta el hardware, píxeles en pantalla que forman el texto ingresado.
En este caso yo tengo que cumplir con un protocolo impuesto por la GPU y OpenGL, crear una textura en memoria con un determinado formato. "Subirlo" a la GPU. Texturizar un polígono con la textura anterior. Dibujar en pantalla.

Si yo solo tengo en cuenta uno de los extremos corro el peligro de escribir código que poco tiene que ver con la realidad y en vez de acercarme al objetivo, me voy alejando hasta que tengo que tirarlo a la basura o buscar maneras para que el código, que tanto trabajo me costo escribir, encaje con el otro extremo.

Es una buena idea unir los extremos lo antes posible por mas que la información no sea del todo correcto, en este caso, mostrar una textura en pantalla con el ancho y alto correcto, en la posición especificada y con un determinado color ya me ayuda a entender lo que implica esta tarea pudiendo tomar decisiones mas acertadas sobre el diseño del código interno. Haciéndolo mas entendible y modificable.

Por lo general, los extremos no suelen estar tan alejados uno de otro, pero el planteo también sirve para esos casos; por ejemplo, la IA de un NPC va a crear una entidad "Disparo" en un determinado momento. Aquí tengo por un lado la IA y por el otro el mundo y sus entidades.

 

OOP

La técnica de Exploración no es compatible con orientación a objetos,  un evangelista de OOP diría que primero hay que buscar las responsabilidades de las clases y como colaboran con las demas, diagramarlas y recién ahí ponerse a programar.
Por otro lado, Unir extremos si es posible ser usado sin importar el paradigma que uno use.

lunes, 16 de mayo de 2016

IMGUI

Este es un ejemplo de una GUI tradicional:
Button *btn = new Button("hola", width, height);
btn->addClickCallback(onBtnClick);
window->addButton(btn, x, y);

void onBtnClick(Button *btn)
{
}
En resumen, primero se crea un botón, después se agrega un callback, que va a ser ejecutado cuando el botón es presionado, y luego se lo agrega a una ventana, la cual va a dibujar todos sus hijos en la posición correcta.

Entra Modo inmediato

if (Button(State, "hola", X, Y, Width, Height))
{
 ...
}
Si, eso solo.

Ademas de la simpleza del uso (nada le gana a una llamada a una función) el API no retiene ningún puntero, no hay peligro de dangling pointers o contadores de referencia que no se limpiaron correctamente.

Otra ventaja de usar solo una función es el menor uso de memoria. Solo necesitamos guardar el estado del objeto que esta siendo utilizado en un momento dado, ya que solo se puede interactuar con un objeto a la vez.

En una GUI tradicional también se podría hacer esto último, pero como esta atado a un diseño orientado a objetos, seguramente el estado va a estar guardado en cada elemento.

Falacia del ejemplo trivial

Hacer un botón y un campo de texto es muy sencillo y todavía no tuve la oportunidad de hacer algo mas complejo, como una lista de objetos desplazables, por ejemplo.


Quizás, cuando llegue ese momento pueda comparar una GUI en modo inmediato vs una en modo "retenido".

jueves, 5 de mayo de 2016

TTF Rasterizer

Motivación

Programar un rasterizer de TTF es posiblemente una de las cosas mas inútiles que hay, debido a que existen varias soluciones listas para integrar en un engine.
Por otro lado, desde un punto de vista educativo, el aprendizaje obtenido es bastante sorprendente.

Documentación

El formato TrueType (posteriormente llamado OpenType) se puede encontrar en la página de Apple y de forma mas escueta en la de Microsoft.
Es realmente triste que estos documentos estén tan pobremente detallados, mas de una vez tuve que ver código de otros rasterizers para comprobar si estaba entendiendo bien la especificación.

Recuerdo que el mayor inconveniente que tuve es que el archivo esta en formato big-endian, y este detalle solo estaba mencionado en el documento de Microsoft.

La información en un archivo TTF esta dividida en tablas y sin ninguna duda la mas importante es la que contiene los glyphs, la representación visual de un carácter.
Otra tabla, por ejemplo, une el código de un carácter (Unicode) con su correspondiente glyph

Cada glyph es un conjunto de puntos ordenados, que al ser unidos forman el contorno de un símbolo.

Estos puntos pueden estar en la curva o fuera de la curva, estos últimos especifican puntos de control de una curva bezier. Uno esperaría entonces que siempre que haya un punto de control, que se encuentre en el medio de 2 puntos en curva, marcando el inicio y fin de la misma.
Bezier
Pero no siempre es así y a veces hay mas de un punto fuera de la curva, entonces uno se pregunta, ¿de dónde cuernos saca los puntos de inicio y fin?
Como estos puntos se pueden calcular, no necesitan estar explicitados en el glyph. Este otro "pequeño" detalle también es difícil de entender leyendo solo la documentación.

Las curvas de la e y la o son mas angostas de lo que deberían.

Pasitos de bebé

Lo primero que intenté hacer fue poder visualizar algo de la información del archivo TTF que quería parsear; ver que los valores obtenidos tuvieran algo de sentido y después poder dibujar algo que se pareciera a un carácter.

La imagen de abajo muestra un contorno usando los puntos de control como si fueran rectas y en la parte de arriba ya con las curvas bezier.


Finalmente, una vez que tuve el contorno de un carácter pude rasterizar ese glyph
Contorno y rasterizer.
De ninguna manera esta biblioteca esta completa, ni desde un punto de capacidades, velocidad ni robustez. Por lo pronto le falta
Ademas, hay una fina linea entre las capacidades que debería brindar un elemento de texto y no el rasterizer, entre otras:
  • Parsear códigos de control, como espacios, saltos de linea y tabulación
  • justificación de texto
  • Negrita e itálica

API

Desde el punto de vista del programador que va a dibujar texto solo hay que usar 2 funciones, una para cargar la TTF y otra para dibujar el texto en una posición con un tamaño y un color.

ttf_font *TtfFont = CreateTtf(game, "freesans.ttf");
...

DrawText(game, TtfTont, "Hola", Size, Pos, Black);

Como mencioné en mi post anterior, estoy alejándome de objetos, herencia y polimorfismo y es por eso que el API son solo funciones. Esto me abre la puerta a poder hacer mi propia IMGUI, que seguramente este escribiendo en en futuro cercano :)

martes, 3 de mayo de 2016

Bienvenidos

Desde hace un tiempo que estoy buscando otras maneras de programar inspiradas por los blogs de Casey muratori y charlas de Mike Acton.
Para poner a prueba estas teorias me inventé un proyecto testigo donde puedo jugar con estas ideas hasta convencerme que realmente funcionan y son mejores a las que estoy acostumbrado.

El juego/engine que estoy haciendo es un plataformero que por ahora tiene los siguientes features:
  • Hot reloading
  • Editor de niveles en juego
  • Colisiones en 2D estilo plataformero (rigid bodies)
  • Render en OpenGL con shaders
  • TTF Rasterizer
  • Guardado y cargado de 1 nivel
  • Asset reloading en run time (shaders y fonts)
  • Logs a archivos
  • Build en Linux y Windows
Modo edición, probando el rasterizer de fuentes
El juego esta programado íntegramente en C y C++, aunque lo único que uso de C++ es la sobrecarga de operadores y funciones. No uso objetos, ni polimorfismo, ni templates, ni la std e intento usar lo menos posible las bibliotecas estándar de C. Ahora sigo usando SDL, pero estoy de a poco reemplazándola por código propio.

No usar ninguna biblioteca me brinda una puerta a poder aprender muchas cosas tecnológicas que por la naturaleza de mi trabajo solo la puedo aprender en mi tiempo libre, como es el caso de un Rasterizer de TrueType Fonts.

En este blog voy a ir compartiendo experiencias que vayan surgiendo en esta búsqueda de una "mejor" forma de programar.