Haber generado un motor de juego corriendo en JavaScript con C++, usando WebGL para el renderizado es un gran logro, todo gracias a las herramientas que Mozilla ha desarrollado para hacerlo posible.

Muchas interrogantes han surgido a raíz del lanzamiento de Unreal Engine 3 para Asm.js, ¿Cómo funciona en los navegadores?, ¿por qué es importante?, ¿debe ser mi pan de cada día?, ¿funciona en todos los navegadores, hay alguna excepción?, en fin todo lo relacionado al tema.

¿Qué es Asm.js?

Se define como un subconjunto estricto de JavaScript que se puede utilizar como un nivel bajo, el idioma de destino eficiente para los compiladores. Este sublenguaje eficazmente describe una máquina virtual segura para lenguajes de memoria que no lo son, como en el caso de C o C++.

Una combinación de validación estática y dinámica que permiten a los motores de JavaScript emplear un ahead-of-time (AOT) la estrategia de optimización de compilación de código asm.js válida.

Asm.js proviene de una nueva categoría de aplicaciones de JavaScript, un género completamente innovador creado por Emscripten, un proyecto visionario de Mozilla.

La web 3D más cerca que nunca con asm.js
La web 3D más cerca que nunca con asm.js

¿Cómo funciona Emscripten?

Lo primero que hace es tomar el código en C/C++, lo pasa a través de LLVM, convirtiéndolo en un bytecode generado en JavaScript (específicamente Asm.js, un subconjunto de JavaScript).

Si el código compilado Asm.js está siendo renderizado es más probable que sea manejado por WebGL (e interpretado usando OpenGL). De esta manera el conjunto de procesos lineales está técnicamente haciendo uso de JavaScript y el navegador. Esto se hace para que el código compilado Asm.js pueda correr tan rápido como sea posible. Como Asm.js es un subconjunto de JavaScript está muy limitado en lo que puede hacer y cómo se puede operar.

Objetivo de Asm.js

Según David Herman (Senior Researcher de Mozilla Research) es hacer de la web abierta una máquina virtual convincente, compilar otros lenguajes y plataformas. Por ahora enfocándose en la compilación de código de bajo nivel como C y C++. A largo plazo, esperan añadir soporte para un mayor nivel de construcciones con objetos estructurados y recolección de basura; plataformas como la JVM y NET.

Como mencionamos anteriormente Asm.js es sólo JavaScript, no hay complementos especiales en el navegador o características necesarias para hacer que funcione (un navegador que es capaz de detectar y optimizar el código Asm.js obviamente se ejecutará más rápido).

Funcionamiento de Asm.js

Vamos a ver como funciona y cuales son las limitantes de Asm.js con una función extraída de un módulo compilado (versión parcial de BananaBread).

function Vb(d) {

d = d | 0;

var e = 0, f = 0, h = 0, j = 0, k = 0, l = 0, m = 0, n = 0,

o = 0, p = 0, q = 0, r = 0, s = 0;

e = i;

i = i + 12 | 0;

f = e | 0;

h = d + 12 | 0;

j = c[h >> 2] | 0;

if ((j | 0) > 0) {

c[h >> 2] = 0;

k = 0

} else {

k = j

}

j = d + 24 | 0;

if ((c[j >> 2] | 0) > 0) {

c[j >> 2] = 0

}

l = d + 28 | 0;

c[l >> 2] = 0;

c[l + 4 >> 2] = 0;

l = (c[1384465] | 0) + 3 | 0;

do {

if (l >>> 0 < 26) {

if ((4980736 >>> (l >>> 0) & 1 | 0) == 0) {

break

}

if ((c[1356579] | 0) > 0) {

m = d + 4 | 0;

n = 0;

while (1) {

o = c[(c[1356577] | 0) + (n << 2) >> 2] | 0;

do {

if (a[o + 22 | 0] << 24 >> 24 == 24) {

if (!(Vp(d, o | 0) | 0)) {

break

}

p = (c[m >> 2] | 0) + (((c[h >> 2] | 0) - 1 | 0) * 40 & -1) + 12 | 0;

q = o + 28 | 0;

c[p >> 2] = c[q >> 2] | 0;

c[p + 4 >> 2] = c[q + 4 >> 2] | 0;

c[p + 8 >> 2] = c[q + 8 >> 2] | 0;

c[p + 12 >> 2] = c[q + 12 >> 2] | 0;

c[p + 16 >> 2] = c[q + 16 >> 2] | 0;

c[p + 20 >> 2] = c[q + 20 >> 2] | 0;

c[p + 24 >> 2] = c[q + 24 >> 2] | 0

}

} while (0);

o = n + 1 | 0;

if ((o | 0) < (c[1356579] | 0)) {

n = o

} else {

break

}

}

r = c[h >> 2] | 0

} else {

r = k

} if ((r | 0) == 0) {

i = e;

return

}

n = c[j >> 2] | 0;

if ((n | 0) >= 1) {

i = e;

return

}

m = f | 0;

o = f + 4 | 0;

q = f + 8 | 0;

p = n;

while (1) {

g[m >> 2] = 0.0;

g[o >> 2] = 0.0;

g[q >> 2] = 0.0;

Vq(d, p, f, 0, -1e3);

n = c[j >> 2] | 0;

if ((n | 0) < 1) {

p = n

} else {

break

}

}

i = e;

return

}

} while (0);

if ((c[1356579] | 0) <= 0) {

i = e;

return

}

f = d + 16 | 0;

r = 0;

while (1) {

k = c[(c[1356577] | 0) + (r << 2) >> 2] | 0;

do {

if (a[k + 22 | 0] << 24 >> 24 == 30) {

h = b[k + 14 >> 1] | 0;

if ((h - 1 & 65535) > 1) {

break

}

l = c[j >> 2] | 0;

p = (c[1384465] | 0) + 3 | 0;

if (p >>> 0 < 26) {

s = (2293760 >>> (p >>> 0) & 1 | 0) != 0 ? 0 : -1e3

} else {

s = -1e3

} if (!(Vq(d, l, k | 0, h << 16 >> 16, s) | 0)) {

break

}

g[(c[f >> 2] | 0) + (l * 112 & -1) + 56 >> 2] = +(b[k + 12 >> 1] << 16 >> 16 | 0);

h = (c[f >> 2] | 0) + (l * 112 & -1) + 60 | 0;

l = k + 28 | 0;

c[h >> 2] = c[l >> 2] | 0;

c[h + 4 >> 2] = c[l + 4 >> 2] | 0;

c[h + 8 >> 2] = c[l + 8 >> 2] | 0;

c[h + 12 >> 2] = c[l + 12 >> 2] | 0;

c[h + 16 >> 2] = c[l + 16 >> 2] | 0;

c[h + 20 >> 2] = c[l + 20 >> 2] | 0;

c[h + 24 >> 2] = c[l + 24 >> 2] | 0

}

} while (0);

k = r + 1 | 0;

if ((k | 0) < (c[1356579] | 0)) {

r = k

} else {

break

}

}

i = e;

return

}

Podrás encontrar el código en el GitHub de BananaBread.

Técnicamente esto es código JavaScript, pero ya podemos ver que esto se parece al uso de DOM en JavaScript. Pero… ¿Qué nos dice el código?

  • Asm.js sólo es capaz de manejar una selección de diferentes tipos de números y no otra estructura de datos (esto incluye cadenas, booleanos, o los objetos).
  • Todos los datos externos se almacenan y se hace referencia a un solo objeto, llamado heap, reemplazando las variables globales, estructuras de datos, cierres y cualquier otra forma de almacenamiento de datos.
  • Al acceder y establecer variables los resultados son constantemente obligados a un tipo específico. Por ejemplo f = e | 0; establece la variable f para igualar el valor de e, también asegura que el resultado será un número entero (|0 hace esto, la conversión de un valor en un entero). También vemos que esto ocurre con datos del tipo float, puedes verlo con el uso de 0,0 y g […] = + (…).
  • En cuanto a los valores que entran y salen de las estructuras de datos aparecen representados por la variable c, un Int32Array (los valores se convierten siempre desde 0 a un número entero usando |0) y g es un Float32Array (los valores son siempre convertidos a un flotador, envolviendo el valor con + (…)).

De esta manera el resultado es altamente optimizado y se puede convertir directamente desde esta sintaxis Asm.js sin tener que interpretar, ya que normalmente se tiene que hacer con el lenguaje Java.

Ejemplo de la especificación Asm.js :


function DiagModule(stdlib, foreign, heap) {

"use asm";

// Variable Declarations

var sqrt = stdlib.Math.sqrt;

// Function Declarations

function square(x) {

x = +x;

return +(x*x);

}

function diag(x, y) {

x = +x;

y = +y;

return +sqrt(square(x) + square(y));

}

return { diag: diag };

}

Podemos observar mejor la estructura de un módulo Asm.js. Un módulo está contenido dentro de una función y comienza con “use asm”. Esto le da al intérprete la insinuación de que todo dentro de la función debe ser manejado como Asm.js y ser compilado para montaje directamente.

¿Observas los argumentos stdlib, foreign, y heap? El objeto stdlib contiene referencias a una serie de funciones integradas de matemáticas. Foreign da acceso a la funcionalidad definida por el usuario (por ejemplo, dibujar una forma en WebGL). Y heap le da un ArrayBuffer que se puede ver a través de un número de diferentes lentes, como Int32Array y Float32Array.

El resto del módulo se divide en tres partes: declaraciones de variables, declaraciones de función y un objeto que exportan las funciones para exponer al usuario.

La exportación es un punto especialmente importante de entender, ya que permite que todo el código dentro del módulo que se maneja como Asm.js pueda ser utilizable al código normal JavaScript. Como en el siguiente ejemplo:


document.body.onclick = function() {

function DiagModule(stdlib){"use asm"; ... return { ... };}

var diag = DiagModule({ Math: Math }).diag;

alert(diag(10, 100));

};

Esto resultaría de un DiagModule en Asm.js que se ha manejado especialmente por el intérprete de JavaScript, pero aún así sigue estando disponible para otro código JavaScript (en el caso de que quisiéramos acceder a ella y usarla dentro de un controlador de clic, por ejemplo).

Imagen de previsualización de YouTube

¿Cómo es el rendimiento?

A estas alturas la única implementación que existe es en las versiones nocturnas de Firefox; los primeros números muestran un rendimiento muy bueno. Para aplicaciones complejas (tales como los juegos); el rendimiento es sólo alrededor de 2 veces más lento que normalmente compilado en C++ (comparándolo con otros lenguajes como Java o C#).

Esto es sustancialmente más rápido que el tiempo de ejecución actual del navegador, obteniéndose un rendimiento que es alrededor de 4 a 10 veces más rápido que la última versión de Firefox y Chrome.

Casos de uso

Hay que señalar que casi todas las aplicaciones que se están enfocando en Asm.js ahora son compilados de C/C++ a Asm.js utilizando Emscripten. Con esto en mente el tipo de aplicaciones que se van a Asm.js son los que se beneficiarán de la portabilidad de correr en un navegador, pero que tienen un nivel de complejidad en el que un puerto directo a JavaScript lo haría imposible.

Hasta ahora la mayoría de los casos de uso se han centrado en bases de código donde el desempeño es muy importante: como en el funcionamiento de los gráficos en los juegos, intérpretes de lenguajes de programación y bibliotecas.

Veamos rápidamente la lista de proyectos Emscripten que serán de uso inmediato para muchos desarrolladores.

  • Una buena demostración de lo que es posible es el juego FPS BananaBread, que se puede jugar directamente en el navegador y trae consigo características multijugador y bots.
  • Un puerto de LaTeX a JavaScript, llamado texlive.js , utilizando Emscripten, lo que le permite compilar archivos PDF totalmente dentro de su navegador.
  • Un puerto de SQLite a JavaScript capaz de correr en Node.js.
  • NaCl: Una biblioteca de redes y criptografía.

Soporte de Asm.js

Como se mencionó anteriormente la versión nocturna de Firefox es actualmente el único navegador que soporta la optimización de código Asm.js.

Si un navegador no es compatible con arreglos o especialmente al compilar el código Asm.js entonces el rendimiento va a estar mucho peor. Por supuesto, esto no es especial para Asm.js, probablemente cualquier navegador que no tiene esas características, también este sufriendo de otras maneras.

Para más información sobre Asm.js, especificaciones y el proyecto Emscripten de Mozilla visita las respectivas páginas oficiales.

Fuente: John Resig

Cecy Martínez

Cecy Martínez

Jefa de contenido y editora en HTML5Fácil, NinjaCodeTV, Desveloperstv y Co funder de WaveCode.LA

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Cerrar