1) (data (i32.const 8) '1e000100010001e000~0l0i0b0/0r0t0/0t0l0s0f0.0t0s0') ... ;; 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

WebVRパート3:WebAssemblyとAssemblyScriptの可能性を解き放つ

WebAssemblyは間違いなく ない Webと世界の共通語としてのJavaScriptの代替品。

WebAssembly(略称Wasm)は、スタックベースの仮想マシン用のバイナリ命令フォーマットです。 Wasmは、C / C ++ / Rustなどの高級言語をコンパイルするためのポータブルターゲットとして設計されており、クライアントおよびサーバーアプリケーションのWebへのデプロイを可能にします。」 - WebAssembly.org

WebAssemblyは言語ではないことを区別することが重要です。 WebAssemblyは、Javaの「.class」ファイルのようなものです。 Web開発者が別の言語からコンパイルし、ダウンロードしてブラウザで実行します。



WebAssemblyは、JavaScriptに、時々借りたいと思っていたが決して借りたくないすべての機能を提供しています。 本当に 所有したかった。ボートや馬を借りるのと同じように、WebAssemblyを使用すると、贅沢な「言語ライフスタイル」を選択しなくても、他の言語に旅行できます。これにより、Webは、機能の提供やユーザーエクスペリエンスの向上などの重要なことに集中できるようになりました。

Rust、C / C ++、C#/。Net、Java、Python、Elixir、Go、そしてもちろんJavaScriptの20以上の言語がWebAssemblyにコンパイルされます。

シミュレーションのアーキテクチャ図を覚えている場合は、シミュレーション全体をnBodySimulatorに委任したため、Webワーカーが管理されます。

シミュレーションのアーキテクチャ図

図1:全体的なアーキテクチャ。

あなたがから覚えているなら イントロポストnBodySimulator step()があります33msごとに呼び出される関数。 step()関数はこれらのことを行います-上の図で番号が付けられています:

  1. nBodySimulatorのcalculateForces()呼び出しthis.worker.postMessage()計算を開始します。
  2. workerWasm.js this.onmessage()メッセージを取得します。
  3. workerWasm.jsは、nBodyForces.wasmのnBodyForces()を同期的に実行します関数。
  4. workerWasm.jsはthis.postMessage()を使用して返信します新しい力でメインスレッドに。
  5. メインスレッドのthis.worker.onMessage()返されたデータと呼び出しをマーシャリングします。
  6. nBodySimulatorのapplyForces()ボディの位置を更新します。
  7. 最後に、ビジュアライザーが再描画します。

UIスレッド、Webワーカースレッド

図2:シミュレータのstep()関数の内部

前の投稿 、WASM計算をラップするWebワーカーを構築しました。今日、私たちは「WASM」というラベルの付いた小さなボックスを構築し、データを出し入れしています。

簡単にするために、私は AssemblyScript 計算を書くためのソースコード言語として。 AssemblyScriptはTypeScriptのサブセット(型付きJavaScript)であるため、既にご存知でしょう。

たとえば、このAssemblyScript関数は、2つのボディ間の重力を計算します。:f64 someVar:f64でsomeVar変数をコンパイラのfloatとしてマークします。このコードは、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[]

このAssemblyScript関数は、2つのボディの(x、y、z、mass)を受け取り、ボディが相互に適用する(x、y、z)力ベクトルを表す3つのfloatの配列を返します。 JavaScriptはどこにあるのかわからないため、JavaScriptからこの関数を呼び出すことはできません。 JavaScriptに「エクスポート」する必要があります。これは私たちの最初の技術的課題につながります。

WebAssemblyのインポートとエクスポート

ES6では、JavaScriptコードでのインポートとエクスポートについて検討し、RollupやWebpackなどのツールを使用して、レガシーブラウザーで実行されてimportを処理するコードを作成します。およびrequire()。これにより、トップダウンの依存関係ツリーが作成され、「 木を振る 」と コード分​​割

WebAssemblyでは、インポートとエクスポートはES6インポートとは異なるタスクを実行します。 WebAssemblyのインポート/エクスポート:

以下のコードでは、env.abortおよびenv.trace WebAssemblyモジュールに提供する必要のある環境の一部です。 nBodyForces.logIおよびfriends関数は、コンソールにデバッグメッセージを提供します。 WebAssemblyの唯一のタイプはi32、i64、f32、f64の数値であり、i32は抽象線形メモリを参照しているため、WebAssemblyとの間で文字列を送受信することは簡単ではありません。

注意: これらのコード例は、JavaScriptコード(Webワーカー)とAssemblyScript(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(', ')); } } }

AssemblyScriptコードでは、次のようにこれらの関数のインポートを完了することができます。

// nBodyForces.ts declare function logI(data: i32): void declare function logF(data: f64): void

注意中止とトレースは自動的にインポートされます

AssemblyScriptから、インターフェイスをエクスポートできます。エクスポートされた定数は次のとおりです。

// 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

そしてこれがnBodyForces()のエクスポートですこれをJavaScriptから呼び出します。タイプFloat64Arrayをエクスポートしますファイルの先頭にあるので、WebワーカーでAssemblyScriptのJavaScriptローダーを使用してデータを取得できます(以下を参照)。

// 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

WebAssemblyアーティファクト:.wasmおよび.wat

AssemblyScript nBodyForces.tsの場合WebAssemblyにコンパイルされます nBodyForces.wasmバイナリ 、バイナリの命令を説明する「テキスト」バージョンを作成するオプションもあります。

WebAssemblyアーティファクト

図3:AssemblyScriptは言語であることを忘れないでください。 WebAssemblyはコンパイラーおよびランタイムです。

nBodyForces.watの内部ファイル、これらのインポートとエクスポートを見ることができます:

;; 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) '1e000100010001e000~0l0i0b0/0r0t0/0t0l0s0f0.0t0s0') ... ;; 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 ...

これでnBodyForces.wasmができましたバイナリとそれを実行するWebワーカー。爆破の準備をしなさい!そして、いくつかのメモリ管理!

統合を完了するには、floatの変数配列をWebAssemblyに渡し、floatの変数配列をJavaScriptに返す必要があります。

ナイーブなJavaScriptブルジョアを使用して、これらの派手な可変サイズの配列をクロスプラットフォームの高性能ランタイムに出し入れしたいだけに着手しました。 WebAssemblyとの間でデータをやり取りすることは、このプロジェクトで最も予想外の困難でした。

しかし、多くの感謝を込めて AssemblyScriptチームによる手間のかかる作業 、私たちは彼らの「ローダー」を使って助けることができます:

// workerWasm.js - our web worker /** * AssemblyScript loader adds helpers for moving data to/from AssemblyScript. * Highly recommended */ const loader = require('assemblyscript/lib/loader')

require() RollupやWebpackのようなモジュールバンドラーを使用する必要があることを意味します。このプロジェクトでは、そのシンプルさと柔軟性のためにRollupを選択し、振り返ることはありませんでした。

Webワーカーは別のスレッドで実行され、基本的にonmessage()であることを忘れないでくださいswitch()で関数ステートメント。

loaderいくつかの便利なメモリ管理機能を備えたwasmモジュールを作成します。 __retain()および__release()ワーカーランタイムでガベージコレクション参照を管理する__allocArray()パラメータ配列をwasmモジュールのメモリにコピーします__getFloat64Array()結果の配列をwasmモジュールからワーカーランタイムにコピーします

これで、フロート配列をnBodyForces()の内外でマーシャリングできます。シミュレーションを完了します。

// 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 }) } }

学んだことをすべて終えたら、WebワーカーとWebAssemblyの旅を振り返ってみましょう。 Webの新しいブラウザバックエンドへようこそ。これらはGitHubのコードへのリンクです:

  1. Index.htmlを取得
  2. main.js
  3. nBodySimulator.js -Webワーカーにメッセージを渡します
  4. workerWasm.js -WebAssembly関数を呼び出します
  5. nBodyForces.ts -力の配列を計算して返します
  6. workerWasm.js -結果をメインスレッドに戻します
  7. nBodySimulator.js -力の約束を解決します
  8. nBodySimulator.js -次に、力を体に適用し、ビジュアライザーにペイントするように指示します

ここから、nBodyVisualizer.jsを作成してショーを始めましょう!次の投稿では、Canvas APIを使用してビジュアライザーを作成し、最後の投稿はWebVRとAframeで締めくくります。

関連: WebAssembly / Rustチュートリアル:ピッチパーフェクトなオーディオ処理

基本を理解する

WebAssemblyはJavaScriptを置き換えることができますか?

WebAssemblyは言語ではないため、JavaScriptを置き換えることはできません。また、WebAssemblyでの機能とユーザーエクスペリエンスの開発は効率が悪くなります。

WebAssemblyの方が速いのはなぜですか?

WebAssemblyは、パフォーマンスが低く、開発者の使いやすさではなくパフォーマンスを重視して設計されているため、高速です。

JavaScriptをWebAssemblyにコンパイルできますか?

はい、AssemblyScriptはWebAssemblyにコンパイルされ、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 ...

これでnBodyForces.wasmができましたバイナリとそれを実行するWebワーカー。爆破の準備をしなさい!そして、いくつかのメモリ管理!

統合を完了するには、floatの変数配列をWebAssemblyに渡し、floatの変数配列をJavaScriptに返す必要があります。

ナイーブなJavaScriptブルジョアを使用して、これらの派手な可変サイズの配列をクロスプラットフォームの高性能ランタイムに出し入れしたいだけに着手しました。 WebAssemblyとの間でデータをやり取りすることは、このプロジェクトで最も予想外の困難でした。

しかし、多くの感謝を込めて AssemblyScriptチームによる手間のかかる作業 、私たちは彼らの「ローダー」を使って助けることができます:

// workerWasm.js - our web worker /** * AssemblyScript loader adds helpers for moving data to/from AssemblyScript. * Highly recommended */ const loader = require('assemblyscript/lib/loader')

require() RollupやWebpackのようなモジュールバンドラーを使用する必要があることを意味します。このプロジェクトでは、そのシンプルさと柔軟性のためにRollupを選択し、振り返ることはありませんでした。

Webワーカーは別のスレッドで実行され、基本的にonmessage()であることを忘れないでくださいswitch()で関数ステートメント。

loaderいくつかの便利なメモリ管理機能を備えたwasmモジュールを作成します。 __retain()および__release()ワーカーランタイムでガベージコレクション参照を管理する__allocArray()パラメータ配列をwasmモジュールのメモリにコピーします__getFloat64Array()結果の配列をwasmモジュールからワーカーランタイムにコピーします

これで、フロート配列をnBodyForces()の内外でマーシャリングできます。シミュレーションを完了します。

グーグルグラスの使い方
// 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 }) } }

学んだことをすべて終えたら、WebワーカーとWebAssemblyの旅を振り返ってみましょう。 Webの新しいブラウザバックエンドへようこそ。これらはGitHubのコードへのリンクです:

  1. Index.htmlを取得
  2. main.js
  3. nBodySimulator.js -Webワーカーにメッセージを渡します
  4. workerWasm.js -WebAssembly関数を呼び出します
  5. nBodyForces.ts -力の配列を計算して返します
  6. workerWasm.js -結果をメインスレッドに戻します
  7. nBodySimulator.js -力の約束を解決します
  8. nBodySimulator.js -次に、力を体に適用し、ビジュアライザーにペイントするように指示します

ここから、nBodyVisualizer.jsを作成してショーを始めましょう!次の投稿では、Canvas APIを使用してビジュアライザーを作成し、最後の投稿はWebVRとAframeで締めくくります。

関連: WebAssembly / Rustチュートリアル:ピッチパーフェクトなオーディオ処理

基本を理解する

WebAssemblyはJavaScriptを置き換えることができますか?

WebAssemblyは言語ではないため、JavaScriptを置き換えることはできません。また、WebAssemblyでの機能とユーザーエクスペリエンスの開発は効率が悪くなります。

WebAssemblyの方が速いのはなぜですか?

WebAssemblyは、パフォーマンスが低く、開発者の使いやすさではなくパフォーマンスを重視して設計されているため、高速です。

JavaScriptをWebAssemblyにコンパイルできますか?

はい、AssemblyScriptはWebAssemblyにコンパイルされ、Typescriptのように感じられます。