javisantana.com

Repaso 2013

repaso 2013 y visión para el 2014

Ya casi hemos terminado el 2013 y toca la típica review y objetivos para el año que viene.

Este año ha sido intenso, algunas de las razones son las siguientes:

No creo ni en la suerte ni en el destino pero mi intuición me dice que el año que viene va a ser movidito. Sólo espero sacar los productos que tengo pendientes y ver como evolucionan, mejorar mi puto inglés de una vez por todas (objetivo recurrente durante los últimos 6 años), pasarmelo lo mejor posible y quizá cerrar alguna que otra herida del pasado.

trabajar en remoto, the bad parts

Qué bonito es trabajar desde casa, leer el libro de 37signals y asentir, sacar pecho “yo trabajo desde casa, soy el puto amo” y tantas otras cosas. Pero como todo, generalizar es la receta perfecta para darte la hostia. Cada persona, empresa y situación es diferente.

Estas son algunas de las cosas que he sufrido y sufro durante el tiempo que he trabajado en remoto:

animaciones en javascript

Animaciones en javascript

Ahora que javascript está tan de moda, es tan potente y nos permite hacer lo que código nativo nos permitía hacer en un 486 los desarrolladores estamos empezando a hacer cosas algo más interesantes que poner texto y enlaces como juegos o animaciones. Lo cierto es que a pesar de que javascript sea aún lento para hacer cosas con mucha carga matemática, tenemos a nuestra disposición tarjetas gráficas muy potentes y gracias a las mejoras que últimamente han incluído en los navegadores, podemos usarlas directamente (con WebGL) o indirectamente (CSS3, SVG, canvas)

El objetivo de este artículo no es explicar como renderizar rápido ni de como aprovechar la GPU si no de como hacer una animación.

paso 1: setInterval

Lo primero que se nos ocurre es el típico setInterval. Seguro que conoces la función, pero básicamente lo que hace es llamar a una función que le especificas cada cierto tiempo. Por ejemplo, la animación sería tal que así:

        setInterval(function () {
            update()
            render();
        }, 20)

    

Esto llamará a la función cada 20ms, esto es 50 frames por segundo (FPS). Para la mayoría de casos cumple perfectamente. Pero qué pasa si la función update tarda 30ms en terminar? Realmente no sé que es lo que hace el navegador, supongo que cada uno tendrá su política, pero cabe pensar que con el tiempo se empezarán a aculumar eventos de llamada a esa función ya que no es capaz de terminar en menos de 20ms.

Aunque el navegador fuese muy listo y gestionase eso perfectamente no podemos hacer la animación correctamente porque no sabemos el tiempo que ha pasado de un frame a otro.

paso 2: setTimeout

Para evitar que se nos acumulen llamadas por que la función es lenta vamos a pedir renderizar un frame solo cuando hayamos terminado:

        var logic = function () {
            update()
            render();
            setTimeout(logic, 20);
        };
        setTimeout(logic, 0);

    

Vale, ahora ya no se acumulan eventos y si la función update o render tarda más que esos 20ms no pasará nada, sólo actualizaré la animación cuando el frame anterior haya terminado. Mejor que antes pero aún estamos animando como unos tristes.

paso3: controlando la animación

Imaginemos que estamos controlando una animación de una imagen moviendose por la pantalla. Queremos que en 1 segundo se mueva 500px:

        var img = new Image();
        img.src = 'image.png';
        img.style['position'] = 'absolute';
        var pos = 0;
        img.style['left'] = pos + 'px';

        var logic = function () {

            // update
            pos += 500*0.02;
            img.style['left'] = pos + 'px';

            // browser will render the img on style change
            //render();
            if(pos < 500) {
                setTimeout(logic, 20);
            }
        };
        setTimeout(logic, 0);

    

En un segundo, a 50FPS habremos dibujado 50 frames con lo cual pos será 500 al pasar un segundo. No está mal, pero imaginemos que tenemos una máquina lenta, tan lenta que no es capaz de actualizar a 50FPS. En ese caso tendremos que la imagen llegará a la posición 500px, pero en más de un segundo. No estamos controlando el tiempo.

Para ello podemos usar el tiempo transcurrido desde el último frame. Vamos, lo que se lleva aplicando en videojuegos simples toda la vida:

        var t0 = new Date().getTime();
        var logic = function () {
            var t1 = new Date().getTime();
            var dt = t1 - t0;
            t0 = t1;

            // update
            pos += 500*dt;
            img.style['left'] = pos + 'px';

            // browser will render the img on style change
            //render();
            if(pos < 500) {
                setTimeout(logic, 20);
            }
        };
        setTimeout(logic, 0);

    

Ya está, ahora aunque la máquina sea lenta controlamos la animación correctamente

paso 4: requestAnimationFrame

Ahora podemos controlar un poco mejor la animación gracias a requestAnimationFrame. Para variar es una función que no es standard y cada navegador la implementa con el nombre que le da la real gana.

En resumen, esta función llama a la función que nostros queramos cuando el navegador vaya a renderizar. Eso es bueno, por que si nosotros llamamos 1000 veces por segundo a esta función como mucho llamará a la animación el máximo que pueda actualizar. Asumamos que esto es bueno, aunque si estás haciendo algo medio serio la actualización de la lógica puede ir parcialmente desacoplada del renderizado (luego veremos un ejemplo).

Así que la cosa quedaría tal que así:

        var logic = function () {
            var dt = //calculate dt
            uddate(dt);
            render()
            requestAnimationFrame(logic);
        }
        requestAnimationFrame(logic);
    

Perfecto, además podemos llamar a requestAnimationFrame desde cualquier lado para que comience la animación.

Normalmente (la ley del copy & paste así lo dice) se define requestAnimationFrame tal que así:

        var requestAnimationFrame = window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                function(a){setTimeout(a,20);};
    

aunque en mi opinión debería ser algo así:

        function createRequestAnimationFrame(fn) {
            var rendered = true;
            return window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                function(a) {
                    if(rendered) {
                        rendered = false;
                        setTimeout(function() {
                            fn();
                            rendered = true;
                        }, 20);
                    }
                };
        }

        logic = function() {
            update();
            myAnimationRequestFrame();
        }
        myAnimationRequestFrame = createRequestAnimationFrame(logic);
        myAnimationRequestFrame();

        element.onclick = function() { 
            // logic
            myAnimationRequestFrame();
        }
    

De este modo aunque llamemos 1000 veces a myAnimationRequestFrame sólo se llamará a la animación cuando toque

paso 5: casos extremos

Qué pasa si el navegador es muy lento. Bueno, lo ideal es quitar la animación, pero claro, no sabemos a priori si la máquina es lenta (bueno, puedes comprobar si es Firefox). Para evitar que todo se vaya al traste quizá lo mejor sea limitar la animación para que no se vaya de madre.

        var logic = function () {
            var dt = //calculate dt
            dt = Math.min(0.05, dt);
            update(dt);
            render()
            requestAnimationFrame(logic);
        }
        requestAnimationFrame(logic);
    

De esta forma si la animación es muy lenta la ralentizamos pero así estamos seguros de que la lógica no falla. También nos protege del caso en el que el usuario cambie de tab (requestAnimationFrame no asegura que se llame al callback si la pestaña donde se ejecuta el código no está activa), al volver a activarse el dt será muy grande y podría desestabilizar en caso de no controlar el dt. Imagino que habrá algún API para controlar si la pestaña es activa…

Imaginemos que la función update hace algo más complejo que sumar, por ejemplo una integración numérica. En ese caso si el dt es muy grande la integración se puede ir al traste, necesitamos dt suficientemente pequeños. Ese caso quedaría resuelto también.

Por ejemplo, queremos que en el ejemplo anterior la imagen vaya hasta 500 pero suavemente:

        var smooth = 0.1;
        var distance_to_target = 500 - pos;
        pos += distance_to_target*smooth*dt;
    

Si dt es muy grande lo que pasará es que esa función empezará a oscilar haciendo un efecto muelle en vez de llegar suavemente o incluso oscilará hasta el infinito si dt es muy grande (a que os suena de de cuando érais jóvenes y estudiabais? si eres teleco/industrial y no te suena vuelve a la carrera).

y último: haciendo las cosas realmente bien

Mejor que explicarlo aquí, id a este artículo del mítico Javier Arévalo y grabadlo en vuestra mente:

Fixed time step loop

Comentarios y trolleos son bienvenidos

@javisantana

CORS en la vida real

No voy a explicar qué es CORS, la página de la wikipedia y cientos de web a golpe de búsqueda te lo explicarán. A mi gusta verlo como el gran bugfix del desarrollo web. Este post trata de explicar algunas de las dificultades que nos hemos encontrado en el uso de CORS después de dos años usándolo en producción.

Hasta ahora hemos estado usando JSONP y otros hacks para consumir servicios de dominios diferentes al que se ejecuta nuestra web app. Aunque tenga una siglas más o menos aparentes, detrás de ellas se esconde un hack horrible, que nunca debería haber existido. Alquien pensó que ya era hora de cambiar eso, que debería haber una tecnología que permitiese coger datos de dominios usando AJAX que no son el nuestro de forma segura. Y ahí entro CORS.

Nosotros en cartodb y otros proyectos de vizzuality usamos CORS cosas muy variopintas:

- poder manipular los píxeles de las imágenes (para hacer análisis raster en mapas)
- poder hacer POST y con enviar más datos de los que permite una petición GET
- consumir datos de nuesta API desde aplicaciones web sin necesidad de un backend

Decir que en ningún caso hemos usado autentificación, desconozco lo completo que será el tema, pero imagino que tendrá sus cosillas (hay tokens CSRF por ejemplo) de por medio.

Cuando digo que tenemos en producción quiero decir que hemos estado más de 9 meses basando nuestra arquitectura pública de mapas en CORS, quiero decir, ya hemos llegado a la fase de “esto functiona” a “vamos puliendo los detalles”. Cuando digo producción, digo millones de usuarios y en sitios con mucho tráfico y usuarios diferentes, por ejemplo, wikileaks, blog de twitter, lemonde, the guardian y otros con mucho más tráfico que no recuerdo.

Los problemas que nos hemos encontrado:

- INTERNET EXPLORER. Sí, las versiones anteriores no lo soportan y dependiendo de como le de [1] IE10 a veces reporta que tampoco lo soporta. Así que tendrás que tener un fallback ( leer la siguiente)
- Vas a necesitar un fallback: no puedes basar tu arquitectura en CORS, aunque los navegadores que soporten sean compatibles, hay muchos otros problemas (leer siguientes), así que vas a tener que tragar con JSONP o proxy en tu servidor.
- Algunos proxies eliminan las cabeceras de CORS. Cuando tu navegador web hace un OPTIONS antes de hacer la petición de verdad (necesita saber si puede de verdad hacerlo) si eliminan las cabeceras el navegador devolverá un error.
- El número de peticiones se multiplica. Hay que hacer un OPTIONS antes de hacer la petición real. No es que sea mucha carga, pero es una petición más que tus servidores tienen que tragar (no es cacheable según el standard y efectivamente los navegadores no las cachean), hay cierto lag y es un fastidio cuando estás desarrollando.
- Ahora ese problema ya no existe, pero durante este tiempo tanto chrome como FF han cambiado la forma de funcionar.

Para terminar, nosotros basamos todo nuestro API de mapas en CORS y teníamos como fallback JSONP. Hace cosa de 1 mes hemos pasado de usar JSONP como herramienta principal y CORS como fallback. Falta aún un tiempito para poder basar todo el sistema en CORS, pero desde luego es un gran avance en el desarrollo web.

Ya hay mucha gente incluyendo las cabeceras de CORS en sus servicios permitiendo hacer algunas cosas interesantes, por ejemplo, los tiles de los mapas de google los incluyen o algunos servicios de autentificación, por ejemplo openstreemap, permitiendo hacer oauth desde javascrip

[1] si está en modo compatibilidad IE10 no funciona CORS, pero incluso a veces en modo normal, tampoco.

Documentation template for small projects

During the Torque development (a small javascript library to create animations on maps) we decided to use a markdown template to write there the reference API documentation:

https://gist.github.com/javisantana/6413167

some goodies about it:

- plain text

- easy to read without format

- plenty of tools to transform to html (or whatever)

- easy to parse if the project grows and it need to be transformed to something better

- nice starting point. Useful when you don’t know how/where to start

While I was coding I always had a vim window open with the documentation so I can update it at the same time I was changing the interface in the code. It’s the only way I’ve found to keep the API documentation up to date.

At the end this is another example of “stone soup" story. In this case putting this file in a doc folder and opening it every time you start to code remind you need to update it.