Iniciando com WebAssembly – Parte 2
Aeee! fico feliz que você chegou na segunda parte 😀 Esta é uma série inédita de artigos sobre WebAssembly. Vamos escovar uns bits? Já vimos quais são os objetivos do WebAssembly e o que pode ser feito. Agora é hora de colocar a mão na massa!
Iniciando com WebAssembly – Parte 1
➡️ Iniciando com WebAssembly – Parte 2
INICIANDO COM WEBASSEMBLY – PARTE 3
Vimos no artigo anterior que o WebAssembly é um novo formato binário para a Web.
O JavaScript é incrível e as engines dos navegadores evoluiram muito nos últimos anos, mas mesmo assim, certas tarefas ainda ficam longe da performance ideal, e é aí que entra o WebAssembly.
Um exemplo prático pode ser visto abaixo, em um jogo muito legal que foi desenvolvido originalmente com Unity, e agora pode ser jogado no navegador, com uma performance incrível (Pode jogar, mas volte aqui depois, Ok?).
[Tanks - Um jogo feito em Unity portado para WebAssembly - http://webassembly.org/demo/
Como começar?
No início tudo parece meio obscuro, mas com o tempo e um pouco de estudo já é possível sair do Hello World.
Neste artigo vamos nos concentrar em fazer algo simples, de ponta a ponta, para colocar em prática o que aprendemos até agora, certo?
A figura abaixo apresenta o fluxo de uma aplicação que utiliza WebAssembly.
Fluxo de um módulo WebAssembly - Do programa feito em C até o JavaScript
Vamos fazer um simples programa em C, uma calculadora, e porta-lo para WebAssembly.
Nossa calculadora fará as 4 operações básicas, para cada uma das operações teremos uma função.
Você deve estar se questionando, mas C não é muito difícil?
Minha experiência com C é quase nula. Na faculdade eu tive cadeiras de Pascal, e o pouco contato que tive com a linguagem foi apenas por curiosidade.
Mesmo assim, fui capaz de montar esse exemplo básico sem muitas dificuldades.
#include <stdio.h> int sum(int n1, int n2) { return n1 + n2; } int sub(int n1, int n2) { return n1 - n2; } int mult(int n1, int n2) { return n1 * n2; } int div(int n1, int n2) { return n1 / n2; } int main() { printf("Sum 2 + 2 >>> %d\n", sum(2,2)); printf("Sub 8 - 2 >>> %d\n", sub(8,2)); printf("Mult 4 * 4 >>> %d\n", mult(4,4)); printf("Div 20 / 5 >>> %d\n", div(20,5)); }
O programa acima define 4 funções e as executa no entry point (algo como o nosso conhecido index.js ou index.html).
O C, diferente do JavaScript, é uma linguagem compilada.
Quando escrevemos código JavaScript, a engine do browser (ou de qualquer que seja o ambiente rodando JS) irá interpreta-lo em tempo de execução.
Pense que nessa interpretação o que acontece é que o código "humano" (a sintaxe do JS) é transformado em código de máquina.
Ilustração mostrando os passos necessários para execução de um código JavaScript
Em linguagens compiladas, antes da execução precisamos primeiramente transformar esse código "humano" em código de máquina, e para isso usamos o processo de compilação.
Para compilar um programa desenvolvido em C precisamos de um compilador.
Como estou desenvolvendo no Linux, vou utilizar o GCC, que na verdade é um agrupamento de vários compiladores com múltiplos targets, sendo possível compilar não só programas C.
Para quem estiver usando MacOS, o GCC deve funcionar da mesma maneira.
Para usuários Windows, recomendo seguir essa referência da Microsoft: https://msdn.microsoft.com/en-us/library/bb384838.aspx
Após a instalação do GCC, vamos compilar!
gcc -o calc calc.c
Este comando irá compilar o nosso programa calc.c
e gerar um executável chamado calc
no diretório atual.
Agora podemos usar o nosso programa:
./calc
Resultado da execução do programa calc
Portando para WebAssembly
Agora que já temos a nossa calculadora desenvolvida em C, vamos faze-la funcionar na Web!
Como vimos anteriormente, o WebAssembly é um formato binário.
Faremos agora algo bem parecido com o exemplo anterior em C, vamos compilar a calculadora, porém, para wasm
, e não para um programa executável.
O primeiro passo é instalar o Emscripten, que é um compilador de LLVM para JavaScript.
Pense no Emscripten como o nosso GCC utilizado no exemplo acima, onde compilamos nossa calculadora em C.
Será necessário ter o Git instalado, Python 2.7.x., assim como o compilador GCC (previamente instalado).
Caso tenha dificuldades nesta parte, você pode investigar os detalhes na documentação do WebAssembly: http://webassembly.org/getting-started/developers-guide/
Agora vamos compilar o Emscripten:
git clone https://github.com/juj/emsdk.git cd emsdk ./emsdk install sdk-incoming-64bit binaryen-master-64bit ./emsdk activate sdk-incoming-64bit binaryen-master-64bit
Feito isso, a instalação do Emscripten estará completa.
Para facilitar, é bom adicionar o Emscripten no seu bash_profile ou bashrc:
source ~/Workspace/emsdk-portable/emsdk_env.sh
O comando que ficará disponível no seu terminal será o emcc
, é com ele que vamos compilar nosso código para WebAssembly.
Agora vamos compilar a nossa calculadora para a Web!
emcc calc.c -o1 -o calc.wasm -s WASM=1 -s SIDE_MODULE=1
O comando acima transformará a nossa calculadora (calc.c) no arquivo calc.wasm
que é o formato final para consumo no navegador.
Agora entra a parte legal, fazer o WebAssembly funcionar com o JavaScript.
A API JavaScript para consumir módulos WebAssembly ainda está um pouco complicada, mas com o feedback da comunidade e casos de uso no mundo real, a tendência é que isso vá melhorando.
Para carregar um módulo WebAssembly precisamos executar alguns passos:
- Carregar os bytes do arquivo .wasm em um Typed Array ou ArrayBuffer
- Compilar os bytes em um módulo WebAssembly (WebAssembly.Module
)
- Instanciar o módulo WebAssembly.Module
Para facilitar nossa vida, vamos criar uma pequena função que fará todo o trabalho necessário, vamos chama-la de fetchAndCompileWASM
.
Ela fará justamente o que o seu nome diz, carregará o arquivo wasm
, fará a compilação e deixará tudo pronto para execução no navegador.
O primeiro passo é criar o corpo da função, e como vamos lidar com uma requisição assíncrona, vamos utilizar async/await para ficar ainda mais legal.
async function fetchAndCompileWASM(wasmFileURL) { return new Promise((resolve, reject) => { return fetch(wasmFileURL) .then(response => { if (response.status === 200) { return response.arrayBuffer() } else { reject(response) } }) } )}
No trecho acima definimos uma função assíncrona que retorna uma Promise.
Nesta Promise retornamos um fetch
, que buscará o arquivo .wasm
e no caso de sucesso, um HTTP 200 retornamos um ArrayBuffer da resposta.
Agora que temos o ArrayBuffer, precisamos compilar o módulo wasm carregado e instancia-lo.
.then(buffer => WebAssembly.compile(buffer)) .then(module => { const imports = { env: { memoryBase: 0, tableBase: 0, memory: new WebAssembly.Memory({ initial: 256 }), table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }) } }; let wasmInstance = new WebAssembly.Instance(module, imports);
O objeto imports
é necessário para instanciar o módulo WebAssembly, não vamos entrar em detalhes agora, mas neste objeto conseguimos definir acesso a memória, entre outras coisas interessantes.
A função completa para uso no navegador fica assim:
async function fetchAndCompileWASM(wasmFileURL) { return new Promise((resolve, reject) => { return fetch(wasmFileURL) .then(response => { if (response.status === 200) { return response.arrayBuffer() } else { reject(response) } }) .then(buffer => WebAssembly.compile(buffer)) .then(module => { const imports = { env: { memoryBase: 0, tableBase: 0, memory: new WebAssembly.Memory({ initial: 256 }), table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }) } }; let wasmInstance = new WebAssembly.Instance(module, imports); resolve(wasmInstance.exports); }) .catch(e => reject({ message:'Something went wrong loading and compiling the given wasm module', details:e })); }) }
Agora estamos quase prontos para utilizar a nossa calculadora desenvolvida em C no navegador!
(async function() { try { let wasmModule = await fetchAndCompileWASM('calc.wasm'); console.log(wasmModule) } catch(e) { console.log(e); } }())
Ao executarmos este código no navegador, podemos ver que o módulo wasm carregado possui algumas diferenças.
Cada função do nosso programa feito em C é retornada com um "_" na frente.
Veja na imagem abaixo:
Carregando e executando nossa calculadora wasm
Agora o mesmo resultado do nosso programa em C mostrado anteriormente:
(async function() { try { let wasmModule = await fetchAndCompileWASM('calc.wasm'); console.log("Sum 2 + 2 >>>", wasmModule._sum(2,2)); console.log("Sub 8 - 2 >>>", wasmModule._sub(8,2)); console.log("Mult 4 * 4 >>>", wasmModule._mult(4,4)); console.log("Div 20 / 5 >>>", wasmModule._div(20,5)); } catch(e) { console.log(e); } }())
Executando nossa calculadora com JavaScript
Conclusão
Wow! Parabéns e muito obrigado por ter chegado até o final :)
Espero que a segunda parte do artigo tenha sido satisfatória.
Como prometido, fomos de ponta a ponta, criando um programa básico em C e fazendo o mesmo funcionar no navegador.
É incrível ver o poder do WebAssembly e ver como isso influenciará o futuro do desenvolvimento Web.
O nosso exemplo pode parecer simples, apenas uma calculadora, mas pense em todas as outras possibilidades que o WebAssembly proporcionará.
Na terceira parte do artigo vamos empacotar tudo com Webpack e deixar o desenvolvimento mais fácil, espero ver vocês por lá!