Skip to content

html5

HTML5: Web Workers (1/2)

Web Workers son tareas JavaScript que pueden lanzarse en segundo plano, a modo de threads. Su objetivo es permitir que las aplicaciones web ejecutadas en el cliente tengan la posibilidad de lanzar hilos de ejecución concurrentes con una gran carga de trabajo y duración indeterminada.

Su propósito principal es que se puedan crear tareas que funcionen al margen del proceso normal de gestión de eventos de los controles de la interface de usuario, evitando bloquear la página durante su ejecución, y que no aparezca la típica ventana de aviso del navegador diciendo que se ha superado el tiempo máximo de ejecución para un script.

El API resulta bastante sencillo y la documentación oficial está llena de ejemplos. De hecho, lanzar un worker «normalito» es algo tan simple como escribir la siguiente línea de código:

var worker = new Worker("worker.js");

Sólo se pueden lanzar scripts de esa forma. Es decir, indicando la URL donde se encuentra el código JavaScript a ejecutar. No se puede utilizar ni «javascript:» ni «data:». La URL resultante del nombre del fichero externo debe tener el mismo esquema que la página original que motivó la ejecución del script. Por ejemplo, es imposible desde una página cargada desde una dirección «https:» lanzar un script de la misma dirección pero con «http:». Y es más, el único lenguaje soportado es JavaScript, el atributo de tipo MIME es ignorado, el navegador asume siempre que el código está escrito en JavaScript.

Matar de manera inmediata un worker que se encuentra en ejecución es tan sencillo como llamar a su función «terminate». Algo que se puede hacer por ejemplo desde un botón a petición del usuario:

<button type="button" onclick="worker.terminate();">Kill</button>

Por su parte, la forma preferida de terminar un worker desde dentro debería ser llamando a su propia función «close». Pero teniendo muy en cuenta que esta función no fuerza a la terminación de forma brusca del código del worker en el punto que se produce la llamada a dicha función de cierre, como lo hace la función «terminate». El navegador ejecutará las líneas de código siguientes, si las hubiera.

while(true){
  self.close(); //El worker seguirá ejecutándose
}

La función «close» detiene el lanzamiento de eventos, previene el disparo de timers, descarta las notificaciones pendientes de operaciones asíncronas, etc.

Dentro de un worker se puede hacer prácticamente cualquier cosa. Pueden procesar eventos, callbacks, e incluso es posible crear otros workers. La única limitación lógica es que no tienen un contexto de navegación asociado. No pueden acceder al DOM, window, document o parent. Pero sí a navigator, location, XMLHttpRequest, timers, applicationCache o Web SQL database. Y una opción interesante es que tienen la posibilidad de ejecutar el código de otros scripts mediante la función «importScripts».

importScripts("script1.js"); //De uno en uno...
importScripts("script2.js");
importScripts("script3.js", "script4.js"); //.. o varios a la vez

Para capturar posibles errores no controlados que se produzcan dentro del thread hijo, y que se quieran capturar desde el padre, se puede indicar un manejador de eventos en su atributo «onerror». Esta función recibe un objeto con el detalle concreto del error: nombre del fichero, número de línea y mensaje de texto.

worker.onerror = function(event){
  alert(event.filename + " [" + event.lineno + "]: " + event.message);
};

Lógicamente, la parte más interesante de este API es la que permite comunicar el hilo padre principal con el worker hijo. Para que el padre pueda darle trabajo al hijo, y para que el hijo pueda comunicarle al padre los resultados de su trabajo. La comunicación se realiza a través de mensajes y manejadores. Y más concretamente, para enviar mensajes se utiliza la función «postMessage», y para recibir mensajes se utiliza el manejador «onmessage».

Una comunicación básica entre padre e hijo tendría cuatro pasos:

1. El hilo padre manda un mensaje (evento) al worker:

worker.postMessage("Información para el worker");

2. El worker recibe el mensaje del padre en el atributo «data» del evento:

self.onmessage = function(event){
  //Recibe "Información para el worker" en event.data
};

3. Cuando el worker termina su proceso envía un mensaje (evento) al padre:

self.postMessage("Información para el padre");

4. El padre recibe el mensaje del hijo en el atributo «data» del evento:

worker.onmessage = function(event){
  //Recibe "Información para el padre" en event.data
};

Y esto es básicamente a mi juicio lo más importante. Recomiendo mirar los ejemplos que vienen dentro de la propia especificación para ver situaciones un poco más complejas.

Flash y HTML5, o no

Flash Apple Android Windows MobilePues sí, este es otro post sobre Flash y HTML5, o no.

Antecedentes
Últimamente anda el patio bastante revuelto por toda una cadena de acontecimientos (en orden no cronológico):
1) La presentación de la nueva versión de Adobe Flash Professional CS5 y, no menos importante, de Flex 4 y el rebautizado Flash Builder 4.
2) La preparación de la nueva versión Adobe Flash Player 10.1 con soporte para su ejecución en dispositivos móviles.
3) El cambio de licencia de Apple para limitar las herramientas de desarrollo que pueden utilizarse para producir aplicaciones que se ejecuten en sus plataformas («Applications must be originally written in Objective-C, C, C++, or JavaScript as executed by the iPhone OS WebKit engine»).
4) El soporte cada vez más limitado de Flash en dispositivos móviles con Android y Windows Mobile, frente a las enormes expectativas originales.
5) El vídeo de la presentación de DreamWeaver CS5 donde se ejecuta una animación realizada en Flash dentro una página web mediante JavaScript sin necesidad de tener el plugin instalado.
6) La aparición de proyectos personales como Gordon para la ejecución en navegadores de animaciones hechas en Flash utilizando sólo JavaScript.
7) La conversión del motor de Quake II para su ejecución con Google Web Toolkit mediante JavaScript en la parte cliente, con cierta tecnología auxiliar, y Java en la parte servidora.

Ufffff…….

Dejo a cada cual la interpretación de las decisiones empresariales y su conveniencia, yo prefiero centrarme en la parte técnica. Y más concretamente en la ejecución nativa de aplicaciones Flash sobre un navegador con tecnologías estándar frente al uso de plugins específicos.

El formato SWF
El punto de partida es que las herramientas que desarrolla la propia Adobe para la creación de «películas» Flash están muy establecidas en el mercado por su facilidad de uso y alta productividad. Existe una base enorme de aplicaciones ya desarrolladas bajo esta tecnología, y se quiere reducir la inversión necesaria en tiempo y dinero para ejecutarlas en otros entornos distintos para los que fueron originalmente diseñadas, como el caso de los dispositivos móviles.

Ahora bien, ¿qué tiene dentro un fichero «Flash»?, y sobre todo, ¿cómo podemos sustituirlos utilizando tecnología estándar?

Los ficheros que contienen las aplicaciones para Flash tienen extensión SWF. Y las especificaciones técnicas de este formato de archivos son definidas por Adobe que las hace pública con cada nueva versión. Muy a grandes rasgos, lo que contiene un fichero SWF es una cabecera y una serie de bloques denominados tags. Cada tag es de un tipo que determina la información que contiene y como debe interpretarse. Hay tags que contienen recursos, como imágenes, sonidos, músicas, vídeos, fuentes de texto, o cualquier tipo de información binaria arbitraria. Otros tags que contienen la definición y posicionamiento de determinados objetos, como botones, figuras, textos, o estilos creados con el IDE. Y otros últimos tags que contienen acciones para el control de la reproducción, como saltar a un determinado frame por ejemplo, e incluso código compilado en ActionScript.

En consecuencia, simplificando muchísimo el asunto, el plugin de Adobe consta de al menos cuatro elementos principales: un parser de ficheros SWF, un motor de control y renderizado, una máquina virtual para ejecutar ActionScript, y un conjunto de librerías que proporcionan el runtime que utiliza el código ActionScript.

Un Parser
Pero vayamos por partes. ¿Se puede parsear un fichero SWF desde JavaScript?

En líneas generales, la tarea de hacer un parser de ficheros SWF no es complicada, y lo digo por experiencia propia. No sólo porque exista un documento que describa en detalle el formato, sino porque hay bastante código fuente desarrollado que puede utilizarse como referencia. Empezando por las clases Java de SWFUtils liberadas como código abierto por parte de Adobe dentro del Flex SDK. Montar el proyecto y empezar a depurar la carga de un fichero SWF paso a paso es cuestión de minutos. Y de hecho, es bastante recomendable hacerlo, ya que los fuentes contienen información adicional que no se encuentra en la documentación oficial.

Hacer un parser equivalente en JavaScript no es complicado, y vuelvo a hablar por experiencia propia. Aunque en este aspecto la referencia, e hilo conductor de este post, es el proyecto Gordon de Tobias Schneider, también de código abierto. Lo que hace Gordon es una petición del fichero SWF al servidor web a través de un objeto XMLHttpRequest y procesa el bloque de datos recibidos. Los ficheros SWF están comprimidos en formato zip, por lo que antes los descomprime, también desde JavaScript, mediante la librería inflate de Masanao Izumo un hack consistente en añadir una cabecera PNG a los datos y asignándoselos a una etiqueta <img>. Una vez descomprimido en memoria se extrae y procesa cada tag de forma individual.

Los Recursos Embebidos
A medida que se va leyendo el contenido de un fichero SWF, ¿un navegador puede procesar de forma nativa todos los recursos embebidos dentro de un fichero SWF? Es más, ¿se pueden instanciar de forma dinámica con JavaScript?

Los formatos de imágenes soportados por Flash son JPEG, PNG y GIF. En HTML se pueden referenciar con la tradicional etiqueta <img>. Y con JavaScript se pueden leer e instanciar de forma dinámica utilizando el elemento canvas de HTML5 a través de su CanvasRenderingContext2D para acabar referenciándolas luego por su uri.

var context = canvas.getContext("2d");
var imageData = context.createImageData(width, height);
imageData.data = bitmap;
context.putImageData(imageData, 0, 0);
var uri = canvas.toDataURL();

Los sonidos se pueden reproducir en HTML5 mediante la etiqueta <audio>. Aunque otro cantar son los formatos soportados y los codecs disponibles. Flash soporta ADPCM, MP3, Nellymoser y Speex. En HTML5 se pueden embeber sonidos mediante una referencia en el HTML del cliente, e incluso reproducirlos usando streaming.

<audio src="audio.spx" type="audio/ogg; codecs=speex">

Lo que parece estar más limitado actualmente en JavaScript es la creación dinámica de sonidos, ya que hasta donde yo alcanzo, no existe un método estándar que permita instanciarlos en tiempo de ejecución con el objetivo de obtener una referencia que alimente al atributo «src» de la etiqueta. Aunque incluso para esto hay una solución: el uso de «data URI», tal y como se describe en el RFC2397. Es decir, serializando el stream de bytes del sonido en una cadena de texto en base 64.

src="data:video/ogg,OggS%00%02%00%00 ..."

Como nota al margen, comentar que Google utiliza esta técnica con las pequeñas imágenes que presenta a veces en la página de resultados de su famoso buscador.

Flash soporta vídeo en formato Sorenson H.263. Y en HTML5 se pueden reproducir mediante la etiqueta <video>. Aunque la «guerra de los codecs de vídeo» ha dejado un poco coja la especificación del estándar, al no exigir ninguna implementación concreta. En cualquier caso, en HTML5 los ficheros de vídeo se referencian de igual forma que los ficheros de audio.

<video src="videofile.ogg">

Tanto los sonidos como los vídeos son tratados de una manera uniforme por HTML5, con funciones accesibles en JavaScript que permiten gestionarlos (play, pause, seek, volume, …), y eventos relacionados con los cambios que se producen en los mismos (onplay, onseeked, onvolumechange, …)

var audio = new Audio("audio.ogg");
audio.volume = .5;
audio.play();

Las fuentes de texto utilizadas dentro de un fichero SWF pueden tomarse del PC local donde se ejecute el reproductor, o pueden ir embebidas dentro del propio fichero SWF. Y aunque nada haga sospecharlo, aquí es donde el asunto se pone interesante. Hasta donde yo alcanzo, no se pueden instanciar fuentes de forma dinámica con HTML5. La definición de las fuentes dentro de los navegadores es cosa de CSS. Con la propiedad «font-family» se pueden tomar las fuentes del PC local, y con la regla «@font-face» de un servidor web. Y repito, esto no es parte de HTML5, sino de CSS3.

@font-face {
  font-family: customFont;
  src: url(customFont.ttf);
}

Y más interesante todavía. Si que se pueden generar fuentes de texto de forma dinámica con JavaScript, pero utilizando SVG, una tecnología totalmente distinta, mucho más antigua, y que tampoco es parte de HTML5.

En los ficheros SWF se almacenan las secuencias de trazos que deben dibujarse para representar cada carácter de cada fuente de texto de forma individual. Y esas «secuencias», llamadas tradicionalmente «paths», juegan un papel importante dentro de todo este circo tecnológico. La definición de fuentes en SVG se realiza de una forma bastante natural a partir de ellos. SVG es una tecnología para gráficos vectoriales, y en este aspecto es mucho más similar a la idea original de Flash que el elemento canvas de HTML5.

El resto de recursos que pueden encontrarse dentro de un fichero SWF son las propias películas en sí que se construyen con el IDE. La parte más importante de Flash. La definición de cada fotograma de cada película desglosada hasta el mínimo detalle. El conjunto de cada primitiva básica que se utiliza (línea, rectángulo, círculo, …) junto con su estilo (color, trama, efecto, …) y su matriz de transformación (traslación, rotación, escalado, …). Un vector de objetos y comandos que nuevamente tiene un equivalente natural en los paths de SVG.

En SVG se puede indicar una lista de comandos de forma abreviada asociada a un elemento:

<path d="M 100 100 L 300 100 L 200 300 z" />

El equivalente con HTML5 es ir llamando función a función:

context.beginPath();
context.moveTo(10, 100);
context.lineTo(300, 100);
context.lineTo(200, 300);
context.closePath();

El hecho de que el formato final en que se almacenan las películas de Flash se representen mejor en SVG que utilizando el elemento canvas de HTML5 es bastante significativo. Quizás deba ser este el camino a seguir. Y me refiero al hecho de que la solución puede pasar por usar una tecnología estándar específica para la representación de gráficos vectoriales en el navegador, no sólo el elemento canvas de HTML5 de propósito más general y posiblemente más orientado a la gestión de gráficos rasterizados.

SVG además soporta animaciones, eventos, e incluso la ejecución de scripts.

Todo lo demás
Aún quedan un par de puntos interesantes por tratar. ¿Puede un navegador interpretar código ActionScript compilado? ¿E implementar todas las clases de las librerías del runtime de Flash?

El plugin de Flash contiene una interesante pieza de código llamada AVM2 (ActionScript Virtual Machine 2). La máquina virtual encargada de interpretar el código ActionScript 3 compilado que se encuentra dentro de los ficheros SWF. Adobe, en un movimiento bastante interesado interesante por su parte, lo liberó como código abierto cediéndolo a la fundación Mozilla bajo la forma de un proyecto de colaboración llamado Tamarin. De igual forma, las especificaciones técnicas del lenguage también son públicas.

A Flash siempre le estará faltando la ejecución nativa de ActionScript 3 en los navegadores. Liberando su máquina virtual se facilitaba que los navegadores la incorporasen permitiendo dicha ejecución, pero eso es algo que de momento no ha ocurrido. Aunque no estoy yo muy convencido de que vayamos a tener JavaScript como lenguaje de referencia para siempre en los navegadores. La etiqueta <script> de HTML se creó para dar cabida a cualquier tipo de lenguaje, y ya va siendo que la utilicemos. ActionScript es bastante conocido por seguir el estándar ECMA-262, pero no tanto por implementar también el ECMA-357, lo que añade tipos, funciones y operadores para procesar XML de forma nativa dentro del propio lenguaje.

Realizar un intérprete de ActionScript en JavaScript es factible, pero el rendimiento que se pueda obtener es discutible. Es algo sobre lo que no tengo una idea muy clara definida. Habrá que esperar a que algún proyecto con cierta solvencia lo implemente, posiblemente Gordon, o hacer algunas pruebas por mi cuenta y riesgo.

Y por último, la pieza que completa el puzzle es el runtime de Flash. Quien esté dispuesto a implementar un visor de ficheros SWF ha de estar dispuesto a implementar todos los paquetes de clases de máximo nivel que ofrece el Flash Player API, o al menos los de uso más frecuente. Tarea que ya han acometido proyectos como Gnash, el reproductor GNU de ficheros SWF.

Concluyendo
La línea argumental que se ha seguido hasta el momento se basa en realizar todo el procesamiento de los ficheros SWF en la parte cliente, como reemplazo natural del plugin de Flash. No obstante, también cabe la posibilidad de que los ficheros se «parseen» en el servidor, con la idea de extraer los recursos que tienen embebidos y utilizar sus urls correspondientes dentro la página HTML del cliente de la forma tradicional. El problema es que esto conllevaría a que se produciesen muchas más peticiones del cliente al servidor, un tráfico de red mucho mayor, y se perdería las ventajas de tener todo en un único fichero siguiendo el formato compacto de Adobe. Pero por otra parte, realizar todo el proceso en el cliente con JavaScript puede ser prohibitivo.

Pero si el formato SWF no se adapta a nuestras necesidades, y resulta un tanto pesado su tratamiento para conseguir unas simples animaciones, ¿por qué no usamos otro? ¡Precisamente es lo que está haciendo Adobe! En el famoso vídeo, sobre el minuto 04:14, se puede ver que en realidad la animación hecha en Flash se exporta a un fichero en formato FXG. Un formato definido por la propia Adobe, y cuyas especificaciones técnicas son públicas.

El formato FXG copia sigue la línea marcada por SVG. Nuevamente SVG. Y hace que sea más fácil de procesar por parte de JavaScript. En el vídeo se aprecia la llamada a una función renderFlashFXG importada de un fichero «renderFXG.js».

Cambiar el formato SWF a otro que tenga más cosas preprocesadas de cara a su tratamiento por parte de JavaScript puede ser la solución. Puede que no sea tan compacto como el original, pero con el previsible e inevitable aumento del ancho de banda disponible esto no debería importar.

Un apunte final
En cualquier caso, lo que no deberían perder de vista algunos desarrolladores, sobre todo los que basan su modelo de negocio en la publicidad online, típico en los juegos Flash, es que lo que les interesa controlar no es sólo el número total de terminales vendidos por una determinada empresa, sino tambíen los que mayor tráfico de red generan, ya que deberían ser estos los que le permitan obtener mayores beneficios. Y a este respecto, el aumento del tráfico de red generado por terminales con Android es algo muy a tener en cuenta.

Confetti, mi nuevo juego

Acabo de subir un nuevo juego terminado a la web con el nombre de Confetti. Por la imagen adjunta creo que resultará claro entrever el género al que pertenece.

Siguiendo la costumbre de los últimos juegos, este también está hecho en JavaScript y puede jugarse directamente desde el navegador. Sólo lo he probado en Firefox 2 e Internet Explorer 6 bajo Windows XP, en otras configuraciones puede no visualizarse correctamente. En los próximos días trataré de hablar un poco más de él.