WebAssembly é definitivamente não um substituto para o JavaScript como a língua franca da web e do mundo.
WebAssembly (abreviado como Wasm) é um formato de instrução binário para uma máquina virtual baseada em pilha. Wasm foi projetado como um destino portátil para compilação de linguagens de alto nível como C / C ++ / Rust, permitindo a implantação na web para aplicativos de cliente e servidor. ” - WebAssembly.org
É importante distinguir que WebAssembly não é um idioma. WebAssembly é como um ‘.exe’ - ou melhor ainda - um arquivo Java ‘.class’. Ele é compilado pelo desenvolvedor da web em outro idioma, depois é baixado e executado em seu navegador.
WebAssembly está fornecendo ao JavaScript todos os recursos que ocasionalmente queríamos emprestar, mas nunca realmente queria possuir. Muito parecido com o aluguel de um barco ou cavalo, o WebAssembly nos permite viajar para outros idiomas sem ter que fazer escolhas extravagantes de “estilo de vida de linguagem”. Isso permitiu que a web se concentrasse em coisas importantes, como fornecer recursos e melhorar a experiência do usuário.
Mais de 20 linguagens compilam para WebAssembly: Rust, C / C ++, C # / .Net, Java, Python, Elixir, Go e, claro, JavaScript.
Se você se lembra do diagrama de arquitetura de nossa simulação, delegamos toda a simulação a nBodySimulator
, para que ele gerencie o web worker.
Se você se lembra do postagem de introdução , nBodySimulator
tem um step()
função chamada a cada 33ms. O step()
função faz essas coisas - numeradas no diagrama acima:
calculateForces()
chamadas this.worker.postMessage()
para iniciar o cálculo.this.onmessage()
capta a mensagem.nBodyForces()
função.this.postMessage()
para o fio principal com as novas forças.this.worker.onMessage()
organiza os dados e chamadas retornados.applyForces()
para atualizar as posições dos órgãos.
Dentro o post anterior , construímos o web worker que está envolvendo nossos cálculos WASM. Hoje, estamos construindo a pequena caixa rotulada “WASM” e movendo os dados para dentro e para fora.
Para simplificar, escolhi AssemblyScript como a linguagem de código-fonte para escrever nossos cálculos. AssemblyScript é um subconjunto do TypeScript - que é um JavaScript digitado - então você já o conhece.
Por exemplo, esta função AssemblyScript calcula a gravidade entre dois corpos: O :f64
em someVar:f64
marca a variável someVar como um float para o compilador. Lembre-se de que este código é compilado e executado em um tempo de execução completamente diferente do JavaScript.
// AssemblyScript - a TypeScript-like language that compiles to WebAssembly // src/assembly/nBodyForces.ts /** * Given two bodies, calculate the Force of Gravity, * then return as a 3-force vector (x, y, z) * * Sometimes, the force of gravity is: * * Fg = G * mA * mB / r^2 * * Given: * - Fg = Force of gravity * - r = sqrt ( dx + dy + dz) = straight line distance between 3d objects * - G = gravitational constant * - mA, mB = mass of objects * * Today, we're using better-gravity because better-gravity can calculate * force vectors without polar math (sin, cos, tan) * * Fbg = G * mA * mB * dr / r^3 // using dr as a 3-distance vector lets * // us project Fbg as a 3-force vector * * Given: * - Fbg = Force of better gravity * - dr = (dx, dy, dz) // a 3-distance vector * - dx = bodyB.x - bodyA.x * * Force of Better-Gravity: * * - Fbg = (Fx, Fy, Fz) = the change in force applied by gravity each * body's (x,y,z) over this time period * - Fbg = G * mA * mB * dr / r^3 * - dr = (dx, dy, dz) * - Fx = Gmm * dx / r3 * - Fy = Gmm * dy / r3 * - Fz = Gmm * dz / r3 * * From the parameters, return an array [fx, fy, fz] */ function twoBodyForces(xA: f64, yA: f64, zA: f64, mA: f64, xB: f64, yB: f64, zB: f64, mB: f64): f64[]
Esta função AssemblyScript pega (x, y, z, massa) para dois corpos e retorna uma matriz de três flutuadores que descrevem o vetor de força (x, y, z) que os corpos se aplicam um ao outro. Não podemos chamar esta função do JavaScript porque o JavaScript não tem ideia de onde encontrá-la. Temos que “exportar” para JavaScript. Isso nos leva ao nosso primeiro desafio técnico.
No ES6, pensamos em importações e exportações em código JavaScript e usamos ferramentas como Rollup ou Webpack para criar código que é executado em navegadores legados para lidar com import
e require()
. Isso cria uma árvore de dependência de cima para baixo e permite tecnologias interessantes como “ tremor de árvore ”E divisão de código .
No WebAssembly, as importações e exportações realizam tarefas diferentes de uma importação ES6. Importações / exportações do WebAssembly:
trace()
e abort()
).No código abaixo, env.abort
e env.trace
fazem parte do ambiente que devemos fornecer ao módulo WebAssembly. O nBodyForces.logI
e as funções de amigos fornecem mensagens de depuração para o console. Observe que passar strings de entrada / saída de WebAssembly não é trivial, pois os únicos tipos de WebAssembly são números i32, i64, f32, f64, com referências i32 para uma memória linear abstrata.
Nota: Esses exemplos de código estão alternando entre o código JavaScript (o trabalhador da web) e o AssemblyScript (o código WASM).
// Web Worker JavaScript in workerWasm.js /** * When we instantiate the Wasm module, give it a context to work in: * nBodyForces: {} is a table of functions we can import into AssemblyScript. See top of nBodyForces.ts * env: {} describes the environment sent to the Wasm module as it's instantiated */ const importObj = { nBodyForces: { logI(data) { console.log('Log() - ' + data); }, logF(data) { console.log('Log() - ' + data); }, }, env: { abort(msg, file, line, column) { // wasm.__getString() is added by assemblyscript's loader: // https://github.com/AssemblyScript/assemblyscript/tree/master/lib/loader console.error('abort: (' + wasm.__getString(msg) + ') at ' + wasm.__getString(file) + ':' + line + ':' + column); }, trace(msg, n) { console.log('trace: ' + wasm.__getString(msg) + (n ? ' ' : '') + Array.prototype.slice.call(arguments, 2, 2 + n).join(', ')); } } }
Em nosso código AssemblyScript, podemos concluir a importação dessas funções da seguinte forma:
// nBodyForces.ts declare function logI(data: i32): void declare function logF(data: f64): void
Nota : Abortar e rastrear são importados automaticamente .
A partir do AssemblyScript, podemos exportar nossa interface. Aqui estão algumas constantes exportadas:
// src/assembly/nBodyForces.ts // Gravitational constant. Any G could be used in a game. // This value is best for a scientific simulation. export const G: f64 = 6.674e-11; // for sizing and indexing arrays export const bodySize: i32 = 4 export const forceSize: i32 = 3
E aqui está a exportação de nBodyForces()
que chamaremos de JavaScript. Exportamos o tipo Float64Array
na parte superior do arquivo para que possamos usar o carregador JavaScript do AssemblyScript em nosso web worker para obter os dados (veja abaixo):
// src/assembly/nBodyForces.ts export const FLOAT64ARRAY_ID = idof(); ... /** * Given N bodies with mass, in a 3d space, calculate the forces of gravity to be applied to each body. * * This function is exported to JavaScript, so only takes/returns numbers and arrays. * For N bodies, pass and array of 4N values (x,y,z,mass) and expect a 3N array of forces (x,y,z) * Those forces can be applied to the bodies mass to update its position in the simulation. * Calculate the 3-vector each unique pair of bodies applies to each other. * * 0 1 2 3 4 5 * 0 x x x x x * 1 x x x x * 2 x x x * 3 x x * 4 x * 5 * * Sum those forces together into an array of 3-vector x,y,z forces * * Return 0 on success */ export function nBodyForces(arrBodies: Float64Array): Float64Array { // Check inputs const numBodies: i32 = arrBodies.length / bodySize if (arrBodies.length % bodySize !== 0) trace('INVALID nBodyForces parameter. Chaos ensues...') // Create result array. This should be garbage collected later. let arrForces: Float64Array = new Float64Array(numBodies * forceSize) // For all bodies: for (let i: i32 = 0; i i for (let j: i32 = i + 1; j Artefatos de WebAssembly: .wasm e .wat
Quando nosso AssemblyScript nBodyForces.ts
é compilado em um WebAssembly nBodyForces.wasm
binário , existe a opção de criar também uma versão em “texto” descrevendo as instruções no binário.

Figura 3: Lembre-se de que o AssemblyScript é uma linguagem. WebAssembly é um compilador e tempo de execução.
Dentro do nBodyForces.wat
arquivo, podemos ver essas importações e exportações:
;; This is a comment in nBodyForces.wat (module ;; compiler defined types (type $FUNCSIG$iii (func (param i32 i32) (result i32))) … ;; Expected imports from JavaScript (import 'env' 'abort' (func $~lib/builtins/abort (param i32 i32 i32 i32))) (import 'env' 'trace' (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64))) ;; Memory section defining data constants like strings (memory WebVR Parte 3: Desvendando o potencial de WebAssembly e AssemblyScript
WebAssembly é definitivamente não um substituto para o JavaScript como a língua franca da web e do mundo.
WebAssembly (abreviado como Wasm) é um formato de instrução binário para uma máquina virtual baseada em pilha. Wasm foi projetado como um destino portátil para compilação de linguagens de alto nível como C / C ++ / Rust, permitindo a implantação na web para aplicativos de cliente e servidor. ” - WebAssembly.org
É importante distinguir que WebAssembly não é um idioma. WebAssembly é como um ‘.exe’ - ou melhor ainda - um arquivo Java ‘.class’. Ele é compilado pelo desenvolvedor da web em outro idioma, depois é baixado e executado em seu navegador.
WebAssembly está fornecendo ao JavaScript todos os recursos que ocasionalmente queríamos emprestar, mas nunca realmente queria possuir. Muito parecido com o aluguel de um barco ou cavalo, o WebAssembly nos permite viajar para outros idiomas sem ter que fazer escolhas extravagantes de “estilo de vida de linguagem”. Isso permitiu que a web se concentrasse em coisas importantes, como fornecer recursos e melhorar a experiência do usuário.
Mais de 20 linguagens compilam para WebAssembly: Rust, C / C ++, C # / .Net, Java, Python, Elixir, Go e, claro, JavaScript.
Se você se lembra do diagrama de arquitetura de nossa simulação, delegamos toda a simulação a nBodySimulator
, para que ele gerencie o web worker.
Se você se lembra do postagem de introdução , nBodySimulator
tem um step()
função chamada a cada 33ms. O step()
função faz essas coisas - numeradas no diagrama acima:
calculateForces()
chamadas this.worker.postMessage()
para iniciar o cálculo.this.onmessage()
capta a mensagem.nBodyForces()
função.this.postMessage()
para o fio principal com as novas forças.this.worker.onMessage()
organiza os dados e chamadas retornados.applyForces()
para atualizar as posições dos órgãos.
Dentro o post anterior , construímos o web worker que está envolvendo nossos cálculos WASM. Hoje, estamos construindo a pequena caixa rotulada “WASM” e movendo os dados para dentro e para fora.
Para simplificar, escolhi AssemblyScript como a linguagem de código-fonte para escrever nossos cálculos. AssemblyScript é um subconjunto do TypeScript - que é um JavaScript digitado - então você já o conhece.
Por exemplo, esta função AssemblyScript calcula a gravidade entre dois corpos: O :f64
em someVar:f64
marca a variável someVar como um float para o compilador. Lembre-se de que este código é compilado e executado em um tempo de execução completamente diferente do JavaScript.
// AssemblyScript - a TypeScript-like language that compiles to WebAssembly // src/assembly/nBodyForces.ts /** * Given two bodies, calculate the Force of Gravity, * then return as a 3-force vector (x, y, z) * * Sometimes, the force of gravity is: * * Fg = G * mA * mB / r^2 * * Given: * - Fg = Force of gravity * - r = sqrt ( dx + dy + dz) = straight line distance between 3d objects * - G = gravitational constant * - mA, mB = mass of objects * * Today, we're using better-gravity because better-gravity can calculate * force vectors without polar math (sin, cos, tan) * * Fbg = G * mA * mB * dr / r^3 // using dr as a 3-distance vector lets * // us project Fbg as a 3-force vector * * Given: * - Fbg = Force of better gravity * - dr = (dx, dy, dz) // a 3-distance vector * - dx = bodyB.x - bodyA.x * * Force of Better-Gravity: * * - Fbg = (Fx, Fy, Fz) = the change in force applied by gravity each * body's (x,y,z) over this time period * - Fbg = G * mA * mB * dr / r^3 * - dr = (dx, dy, dz) * - Fx = Gmm * dx / r3 * - Fy = Gmm * dy / r3 * - Fz = Gmm * dz / r3 * * From the parameters, return an array [fx, fy, fz] */ function twoBodyForces(xA: f64, yA: f64, zA: f64, mA: f64, xB: f64, yB: f64, zB: f64, mB: f64): f64[]
Esta função AssemblyScript pega (x, y, z, massa) para dois corpos e retorna uma matriz de três flutuadores que descrevem o vetor de força (x, y, z) que os corpos se aplicam um ao outro. Não podemos chamar esta função do JavaScript porque o JavaScript não tem ideia de onde encontrá-la. Temos que “exportar” para JavaScript. Isso nos leva ao nosso primeiro desafio técnico.
No ES6, pensamos em importações e exportações em código JavaScript e usamos ferramentas como Rollup ou Webpack para criar código que é executado em navegadores legados para lidar com import
e require()
. Isso cria uma árvore de dependência de cima para baixo e permite tecnologias interessantes como “ tremor de árvore ”E divisão de código .
No WebAssembly, as importações e exportações realizam tarefas diferentes de uma importação ES6. Importações / exportações do WebAssembly:
trace()
e abort()
).No código abaixo, env.abort
e env.trace
fazem parte do ambiente que devemos fornecer ao módulo WebAssembly. O nBodyForces.logI
e as funções de amigos fornecem mensagens de depuração para o console. Observe que passar strings de entrada / saída de WebAssembly não é trivial, pois os únicos tipos de WebAssembly são números i32, i64, f32, f64, com referências i32 para uma memória linear abstrata.
Nota: Esses exemplos de código estão alternando entre o código JavaScript (o trabalhador da web) e o AssemblyScript (o código WASM).
// Web Worker JavaScript in workerWasm.js /** * When we instantiate the Wasm module, give it a context to work in: * nBodyForces: {} is a table of functions we can import into AssemblyScript. See top of nBodyForces.ts * env: {} describes the environment sent to the Wasm module as it's instantiated */ const importObj = { nBodyForces: { logI(data) { console.log('Log() - ' + data); }, logF(data) { console.log('Log() - ' + data); }, }, env: { abort(msg, file, line, column) { // wasm.__getString() is added by assemblyscript's loader: // https://github.com/AssemblyScript/assemblyscript/tree/master/lib/loader console.error('abort: (' + wasm.__getString(msg) + ') at ' + wasm.__getString(file) + ':' + line + ':' + column); }, trace(msg, n) { console.log('trace: ' + wasm.__getString(msg) + (n ? ' ' : '') + Array.prototype.slice.call(arguments, 2, 2 + n).join(', ')); } } }
Em nosso código AssemblyScript, podemos concluir a importação dessas funções da seguinte forma:
// nBodyForces.ts declare function logI(data: i32): void declare function logF(data: f64): void
Nota : Abortar e rastrear são importados automaticamente .
A partir do AssemblyScript, podemos exportar nossa interface. Aqui estão algumas constantes exportadas:
// src/assembly/nBodyForces.ts // Gravitational constant. Any G could be used in a game. // This value is best for a scientific simulation. export const G: f64 = 6.674e-11; // for sizing and indexing arrays export const bodySize: i32 = 4 export const forceSize: i32 = 3
E aqui está a exportação de nBodyForces()
que chamaremos de JavaScript. Exportamos o tipo Float64Array
na parte superior do arquivo para que possamos usar o carregador JavaScript do AssemblyScript em nosso web worker para obter os dados (veja abaixo):
// src/assembly/nBodyForces.ts export const FLOAT64ARRAY_ID = idof(); ... /** * Given N bodies with mass, in a 3d space, calculate the forces of gravity to be applied to each body. * * This function is exported to JavaScript, so only takes/returns numbers and arrays. * For N bodies, pass and array of 4N values (x,y,z,mass) and expect a 3N array of forces (x,y,z) * Those forces can be applied to the bodies mass to update its position in the simulation. * Calculate the 3-vector each unique pair of bodies applies to each other. * * 0 1 2 3 4 5 * 0 x x x x x * 1 x x x x * 2 x x x * 3 x x * 4 x * 5 * * Sum those forces together into an array of 3-vector x,y,z forces * * Return 0 on success */ export function nBodyForces(arrBodies: Float64Array): Float64Array { // Check inputs const numBodies: i32 = arrBodies.length / bodySize if (arrBodies.length % bodySize !== 0) trace('INVALID nBodyForces parameter. Chaos ensues...') // Create result array. This should be garbage collected later. let arrForces: Float64Array = new Float64Array(numBodies * forceSize) // For all bodies: for (let i: i32 = 0; i i for (let j: i32 = i + 1; j Artefatos de WebAssembly: .wasm e .wat
Quando nosso AssemblyScript nBodyForces.ts
é compilado em um WebAssembly nBodyForces.wasm
binário , existe a opção de criar também uma versão em “texto” descrevendo as instruções no binário.

Figura 3: Lembre-se de que o AssemblyScript é uma linguagem. WebAssembly é um compilador e tempo de execução.
Dentro do nBodyForces.wat
arquivo, podemos ver essas importações e exportações:
;; This is a comment in nBodyForces.wat (module ;; compiler defined types (type $FUNCSIG$iii (func (param i32 i32) (result i32))) … ;; Expected imports from JavaScript (import 'env' 'abort' (func $~lib/builtins/abort (param i32 i32 i32 i32))) (import 'env' 'trace' (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64))) ;; Memory section defining data constants like strings (memory $0 1) (data (i32.const 8) '1e 0 0 0 1 0 0 0 1 0 0 01e 0 0 0~ 0l 0i 0b 0/ 0r 0t 0/ 0t 0l 0s 0f 0. 0t 0s 0') ... ;; Our global constants (not yet exported) (global $nBodyForces/FLOAT64ARRAY_ID i32 (i32.const 3)) (global $nBodyForces/G f64 (f64.const 6.674e-11)) (global $nBodyForces/bodySize i32 (i32.const 4)) (global $nBodyForces/forceSize i32 (i32.const 3)) ... ;; Memory management functions we’ll use in a minute (export 'memory' (memory $0)) (export '__alloc' (func $~lib/rt/tlsf/__alloc)) (export '__retain' (func $~lib/rt/pure/__retain)) (export '__release' (func $~lib/rt/pure/__release)) (export '__collect' (func $~lib/rt/pure/__collect)) (export '__rtti_base' (global $~lib/rt/__rtti_base)) ;; Finally our exported constants and function (export 'FLOAT64ARRAY_ID' (global $nBodyForces/FLOAT64ARRAY_ID)) (export 'G' (global $nBodyForces/G)) (export 'bodySize' (global $nBodyForces/bodySize)) (export 'forceSize' (global $nBodyForces/forceSize)) (export 'nBodyForces' (func $nBodyForces/nBodyForces)) ;; Implementation details ...
Agora temos nosso nBodyForces.wasm
binário e um web worker para executá-lo. Prepare-se para a decolagem! E algum gerenciamento de memória!
Para completar a integração, temos que passar um array variável de floats para WebAssembly e retornar um array variável de floats para JavaScript.
Com o JavaScript burguês ingênuo, decidi passar arbitrariamente esses arrays de tamanho variável espalhafatosos para dentro e para fora de um runtime de alto desempenho de plataforma cruzada. Passar dados de / para o WebAssembly foi, de longe, a dificuldade mais inesperada neste projeto.
No entanto, com muito obrigado pelo levantamento pesado feito pela equipe do AssemblyScript , podemos usar seu “carregador” para ajudar:
// workerWasm.js - our web worker /** * AssemblyScript loader adds helpers for moving data to/from AssemblyScript. * Highly recommended */ const loader = require('assemblyscript/lib/loader')
O require()
significa que precisamos usar um empacotador de módulo como Rollup ou Webpack. Para este projeto, escolhi Rollup por sua simplicidade e flexibilidade e nunca olhei para trás.
Lembre-se de que nosso web worker é executado em um thread separado e é essencialmente um onmessage()
função com um switch()
declaração.
loader
cria nosso módulo wasm com algumas funções de gerenciamento de memória úteis extras. __retain()
e __release()
gerenciar referências de coleta de lixo no tempo de execução do trabalhador __allocArray()
copia nosso array de parâmetros para a memória do módulo wasm __getFloat64Array()
copia a matriz de resultado do módulo wasm para o tempo de execução do trabalhador
Agora podemos empacotar matrizes flutuantes dentro e fora de nBodyForces()
e conclua nossa simulação:
// workerWasm.js /** * Web workers listen for messages from the main thread. */ this.onmessage = function (evt) { // message from UI thread var msg = evt.data switch (msg.purpose) { // Message: Load new wasm module case 'wasmModule': // Instantiate the compiled module we were passed. wasm = loader.instantiate(msg.wasmModule, importObj) // Throws // Tell nBodySimulation.js we are ready this.postMessage({ purpose: 'wasmReady' }) return // Message: Given array of floats describing a system of bodies (x,y,x,mass), // calculate the Grav forces to be applied to each body case 'nBodyForces': if (!wasm) throw new Error('wasm not initialized') // Copy msg.arrBodies array into the wasm instance, increase GC count const dataRef = wasm.__retain(wasm.__allocArray(wasm.FLOAT64ARRAY_ID, msg.arrBodies)); // Do the calculations in this thread synchronously const resultRef = wasm.nBodyForces(dataRef); // Copy result array from the wasm instance to our javascript runtime const arrForces = wasm.__getFloat64Array(resultRef); // Decrease the GC count on dataRef from __retain() here, // and GC count from new Float64Array in wasm module wasm.__release(dataRef); wasm.__release(resultRef); // Message results back to main thread. // see nBodySimulation.js this.worker.onmessage return this.postMessage({ purpose: 'nBodyForces', arrForces }) } }
Com tudo o que aprendemos, vamos revisar nossa jornada do web worker e WebAssembly. Bem-vindo ao novo back-end do navegador da web. Estes são os links para o código no GitHub:
- GET Index.html
- main.js
- nBodySimulator.js - passa uma mensagem para seu web worker
- workerWasm.js - chama a função WebAssembly
- nBodyForces.ts - calcula e retorna uma série de forças
- workerWasm.js - passa os resultados de volta para o tópico principal
- nBodySimulator.js - resolve a promessa de forças
- nBodySimulator.js - então aplica as forças aos corpos e diz aos visualizadores para pintar
A partir daqui, vamos começar o show criando nBodyVisualizer.js
! Nossa próxima postagem cria um visualizador usando a API Canvas e a postagem final termina com WebVR e Aframe.
Relacionado: WebAssembly / Rust Tutorial: Pitch-perfect Audio Processing Compreender o básico
O WebAssembly pode substituir o JavaScript?
WebAssembly não é uma linguagem, portanto, não pode substituir o JavaScript. Além disso, desenvolver recursos e experiência do usuário no WebAssembly é menos eficiente.
Por que o WebAssembly é mais rápido?
O WebAssembly é mais rápido porque faz menos e foi projetado para desempenho em vez de usabilidade do desenvolvedor.
O JavaScript pode ser compilado para WebAssembly?
Sim, o AssemblyScript compila para WebAssembly e se parece com o Typescript.
WebAssembly é definitivamente não um substituto para o JavaScript como a língua franca da web e do mundo.
WebAssembly (abreviado como Wasm) é um formato de instrução binário para uma máquina virtual baseada em pilha. Wasm foi projetado como um destino portátil para compilação de linguagens de alto nível como C / C ++ / Rust, permitindo a implantação na web para aplicativos de cliente e servidor. ” - WebAssembly.org
É importante distinguir que WebAssembly não é um idioma. WebAssembly é como um ‘.exe’ - ou melhor ainda - um arquivo Java ‘.class’. Ele é compilado pelo desenvolvedor da web em outro idioma, depois é baixado e executado em seu navegador.
WebAssembly está fornecendo ao JavaScript todos os recursos que ocasionalmente queríamos emprestar, mas nunca realmente queria possuir. Muito parecido com o aluguel de um barco ou cavalo, o WebAssembly nos permite viajar para outros idiomas sem ter que fazer escolhas extravagantes de “estilo de vida de linguagem”. Isso permitiu que a web se concentrasse em coisas importantes, como fornecer recursos e melhorar a experiência do usuário.
Mais de 20 linguagens compilam para WebAssembly: Rust, C / C ++, C # / .Net, Java, Python, Elixir, Go e, claro, JavaScript.
Se você se lembra do diagrama de arquitetura de nossa simulação, delegamos toda a simulação a nBodySimulator
, para que ele gerencie o web worker.
Se você se lembra do postagem de introdução , nBodySimulator
tem um step()
função chamada a cada 33ms. O step()
função faz essas coisas - numeradas no diagrama acima:
calculateForces()
chamadas this.worker.postMessage()
para iniciar o cálculo.this.onmessage()
capta a mensagem.nBodyForces()
função.this.postMessage()
para o fio principal com as novas forças.this.worker.onMessage()
organiza os dados e chamadas retornados.applyForces()
para atualizar as posições dos órgãos.
Dentro o post anterior , construímos o web worker que está envolvendo nossos cálculos WASM. Hoje, estamos construindo a pequena caixa rotulada “WASM” e movendo os dados para dentro e para fora.
Para simplificar, escolhi AssemblyScript como a linguagem de código-fonte para escrever nossos cálculos. AssemblyScript é um subconjunto do TypeScript - que é um JavaScript digitado - então você já o conhece.
Por exemplo, esta função AssemblyScript calcula a gravidade entre dois corpos: O :f64
em someVar:f64
marca a variável someVar como um float para o compilador. Lembre-se de que este código é compilado e executado em um tempo de execução completamente diferente do JavaScript.
// AssemblyScript - a TypeScript-like language that compiles to WebAssembly // src/assembly/nBodyForces.ts /** * Given two bodies, calculate the Force of Gravity, * then return as a 3-force vector (x, y, z) * * Sometimes, the force of gravity is: * * Fg = G * mA * mB / r^2 * * Given: * - Fg = Force of gravity * - r = sqrt ( dx + dy + dz) = straight line distance between 3d objects * - G = gravitational constant * - mA, mB = mass of objects * * Today, we're using better-gravity because better-gravity can calculate * force vectors without polar math (sin, cos, tan) * * Fbg = G * mA * mB * dr / r^3 // using dr as a 3-distance vector lets * // us project Fbg as a 3-force vector * * Given: * - Fbg = Force of better gravity * - dr = (dx, dy, dz) // a 3-distance vector * - dx = bodyB.x - bodyA.x * * Force of Better-Gravity: * * - Fbg = (Fx, Fy, Fz) = the change in force applied by gravity each * body's (x,y,z) over this time period * - Fbg = G * mA * mB * dr / r^3 * - dr = (dx, dy, dz) * - Fx = Gmm * dx / r3 * - Fy = Gmm * dy / r3 * - Fz = Gmm * dz / r3 * * From the parameters, return an array [fx, fy, fz] */ function twoBodyForces(xA: f64, yA: f64, zA: f64, mA: f64, xB: f64, yB: f64, zB: f64, mB: f64): f64[]
Esta função AssemblyScript pega (x, y, z, massa) para dois corpos e retorna uma matriz de três flutuadores que descrevem o vetor de força (x, y, z) que os corpos se aplicam um ao outro. Não podemos chamar esta função do JavaScript porque o JavaScript não tem ideia de onde encontrá-la. Temos que “exportar” para JavaScript. Isso nos leva ao nosso primeiro desafio técnico.
No ES6, pensamos em importações e exportações em código JavaScript e usamos ferramentas como Rollup ou Webpack para criar código que é executado em navegadores legados para lidar com import
e require()
. Isso cria uma árvore de dependência de cima para baixo e permite tecnologias interessantes como “ tremor de árvore ”E divisão de código .
No WebAssembly, as importações e exportações realizam tarefas diferentes de uma importação ES6. Importações / exportações do WebAssembly:
trace()
e abort()
).No código abaixo, env.abort
e env.trace
fazem parte do ambiente que devemos fornecer ao módulo WebAssembly. O nBodyForces.logI
e as funções de amigos fornecem mensagens de depuração para o console. Observe que passar strings de entrada / saída de WebAssembly não é trivial, pois os únicos tipos de WebAssembly são números i32, i64, f32, f64, com referências i32 para uma memória linear abstrata.
Nota: Esses exemplos de código estão alternando entre o código JavaScript (o trabalhador da web) e o AssemblyScript (o código WASM).
// Web Worker JavaScript in workerWasm.js /** * When we instantiate the Wasm module, give it a context to work in: * nBodyForces: {} is a table of functions we can import into AssemblyScript. See top of nBodyForces.ts * env: {} describes the environment sent to the Wasm module as it's instantiated */ const importObj = { nBodyForces: { logI(data) { console.log('Log() - ' + data); }, logF(data) { console.log('Log() - ' + data); }, }, env: { abort(msg, file, line, column) { // wasm.__getString() is added by assemblyscript's loader: // https://github.com/AssemblyScript/assemblyscript/tree/master/lib/loader console.error('abort: (' + wasm.__getString(msg) + ') at ' + wasm.__getString(file) + ':' + line + ':' + column); }, trace(msg, n) { console.log('trace: ' + wasm.__getString(msg) + (n ? ' ' : '') + Array.prototype.slice.call(arguments, 2, 2 + n).join(', ')); } } }
Em nosso código AssemblyScript, podemos concluir a importação dessas funções da seguinte forma:
// nBodyForces.ts declare function logI(data: i32): void declare function logF(data: f64): void
Nota : Abortar e rastrear são importados automaticamente .
A partir do AssemblyScript, podemos exportar nossa interface. Aqui estão algumas constantes exportadas:
// src/assembly/nBodyForces.ts // Gravitational constant. Any G could be used in a game. // This value is best for a scientific simulation. export const G: f64 = 6.674e-11; // for sizing and indexing arrays export const bodySize: i32 = 4 export const forceSize: i32 = 3
E aqui está a exportação de nBodyForces()
que chamaremos de JavaScript. Exportamos o tipo Float64Array
na parte superior do arquivo para que possamos usar o carregador JavaScript do AssemblyScript em nosso web worker para obter os dados (veja abaixo):
// src/assembly/nBodyForces.ts export const FLOAT64ARRAY_ID = idof(); ... /** * Given N bodies with mass, in a 3d space, calculate the forces of gravity to be applied to each body. * * This function is exported to JavaScript, so only takes/returns numbers and arrays. * For N bodies, pass and array of 4N values (x,y,z,mass) and expect a 3N array of forces (x,y,z) * Those forces can be applied to the bodies mass to update its position in the simulation. * Calculate the 3-vector each unique pair of bodies applies to each other. * * 0 1 2 3 4 5 * 0 x x x x x * 1 x x x x * 2 x x x * 3 x x * 4 x * 5 * * Sum those forces together into an array of 3-vector x,y,z forces * * Return 0 on success */ export function nBodyForces(arrBodies: Float64Array): Float64Array { // Check inputs const numBodies: i32 = arrBodies.length / bodySize if (arrBodies.length % bodySize !== 0) trace('INVALID nBodyForces parameter. Chaos ensues...') // Create result array. This should be garbage collected later. let arrForces: Float64Array = new Float64Array(numBodies * forceSize) // For all bodies: for (let i: i32 = 0; i i for (let j: i32 = i + 1; j Artefatos de WebAssembly: .wasm e .wat
Quando nosso AssemblyScript nBodyForces.ts
é compilado em um WebAssembly nBodyForces.wasm
binário , existe a opção de criar também uma versão em “texto” descrevendo as instruções no binário.

Figura 3: Lembre-se de que o AssemblyScript é uma linguagem. WebAssembly é um compilador e tempo de execução.
Dentro do nBodyForces.wat
arquivo, podemos ver essas importações e exportações:
;; This is a comment in nBodyForces.wat (module ;; compiler defined types (type $FUNCSIG$iii (func (param i32 i32) (result i32))) … ;; Expected imports from JavaScript (import 'env' 'abort' (func $~lib/builtins/abort (param i32 i32 i32 i32))) (import 'env' 'trace' (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64))) ;; Memory section defining data constants like strings (memory $0 1) (data (i32.const 8) '1e 0 0 0 1 0 0 0 1 0 0 01e 0 0 0~ 0l 0i 0b 0/ 0r 0t 0/ 0t 0l 0s 0f 0. 0t 0s 0') ... ;; Our global constants (not yet exported) (global $nBodyForces/FLOAT64ARRAY_ID i32 (i32.const 3)) (global $nBodyForces/G f64 (f64.const 6.674e-11)) (global $nBodyForces/bodySize i32 (i32.const 4)) (global $nBodyForces/forceSize i32 (i32.const 3)) ... ;; Memory management functions we’ll use in a minute (export 'memory' (memory $0)) (export '__alloc' (func $~lib/rt/tlsf/__alloc)) (export '__retain' (func $~lib/rt/pure/__retain)) (export '__release' (func $~lib/rt/pure/__release)) (export '__collect' (func $~lib/rt/pure/__collect)) (export '__rtti_base' (global $~lib/rt/__rtti_base)) ;; Finally our exported constants and function (export 'FLOAT64ARRAY_ID' (global $nBodyForces/FLOAT64ARRAY_ID)) (export 'G' (global $nBodyForces/G)) (export 'bodySize' (global $nBodyForces/bodySize)) (export 'forceSize' (global $nBodyForces/forceSize)) (export 'nBodyForces' (func $nBodyForces/nBodyForces)) ;; Implementation details ...
Agora temos nosso nBodyForces.wasm
binário e um web worker para executá-lo. Prepare-se para a decolagem! E algum gerenciamento de memória!
Para completar a integração, temos que passar um array variável de floats para WebAssembly e retornar um array variável de floats para JavaScript.
Com o JavaScript burguês ingênuo, decidi passar arbitrariamente esses arrays de tamanho variável espalhafatosos para dentro e para fora de um runtime de alto desempenho de plataforma cruzada. Passar dados de / para o WebAssembly foi, de longe, a dificuldade mais inesperada neste projeto.
No entanto, com muito obrigado pelo levantamento pesado feito pela equipe do AssemblyScript , podemos usar seu “carregador” para ajudar:
// workerWasm.js - our web worker /** * AssemblyScript loader adds helpers for moving data to/from AssemblyScript. * Highly recommended */ const loader = require('assemblyscript/lib/loader')
O require()
significa que precisamos usar um empacotador de módulo como Rollup ou Webpack. Para este projeto, escolhi Rollup por sua simplicidade e flexibilidade e nunca olhei para trás.
Lembre-se de que nosso web worker é executado em um thread separado e é essencialmente um onmessage()
função com um switch()
declaração.
loader
cria nosso módulo wasm com algumas funções de gerenciamento de memória úteis extras. __retain()
e __release()
gerenciar referências de coleta de lixo no tempo de execução do trabalhador __allocArray()
copia nosso array de parâmetros para a memória do módulo wasm __getFloat64Array()
copia a matriz de resultado do módulo wasm para o tempo de execução do trabalhador
Agora podemos empacotar matrizes flutuantes dentro e fora de nBodyForces()
e conclua nossa simulação:
// workerWasm.js /** * Web workers listen for messages from the main thread. */ this.onmessage = function (evt) { // message from UI thread var msg = evt.data switch (msg.purpose) { // Message: Load new wasm module case 'wasmModule': // Instantiate the compiled module we were passed. wasm = loader.instantiate(msg.wasmModule, importObj) // Throws // Tell nBodySimulation.js we are ready this.postMessage({ purpose: 'wasmReady' }) return // Message: Given array of floats describing a system of bodies (x,y,x,mass), // calculate the Grav forces to be applied to each body case 'nBodyForces': if (!wasm) throw new Error('wasm not initialized') // Copy msg.arrBodies array into the wasm instance, increase GC count const dataRef = wasm.__retain(wasm.__allocArray(wasm.FLOAT64ARRAY_ID, msg.arrBodies)); // Do the calculations in this thread synchronously const resultRef = wasm.nBodyForces(dataRef); // Copy result array from the wasm instance to our javascript runtime const arrForces = wasm.__getFloat64Array(resultRef); // Decrease the GC count on dataRef from __retain() here, // and GC count from new Float64Array in wasm module wasm.__release(dataRef); wasm.__release(resultRef); // Message results back to main thread. // see nBodySimulation.js this.worker.onmessage return this.postMessage({ purpose: 'nBodyForces', arrForces }) } }
Com tudo o que aprendemos, vamos revisar nossa jornada do web worker e WebAssembly. Bem-vindo ao novo back-end do navegador da web. Estes são os links para o código no GitHub:
- GET Index.html
- main.js
- nBodySimulator.js - passa uma mensagem para seu web worker
- workerWasm.js - chama a função WebAssembly
- nBodyForces.ts - calcula e retorna uma série de forças
- workerWasm.js - passa os resultados de volta para o tópico principal
- nBodySimulator.js - resolve a promessa de forças
- nBodySimulator.js - então aplica as forças aos corpos e diz aos visualizadores para pintar
A partir daqui, vamos começar o show criando nBodyVisualizer.js
! Nossa próxima postagem cria um visualizador usando a API Canvas e a postagem final termina com WebVR e Aframe.
Relacionado: WebAssembly / Rust Tutorial: Pitch-perfect Audio Processing Compreender o básico
O WebAssembly pode substituir o JavaScript?
WebAssembly não é uma linguagem, portanto, não pode substituir o JavaScript. Além disso, desenvolver recursos e experiência do usuário no WebAssembly é menos eficiente.
Por que o WebAssembly é mais rápido?
O WebAssembly é mais rápido porque faz menos e foi projetado para desempenho em vez de usabilidade do desenvolvedor.
O JavaScript pode ser compilado para WebAssembly?
Sim, o AssemblyScript compila para WebAssembly e se parece com o Typescript.
)) (export '__alloc' (func $~lib/rt/tlsf/__alloc)) (export '__retain' (func $~lib/rt/pure/__retain)) (export '__release' (func $~lib/rt/pure/__release)) (export '__collect' (func $~lib/rt/pure/__collect)) (export '__rtti_base' (global $~lib/rt/__rtti_base)) ;; Finally our exported constants and function (export 'FLOAT64ARRAY_ID' (global $nBodyForces/FLOAT64ARRAY_ID)) (export 'G' (global $nBodyForces/G)) (export 'bodySize' (global $nBodyForces/bodySize)) (export 'forceSize' (global $nBodyForces/forceSize)) (export 'nBodyForces' (func $nBodyForces/nBodyForces)) ;; Implementation details ...
Agora temos nosso nBodyForces.wasm
binário e um web worker para executá-lo. Prepare-se para a decolagem! E algum gerenciamento de memória!
Para completar a integração, temos que passar um array variável de floats para WebAssembly e retornar um array variável de floats para JavaScript.
Com o JavaScript burguês ingênuo, decidi passar arbitrariamente esses arrays de tamanho variável espalhafatosos para dentro e para fora de um runtime de alto desempenho de plataforma cruzada. Passar dados de / para o WebAssembly foi, de longe, a dificuldade mais inesperada neste projeto.
No entanto, com muito obrigado pelo levantamento pesado feito pela equipe do AssemblyScript , podemos usar seu “carregador” para ajudar:
// workerWasm.js - our web worker /** * AssemblyScript loader adds helpers for moving data to/from AssemblyScript. * Highly recommended */ const loader = require('assemblyscript/lib/loader')
O require()
significa que precisamos usar um empacotador de módulo como Rollup ou Webpack. Para este projeto, escolhi Rollup por sua simplicidade e flexibilidade e nunca olhei para trás.
Lembre-se de que nosso web worker é executado em um thread separado e é essencialmente um onmessage()
função com um switch()
declaração.
loader
cria nosso módulo wasm com algumas funções de gerenciamento de memória úteis extras. __retain()
e __release()
gerenciar referências de coleta de lixo no tempo de execução do trabalhador __allocArray()
copia nosso array de parâmetros para a memória do módulo wasm __getFloat64Array()
copia a matriz de resultado do módulo wasm para o tempo de execução do trabalhador
Agora podemos empacotar matrizes flutuantes dentro e fora de nBodyForces()
e conclua nossa simulação:
// workerWasm.js /** * Web workers listen for messages from the main thread. */ this.onmessage = function (evt) { // message from UI thread var msg = evt.data switch (msg.purpose) { // Message: Load new wasm module case 'wasmModule': // Instantiate the compiled module we were passed. wasm = loader.instantiate(msg.wasmModule, importObj) // Throws // Tell nBodySimulation.js we are ready this.postMessage({ purpose: 'wasmReady' }) return // Message: Given array of floats describing a system of bodies (x,y,x,mass), // calculate the Grav forces to be applied to each body case 'nBodyForces': if (!wasm) throw new Error('wasm not initialized') // Copy msg.arrBodies array into the wasm instance, increase GC count const dataRef = wasm.__retain(wasm.__allocArray(wasm.FLOAT64ARRAY_ID, msg.arrBodies)); // Do the calculations in this thread synchronously const resultRef = wasm.nBodyForces(dataRef); // Copy result array from the wasm instance to our javascript runtime const arrForces = wasm.__getFloat64Array(resultRef); // Decrease the GC count on dataRef from __retain() here, // and GC count from new Float64Array in wasm module wasm.__release(dataRef); wasm.__release(resultRef); // Message results back to main thread. // see nBodySimulation.js this.worker.onmessage return this.postMessage({ purpose: 'nBodyForces', arrForces }) } }
Com tudo o que aprendemos, vamos revisar nossa jornada do web worker e WebAssembly. Bem-vindo ao novo back-end do navegador da web. Estes são os links para o código no GitHub:
A partir daqui, vamos começar o show criando nBodyVisualizer.js
! Nossa próxima postagem cria um visualizador usando a API Canvas e a postagem final termina com WebVR e Aframe.
WebAssembly não é uma linguagem, portanto, não pode substituir o JavaScript. Além disso, desenvolver recursos e experiência do usuário no WebAssembly é menos eficiente.
O WebAssembly é mais rápido porque faz menos e foi projetado para desempenho em vez de usabilidade do desenvolvedor.
Sim, o AssemblyScript compila para WebAssembly e se parece com o Typescript.