このサイトの記事更新は2019年11月に終了されました。過去記事アーカイブを公開しています。
投稿されたすべてのトピック
Tokyo WebExtensions Meetup #1 が開催されました!
2018 年 3 月 2 日に Tokyo WebExtensions Meetup #1 がデジタルハリウッド大学大学院にて開催されました。
(デジハリさん、会場の提供ありがとうございました!)
20 名 + リモート参加 4 名の拡張機能開発者が集まり、拡張機能に関する問題点や要望などを題材とし、さまざまな観点から熱い議論が繰り広げられました!わお。
当日のスライドや議事録、twitter などは以下になります。
今回のミートアップでは、会の趣旨、Mozilla のスタンス、拡張機能をとりまく現状の説明を経て議論を開始。議題はミートアップの事前にみなで書き出して、それに沿って進んでいきました。拡張機能開発と一言で言っても、新規作成から開発、デバッグ、テスト、そしてリリースさらにはマーケティングなどのプロセスを踏みます。それぞれのプロセスや開発者のコンテクストによって、多様な観点から問題点や要望が浮かびましたが、今回は特に開発・デバッグ、翻訳に注目して議論が展開されました。(議事録を参照くださいませ)
そのあとは懇親会、ピザとお神酒が入り、議論がさらに活発化したのは言うまでもありません。
Tokyo WebExtensions Meetup はコミュニティベースのイベントです。
これから本ミートアップに関することは Mozilla Japan コミュニティ Slack の #extdev で決めていきます。
みなさんも是非ご参加くださいませ。もちろん拡張機能に関する質問なども自由に投稿ください。参加はこちらから。
次回は 5 月中旬をなんとなく予定しています。それでは!
WebAssembly は今どこにあり、次に何があるのか
[この記事は”Where is WebAssembly now and what’s next?“の翻訳です]この記事は WebAssembly と何が速くしたのかのシリーズの6部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。
2月28日、4つのメジャーなブラウザが WebAssembly の必要最低限の機能のみを持つ製品 (MVP) が完了したという コンセンサスを発表しました。
これにより、ブラウザが出荷できる安定したコアが提供されます。このコアには、コミュニティグループが計画している機能のすべてが含まれているわけではありませんが、WebAssembly を高速かつ使いやすくするために十分な機能を備えています。
これにより、開発者は WebAssembly コードの提供を開始できます。以前のバージョンのブラウザでは、開発者は asm.js バージョンのコードを提供できます。asm.js は JavaScript のサブセットであるため、どの JS エンジンでも実行できます。Emscripten を使用すると、同じアプリケーションを WebAssembly と asm.js の両方にコンパイルできます。
初回リリースであっても WebAssmbly は高速に動くでしょう。しかし修正と新規機能を導入により将来的にさらに高速になるでしょう。
ブラウザー上の WebAssembly のパフォーマンス向上
ブラウザーエンジンが WebAssembly のサポートを向上させるとスピードが向上します。ブラウザベンダーはこれらの問題を個々に取り組んでいます。
JS と WebAssembly 間の関数呼び出しの高速化
現在 JS からの WebAssembly 関数呼び出しは求められるスピードに達していません。その理由は「トランポリン」と呼ばれることが原因です。JIT は WebAssembly をどのように扱えばよいのか知らないため、WebAssemblyを何かにルーティングする必要があります。これは最適化された WebAssembly コードを実行するためのセットアップ時のエンジン自体のコードが遅い箇所です。
これは JIT が直接処理する方法を知っていた場合よりも、最大100倍遅くなることがあります。
単一の大きなタスクを WebAssembly モジュールに渡している場合は、このオーバーヘッドに気付かないでしょう。しかし WebAssembly と JS の間で多くのやりとりがある場合 (小規模な作業と同じように)、このオーバーヘッドが目立ちます。
より速いロード時間
JIT はより速いロード時間とより速い実行時間の間のトレードオフを管理する必要があります。コンパイルや最適化に時間を費やすと実行速度が向上しますが、起動が遅くなります。
先進的なコンパイル (コードが実行されるとジャンクが発生しないようにする) と、コードのほとんどの部分がそれに見合った最適化を行うために十分な時間実行されないという基本的な事実とのバランスを取るために、多くの進行中の作業があります。
WebAssembly はどの型が使用されるかを特定する必要が無いため、エンジンは実行時に型をモニターする必要がありません。これは実行時の平行コンパイルといった例のような様々なオプションをもたらします。
さらに JavaScript API への最近の追加では WebAssembly のストリーミング・コンパイルが可能です。これは WebAssembly のデータをダウンロード中にエンジンがコンパイルを開始できることを意味します。
Firefox では二つのコンパイラーを使用するシステムになっています。一つ目のコンパイラは事前に実行し、コードを最適化を行います。コードの実行中は別のコンパイラがバックグラウンドで完全な最適化を行います。完全に最適化されたバージョンのコードへ準備が整うと切り替わります。
MVP リリース以降の機能導入
WebAssembly の目標の 1 つは、すべての機能を設計するのではなく、小さなチャンクで指定し、途中でテストを行うことです。
期待されている機能はたくさんあるが、100% の考え方はまだ存在しないことを意味しています。これらはブラウザベンダが参加している使用策定プロセスを経る必要があります。
これらの機能は将来の機能と呼ばれています。少し紹介します。
DOM を直接扱う
現在 DOM を扱う手段はありません。これは WebAssembly から element.innerHTML
のようにノードを更新できないこと意味します。
代わりに、値を設定するために JS を経由する必要があります。これは値を JavaScript 呼び出し側に渡すことを意味します。一方、WebAssembly 内から JavaScript 関数を呼び出すことは、JavaScript と WebAssembly の両方の関数を WebAssembly モジュールをインポートして使用できることを意味します。
いずれにしても、JavaScript を使用することは直接アクセスよりも遅くなる可能性があります。WebAssembly の一部のアプリケーションはこれが解決されるまでが続くかもしれません。
共有メモリーの同時実行性
コードを高速化する方法の 1 つは、コードの異なる部分を同時に並列で実行できるようにすることです。しかし、スレッド間の通信のオーバーヘッドは、タスクを分けずに実行するよりも多くの時間を要する可能性があるため、時には逆効果になる場合があります。
しかしスレッド間のシェアードメモリを使用すればこのオーバーヘッドを削減できます。これを実施するため WebAssembly は JavaScript の新しい SharedArrayBuffer を使用すべきです。これがブラウザに組み込まれると、ワーキンググループは WebAssembly の動作方法を指定することができます。
SIMD
WebAssembly に関する他の投稿を読んだり、話を聞いたりすると、SIMD のサポートについて聞かれることがあります。頭字語は、単一命令、複数のデータを表します。物事を並行して運用する別の方法です。
SIMD を使用すると、異なる数のベクトルのような大きなデータ構造を取り、同じ命令を異なるパーツに同時に適用することができます。このようにして、ゲームや VR に必要な複雑な計算を大幅に高速化できます。
これらは平均的な Web 開発者にとってさほど重要ではありません。しかしゲーム開発者のようなマルチメディアを扱う開発者にとって非常に重要です。
例外ハンドリング
C++ のような言語の多くのコードは例外を使用しています。しかし例外は WebAssembly の一部としてまだ定義されていません。
あなたのコードを Emscripten でコンパイルした場合、いくつかのコンパイラ最適化レベルの例外処理をエミュレートします。これは非常に遅いため、DISABLE_EXCEPTION_CATCHING
フラグを OFF にできます。
WebAssembly で例外がネイティブに処理できるようになると、このエミュレーションは必要ありません。
開発者にとってより簡単にするその他の改善
いくつかの将来の機能はパフォーマンスは改善しませんが、WebAssembly を使用するにあたり開発者がより簡単に使用できるようになるでしょう。
- ファーストクラスのソースレベル開発ツール. 現在ブラウザ上で WebAssembly をデバッグすることは、生のアセンブリーをデバッグするようなものです。しかし、ソースコードがアセンブリにどのように変換されるかに関して精通している開発者はほとんどいません。開発者がソースコードをデバッグできるように、ツールサポートを改善する方法を検討しています。
- ガベージコレクション. 事前に型を定義できる場合は、コードを WebAssembly に変換する必要があります。したがって TypeScript のようなコードを使用するコードは、WebAssembly にコンパイル可能である必要があります。WebAssembly は JS エンジンに組み込まれているような既存のガベージコレクタとやりとりができないというのが現状です。この将来の機能のアイデアは、WebAssembly に低レベルの GC プリミティブタイプとオペレーションのセットを使用して、組み込み GC へのファーストクラスのアクセスを与えることです。
- ES6 モジュールの統合. ブラウザは現在、script タグを使用して JavaScript モジュールを読み込むためのサポートを追加しています。 この機能を追加すると、url が WebAssembly モジュールを指している場合でも
<script src=url type="module">
のようなタグが機能します。
結論
WebAssembly は今日でも高速で、そしてブラウザの新しい機能や実装の改良により、さらに高速化するはずです。
Lin Clark に関して
WebAssembly を速くするには?
例えば React に取り組んでいるチームは、調整コード (別名仮想 DOM) を WebAssembly バージョンに置き換えることができます。React を使う人は WebAssembly を使用するメリットを除いて、これらのアプリはまったく同じように動作し、何もする必要はありません。
React チームのメンバーのような開発者がこの転換を行う理由は、WebAssembly が高速であるためです。しかし何が高速にしているのでしょうか?
JavaScript のパフォーマンスは現在どうなっているのか?
JavaScript と WebAssembly の間のパフォーマンスの差を理解する前に、私たちは JS エンジンがどのように動くかを理解する必要があります。
この図は今日のアプリケーションの起動時のパフォーマンスがどうなっているかの概略図です。
JS エンジンがこれらのタスクを実行する時間は、ページが使用する JavaScript により異なります。 この図は正確なパフォーマンスの数値を表すものではありません。代わりに JS と WebAssembly で同じ機能のパフォーマンスがどのように異なるかについて、高度なモデルを提供することを目的としています。
各バーには、特定のタスクを行うのに費やされた時間が表示されます。
- 構文解析 — ソースコードをインタプリタが実行できるものに処理するまでにかかる時間。
- コンパイル + 最適化 — ベースラインコンパイラと最適化コンパイラで費やされる時間。最適化コンパイラの作業の幾つかはメインスレッドに含まれていないため、ここでは加えていません。
- 再最適化 — JITが前提条件が満たされていないときに再調整を行う時間。コードの再最適化と最適化されたコードからのベースラインコードへの復帰。
- 実行 — コードを実行するのにかかる時間。
- ガベージコレクション — メモリのクリーンアップに費やされた時間。
注目すべき重要な点:これらのタスクが個別のチャンクまたは特定の順序で実行されないことです。代わりにインターリーブされます。ほんの少しの解析が行われ、次にいくつかの実行が行われ、次にコンパイルされ、さらに解析され、さらに実行されるなどします。
このような内訳によるパフォーマンスのは JavaScript の初期段階からの大きく改善され、以下のようになっています:
初期の JavaScript 実行時の変換処理だけだった頃は実行速度は非常に遅かったです。JIT が導入されると、実行時間が大幅に短縮されました。
トレードオフはコードの監視とコンパイルです。もし JavaScript の開発者が今までと同じように JavaScript を書き続けた場合、解析とコンパイル時間が非常に短くなります。改善されたパフォーマンスにより開発者はより大きな JavaScript アプリケーションを作成することができるようになりました。
これはまだ改善の余地があることを意味します。
WebAssembly をどのように比較しますか?
典型的な Web アプリケーションにおける WebAssembly の比較方法を概説します。
ブラウザがこれらのフェーズをどのように処理するかは、ブラウザにより若干の違いがあります。ここでは SpiderMonkey を使用します。
フェッチ
これは図には表示していませんが、時間がかかるのはサーバーからファイルを取得することだけです。
WebAssembly は JavaScript よりもコンパクトなため、フェッチはより速くなります。コンパクションアルゴリズムは JavaScript バンドルのサイズを大幅に縮小することができますが、WebAssembly の圧縮バイナリ表現をまだまだ小さくすることは可能です。
これはサーバーとクライアント間の転送時間をより短くできることを意味します。これは特に低速ネットワークでは当てはまります。
解析
ブラウザへ届くと JavaScript ソースが抽象構文木に解析されます。
ブラウザはたいていこれを遅延させ、最初は本当に必要なものを解析し、まだ呼び出されていない関数のスタブを作成するだけです。
そこから、AST はその JS エンジンに固有の中間表現 (バイトコードと呼ばれる) に変換されます。
対照的に、WebAssembly はすでに中間表現であるため、変換を行う必要はありません。デコードしてエラーがないか検証するのみです。
コンパイル + 最適化
JIT についての記事で説明したように、JavaScript はコードの実行時にコンパイルされます。実行時に使用されるランタイムに応じて、同じコードの複数のバージョンをコンパイルする必要があります。
ブラウザが異なると WebAssembly のコンパイル方法も異なります。WebAssembly を実行する前にベースラインのコンパイルを行うブラウザもあれば、JIT を使用するものもあります。
どちらにしても、WebAssembly は機械語に非常に近い状態で始まります。例えば、型はプログラムの一部です。これはいくつかの理由により高速です:
- コンパイラは最適化されたコードのコンパイルを開始する前に、コードにどの型を使用されているかを判断するために時間を費やす必要はありません。
- コンパイラは異なる種類のコードに基づいて同じコードの異なるバージョンをコンパイルする必要はありません。
- LLVM では、より多くの最適化が事前に行われています。それによりコンパイルと最適化に必要な作業が少なくて済みます。
再最適化
場合によっては、JIT は最適化されたバージョンのコードをスローして再試行する必要があります。
これは、JIT が実行中のコードに基づいて行う前提が正しくないことが判明した時に発生します。例えばループに入ってくる変数が以前の反復時の変数と異なる場合や、新しい関数がプロトタイプチェーンに挿入された場合に、最適化解除が実施されます。
最適化解除には二つのコストがあります。一つ目は最適化されたコードを取り除いてベースラインのバージョンに戻すのに時間がかかります。二つ目はその関数が依然として多く呼び出されている場合、JIT は最適化コンパイラを介して再度送信する場合があります。そのため2回コンパイルするコストがあります。
WebAssembly では、型のようなものは明白であるため、JIT は実行時に収集するデータに基づいた型の仮定を行う必要はありません。つまり再最適化サイクルを行う必要はありません。
実行
効率よく実行可能な JavaScript を書くことができます。これを行うには JIT が行う最適化について知る必要があります。例えば JIT の記事で説明したように、コンパイラが行う型の特殊化が行う事ができるようなコードを書く方法を知る必要があります。
しかしほとんどの開発者が JIT の内部構造は知りません。このような JIT の内部構造を知っている開発者であってもスイートスポットをヒットすることは難しいでしょう。コードをより読みやすくするために使用する多くのコーディングパターン (一般的なタスクをタイプ間で機能する関数に抽象化するなど) は、コンパイラーがコードを最適化するときに邪魔になります。
さらに、JIT が使用する最適化はブラウザによって異なっているため、あるブラウザの内部構造に合わせてコーディングすると、別のブラウザのでのードのパフォーマンスが低下する可能性があります。
このため、WebAssembly でコードを実行する方が一般的に高速です。WebAssembly では JIT が JavaScript に行う最適化の多く (型の特殊化など) は必要ありません。
また、WebAssembly はコンパイラのターゲットとして設計されています。これはコンパイラーが生成するように設計されており、人間のプログラマーが書くようには設計されていません。
人間のプログラマーは直接 WebAssembly を直接プログラミングする必要が無いため、WebAssembly は機械にとって理想的な一連の命令を提供できます。コードが動作する内容次第で、これらの命令は 10% から 800% 速く実行することができます。
ガベージコレクション
JavaScript では開発者は必要なくなった古い変数をメモリからクリアすることについて心配する必要はありません。代わりに JS エンジンは自動的にガベージコレクタと呼ばれるものを使用します。
ただし予測可能なパフォーマンスが必要な場合は問題になる可能性があります。ガベージコレクタがいつ動作するのかは制御できないため、都合の悪い時間に実施することがあります。ほとんどのブラウザではスケジューリングをかなりうまく行っていますが、コードの実行の途中でオーバーヘッドになることがあります。
少なくとも今のところ、WebAssembly はガベージコレクションをサポートしていません。メモリは C や C++ のように手動で管理されます。 これは開発者にとってプログラミングを難しくする一方で、パフォーマンスの一貫性も向上させます。
結論
WebAssembly は JavaScript よりも多くのケースで高速です:
- WebAssembly のフェッチは圧縮された状態であっても JavaScript より時間がかかりません。
- WebAssembly のデコードは JavaScript を解析するよりも時間がかかりません。
- WebAssembly は JavaScript よりも機械語に近いため、コンパイルと最適化に要する時間が短縮され、すでにサーバー側で最適化が行われています。
- WebAssembly には型やその他の情報が組み込まれているため、再最適化を行う必要はありません。そのため JS エンジンは JavaScript を使用方法を最適化するときに推測を行う必要はありません。
- 開発者が効率良いコードを書くために知っておく必要があるコンパイラのトリックや落とし穴が少ないために、実行時間は短く済み、また WebAssembly の命令セットが機械にとってより理想的です。
- ガベージコレクションはメモリーを手動で管理するため必要としません。
このため、多くの場合、WebAssembly は同じタスクを実行するときに JavaScript より優れたパフォーマンスを発揮します。
WebAssembly が期待通りの性能を発揮しない場合もありますし、より速くする範囲にもいくつかの変更があります。水平方向にもいくつかの変更があり、高速化する場合もあります。次の記事でこれらをカバーします。
Lin Clark に関して
WebAssembly モジュールの作成と操作
実際、開発者は同じアプリケーションで WebAssembly と JavaScript の両方を使用することになります。WebAssembly を自分で作成しないなくても、それを利用可能です。
WebAssembly のモジュールは JavaScript から使用できるように関数を定義することができます。したがって、npm today から lodashの ようなモジュールをダウンロードし、その API の一部である関数を呼び出すように、将来、WebAssembly モジュールをダウンロードすることができるようになるでしょう。
では WebAssembly のモジュールをどのように作成し、JavaScript からどのように呼び出せるのかを見てみましょう。
WebAssembly はどこに適合するのか?
Assembly についての記事の中で、コンパイラが高水準のプログラミング言語をどのようにして機械語に翻訳するのかを話しました。
WebAssembly はこの図のどこに収まるのですか?
ターゲットアセンブリ言語の単なる別のものだと考えるかもしれません。これらの言語 (x86、ARM) のそれぞれが特定のマシンアーキテクチャに対応している点を除いて、それは事実です。
Web 上でユーザーのマシン上で実行されるコードを提供しているときは、コードが実行されるターゲットアーキテクチャはわかりません。
したがって、WebAssembly は他のアセンブリとは少し異なります。実際の物理マシンではなく、仮想マシンの機械語になっています。
このため、WebAssembly の命令は仮想命令と呼ばれることもあります。それらは、JavaScript のソースコードよりもはるかに機械語に直接マッピングされています。ごく一般的なハードウェアで効率的に行うことができることの一種です しかし、特定のハードウェアに対する特定の機械語への直接のマッピングではありません。
ブラウザが WebAssembly をダウンロードします。次に、WebAssembly からターゲットマシンのアセンブリコードまでの短い期間に使用します。
.wasm 形式へコンパイルする
WebAssembly を現在最もサポートしているコンパイラツールチェーンは LLVM です。LLVM に接続できる様々なフロントエンドとバックエンドがあります。
注記: ほとんどの WebAssembly のモジュール開発者は、C や Rust といった言語でコードを作成し、WebAssembly にコンパイルしますが、WebAssembly のモジュールを作成するには他の方法もあります。例えばTypeScript を使用して WebAssembly のモジュールを構築するのに役立つ実験的なツールや、WebAssemblyのテキスト表現を直接コーディングする方法もあります。
C から WebAssembly に変換したいとしましょう。clangフロントエンドを使用して、C から LLVM IR (中間コード) に変換することができます。LLVM は LLVM IR を理解します。そのため、LLVM はいくつかの最適化を実行できます。
LLVM IR (中間コード) から WebAssembly に変換するには、バックエンドが必要です。LLVM プロジェクトには現在進行中のものがあります。バックエンドはその大部分であり、すぐに終了するものです。しかし現在はこれを動作させるのはトリッキーかもしれません。
Emscripten と呼ばれる別のツールがありますが、これは現時点では少し使いやすくなっています。それは別のターゲット (asm.js と呼ばれる) にコンパイルし、それを WebAssembly に変換することで WebAssembly を生成できる独自のバックエンドを持っています。これは内部で LLVM を使用するので、Emscripten から 2 つのバックエンドを切り替えることができます。
Emscriptenには、C/C++ コードベース全体を移植可能にする多くのツールとライブラリが含まれているため、コンパイラよりもソフトウェア開発キット (SDK) のほうが多くなっています。例えば、システム開発者は、読み書きが可能なファイルシステムに慣れているため、Emscripten は IndexedDB を使用してファイルシステムをシミュレートできます。
使用したツールチェーンにかかわらず、最終的にはファイルは .wasm 形式になります。以下で .wasm ファイルの構造について詳しく説明します。まず、JS でどのように使用できるかを見てみましょう。
JavaScript で .wasm モジュールを読み込む
.wasm ファイルは WebAssembly のファイルであり、JavaScript から読み込むことができます。現在のところ、読み込む処理は少し複雑です。
function fetchAndInstantiate(url, importObject) { return fetch(url).then(response =&gt; response.arrayBuffer() ).then(bytes =&gt; WebAssembly.instantiate(bytes, importObject) ).then(results =&gt; results.instance ); }
詳細はこのドキュメントを確認してください。
私たちはこのプロセスをより簡単にしようと取り組んでいます。私たちはツールチェーンを改善し、Webpack のようなモジュールバンドルや SystemJS のようなローダーと統合することを期待しています。WebAssembly モジュールの読み込みは、JavaScript の読み込みと同じくらい簡単です。
WebAssembly モジュールと JS モジュールの間には大きな違いがあります。現在 WebAssembly の関数では、数値 (整数または浮動小数点数) のみをパラメータまたは戻り値として使用できます。
文字列のように複雑なデータ型の場合、WebAssembly モジュールのメモリを使用する必要があります。
もし JavaScript を使って作業していたのであれば、メモリに直接アクセスすることはあまり馴染みがありません。 C、C ++、Rust のようなパフォーマンスの高い言語は、手作業によるメモリ管理を行う必要があります。WebAssembly モジュールのメモリは、これらの言語で見られるヒープをシミュレートします。
これを使用するには、ArrayBuffer という JavaScript の型を使用します。配列バッファはバイトの配列です。したがって、配列のインデックスはメモリアドレスとして機能します。
JavaScript と WebAssembly の間に文字列を渡す場合は、文字を文字コードに変換します。次にメモリ配列に書き込みます。インデックスは整数なので、WebAssembly 関数にインデックスを渡すことができます。したがって、文字列の最初の文字のインデックスをポインタとして使用できます。
WebAssembly モジュールを開発している Web 開発者の場合、そのモジュール周辺にラッパーを作成すべきでしょう。そうすることによってモジュールの使用者はメモリ管理について知る必要はありません。
詳細を知りたい場合は、WebAssembly のメモリの動作を確認してください。
.wasm ファイルの構造
高級言語でコードを記述し、WebAssembly にコンパイルする場合、WebAssembly モジュールの構造を知る必要はありませんが、基本を理解するのに役立ちます。
まだ知らない場合はWebAssembly の記事 (シリーズの第3部) を読むことをお勧めします。
WebAssembly に変換する C 関数を次に示します:
int add42(int num) { return num + 42; }
WASM Explorer を使用してこの機能をコンパイルすることができます。
.wasmファイルを開くと (エディタで表示がサポートされている場合) 次のように表示されます。
00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60 01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80 80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06 81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65 6D 6F 72 79 02 00 09 5F 5A 35 61 64 64 34 32 69 00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20 00 41 2A 6A 0B
これは “バイナリ” 表現のモジュールです。バイナリは通常 16 進表記で表示されますが、簡単にバイナリ表記や人間が読める形式に変換できるため、バイナリの周りに引用符を入れました
例えば、num + 42
のようになります。
コードの仕組み:スタックマシン
あなたが疑問に思っている場合、ここではそれらの指示で何をするのでしょうか。
add
操作でその値がどこから来るのかはわかりませんでした。これは WebAssembly がスタックマシンと呼ばれるものの例であるためです。これは操作が必要とするすべての値が、操作が実行される前にスタックにキューイングされていることを意味します。
add
のような操作は、必要な値の数を知っています。add
は 2 つ必要なため、スタックの先頭から 2 つの値をとります。これは命令がソースレジスタまたはデスティネーションレジスタを指定する必要がないため、add
命令が短く (1Byte) できることを意味します。これにより、.wasm ファイルのサイズが縮小され、ダウンロードに要する時間が短縮されます。
WebAssembly はスタックマシンの観点から指定されていますが、実際のマシンでは動作しません。ブラウザが実行中のマシンのマシンコードにブラウザが WebAssembly を変換すると、レジスタが使用されます。 WebAssembly コードではレジスタが指定されていないため、ブラウザにはそのマシンに最適なレジスタ割り当てを柔軟に使用できます。
モジュールのセクション
add42
関数自体に加えて、.wasm ファイルには他の部分もあります。これらはセクションと呼ばれます。セクションのいくつかは任意のモジュールに必要であり、いくつかはオプションです。
必須:
- Type. モジュールおよびインポートされた関数で定義された関数の関数シグネチャが含まれます。
- Function. モジュールで定義された各関数へのインデックスを与えます。
- Code. モジュール内の各関数の本体。
オプション:
- Export. 関数、メモリ、テーブル、グローバルを他の WebAssembly モジュールや JavaScript で使用できるようにします。これにより、別々にコンパイルされたモジュールを動的にリンクすることができます。これは WebAssembly の .dll バージョンです。
- Import. 他の WebAssembly モジュールまたは JavaScript からインポートする関数、メモリ、テーブル、およびグローバルを指定します。
- Start. WebAssembly モジュールがロードされたときに自動的に実行される関数 (基本的にメイン関数のような)。
- Global. モジュールのグローバル変数を宣言します。
- Memory. メモリをモジュールでどのように使うか定義します。
- Table. JavaScript オブジェクトなど WebAssembly モジュールの外部にある値にマップすることができます。これは間接的な関数呼び出しを許可する場合に特に便利です。
- Data. インポートした、またはローカルのメモリを初期化します。
- Element. インポートした、またはローカルのテーブルを初期化します。
セクションの詳細については、これらのセクションが詳しく説明しています。
次の記事
WebAssembly モジュールを使用する方法を勉強したので、WebAssembly を速くするには?を見てみましょう。
Lin Clark に関して
WebAssembly の短期集中コース
JIT の記事では、機械とのやりとりがエイリアンとのコミュニケーションのようなものだと話しました。
この脳の一部は、思考に専念しています。つまり、加減算や論理演算などです。短期記憶を提供する脳の部分と、長期記憶を提供するもう一つの部分もあります。
これらの異なる部分には名前があります。
- 思考を行う部分は算術論理ユニット (ALU) です。
- 短期記憶はレジスタによって提供されます。
- 長期記憶はランダムアクセスメモリ (もしくはRAM) です。
マシン語の文章は命令と呼びます。
これらの命令の 1 つが脳に入ったらどうなりますか?それは異なることを意味する異なる部分に分割されます。
例えばこのように配線された脳は、常に最初の 6 ビットを取り、それを ALU にパイプすることができます。 ALU は 1 と 0 の位置に基づいて、2 つのものを一緒に追加する必要があることを理解します。
ここには、この機械のためのアセンブリと機械語の間にかなり直接的な関係があることがわかります。このため、さまざまな種類の機械のアーキテクチャー向けにに異なる種類のアセンブリがあります。機械の内部で別のアーキテクチャを使用している場合は、独自の方言のアセンブリが必要になる可能性があります。
人からエイリアンへの翻訳では英語、ロシア語、または北京語からエイリアン語Aまたはエイリアン語Bに移行している可能性があります。プログラミング上、これはC、C ++、Rustからx86またはARMへの変換に似ています。
これらの高レベルのプログラミング言語のいずれかをこれらのアセンブリ言語のいずれかに翻訳したいと考えています (異なるアーキテクチャに対応しています)。これを行うための 1 つの方法は、各言語から各アセンブリに変換することができるさまざまな翻訳者をたくさん作成することです。
それはかなり非効率的になるでしょう。これを解決するため、ほとんどのコンパイラは少なくとも 1 つのレイヤを中間に配置します。コンパイラはこの高水準のプログラミング言語をとり、これを高レベルではないものに変換しますが、機械語のレベルでは動作しません。 それは中間表現 (IR) と呼ばれています。
これはコンパイラがこれらの上位レベルの言語のいずれかを取り、それを 1 つの IR 言語に翻訳できることを意味します。そこから、コンパイラの別の部分がその IR を取得し、ターゲットアーキテクチャに固有のものにコンパイルすることができます。
コンパイラのフロントエンドは、上位レベルのプログラミング言語を IR に変換します。コンパイラのバックエンドは、IR からターゲットアーキテクチャのアセンブリコードに移行します。
Conclusion
これはアセンブリの仕組みと、コンパイラがどのように高水準のプログラミング言語をアセンブリに変換するかです。次の記事では、WebAssembly がこれにどのように適合するかを見ていきます。
Lin Clark に関して
ジャスト・イン・タイム (JIT) コンパイラの短期集中コース
JavaScript はブラウザ上でどのように動作しているか
開発者として JavaScript を追加すると、ゴールと問題が出てきます。
ゴール : コンピュータへ何かを伝えたいと思います
問題 : あなたとコンピュータは別の言語を話します
あなたは人間の言語を話し、コンピュータは機械語を話します。JavaScript や他の高レベルのプログラミング言語を人間の言語として考えていなくても、実際には人間の言葉です。これらは人間の認知のために開発されたものであり、コンピュータの認知のために考えられたものではありません。
JavaScript のエンジンの仕事は人間の言葉を機械が理解できるように変換することです。
人間とエイリアンがお互いに話そうとしている映画、「メッセージ」のように考えています。
この映画では、人間とエイリアンは一語一語の翻訳をするわけではありません。2つのグループは世界に関して別の考え方を持っています。そして人間と機械に関してもそうです (次の記事でこの映画に関してはもっと説明します) 。
では翻訳はどのように行いますか?
プログラミングでは、機械語に翻訳するには一般的に 2 つの方法があります。
インタープリタでは、この翻訳は直ちに一行一行実施します。
一方コンパイラは直ちに翻訳を行いません。翻訳を作成して、それを書きとめるために、事前に動作します。
インタープリタの長所と短所
インタープリタはすぐに起動して実行できます。コードの実行を開始する前に、コンパイルの手順全体を踏む必要はありません。 最初の行を翻訳して実行するだけです。
そしてそれが様々なブラウザが最初から JavaScript インタープリタを使用していた理由です。
しかし同じコードを何度も実行するときに毎回翻訳が必要というインタープリタの問題があります。例えば、ループを使用するときがそうです。この時同じ翻訳を何度も何度も実行する必要があります。
コンパイラの長所と短所
コンパイラは対照的なトレードオフを持っています。
開始時にコンパイルを行うステップを経ないといけないため、起動に少し時間がかかります。 しかし、ループ内のコードパスはそのループを通過するたびに翻訳を繰り返す必要がないため、実行速度が速くなります。
もう一つの違いはコンパイラはコードの中身を確認し、より速く動作するように編集を行う時間があることです。これらの編集は最適化と呼ばれています。
ジャスト・イン・タイム コンパイラ : 2つの世界の最も優れたもの
別のブラウザではこの方法はやや異なっていますが、基本的なアイデアは同じです。モニター (別名プロファイラ) と呼ばれる 新しい機能を JavaScript エンジンへ追加しました。そのモニターは実行中のコードを監視し、何回動作したかとどのようなタイプで使われるのかを記録します。
まず最初にモニターはインタープリタを通してすべてを実行します。
もし同じ行を何度か通った場合、コードのその部分は “warm” と呼ばれます。もし何度も動作する場合、ここは “hot” と呼ばれます。
ベースラインコンパイラ
関数が “warm” な状態になると、JIT はそれを送信しコンパイルします。そしてコンパイル結果を保存します。
それはスピードアップするのに役立ちます。しかし、先程述べたように、コンパイラができることはもっとあります。最適化を行うための最も効率的なやり方を理解するまでには時間がかかることがあります。
ベースラインコンパイラはこれらの最適化の幾つかを行います (以下に例を示します )。長い時間作業を保留にしたくないため、これらに多くの時間をかけたくありません。
しかしコードが本当に “hot” な場合、—もしそれがいっぱい実行されていたら— このような時にさらなる最適化を実施する時間を余分にとる価値があります。
コンパイラの最適化
コードの一部がとても “hot” な時、モニターは最適化コンパイラに対してそれを送信します。これにより、保存される関数の別のより高速なものが作成されます。
コードのより高速なバージョンを作るために、最適化コンパイラはいくつかの過程を行う必要があります。
例えば、特定のコンストラクタによって作成されたすべてのオブジェクトが同じ形状を持っていると仮定することができます。つまり、それらは常に同じプロパティ名を持ち、それらのプロパティは同じ順序で追加され、それに基づいていくつかのコーナーをカットすることができます。
最適化コンパイラはモニターが監視したコードの実行状態の情報を使用します。ループが通る前のすべてのパスが真だった場合、それらは引き続き真だとみなされます。
もちろん JavaScript を使用した場合、それらの保証はありません。あなたが 99 個の同じ形状のオブジェクトを持ってしたとしても、100 個目のオブジェクトはプロパティが無いかもしれません。
従ってコンパイルされたコードは仮定が正しいかどうかを動作させる前に確認する必要があります。もし正しければコンパイルされたコードを動作させます。もし正しくなければ、JIT は誤った仮定とみなし、最適化したコードを破棄します。
たいていの最適化コンパイラはコードを速く動くようにしますが、時々期待されないパフォーマンスの問題が発生します。最適化した上で最適化解除することを続けるコードがある場合、ベースラインのコンパイル済みバージョンを実行するよりも遅くなります。
ほとんどのブラウザでは最適化/最適化解除のサイクルに問題が発生した時、終了させる制限を加えています。もし JIT が最適化を 10 回以上試行しそれを捨て続けた場合、試行をやめます。
最適化の例 : 型の特殊化
JavaScriptが利用する動的型システムでは、実行時に余分な作業が必要です。例えば、次のコードで考えてみましょう。
function arraySum(arr) { var sum = 0; for (var i = 0; i < arr.length; i++) { sum += arr; } }
arr
が 100 個の整数の配列であるとしましょう。コードが “warm up” すると、ベースラインコンパイラは関数内の各演算のスタブを作成します。そのため sum += arr
のスタブがあり、これは += 演算を整数加算として扱います。
sum
と arr
は整数であるとは限りません。 JavaScript では型が動的なので、ループの後の反復で、arr
が文字列になる可能性があります。 整数の加算と文字列の連結は非常に異なる2つの操作なので、非常に異なる機械語にコンパイルされます。JIT がこれを処理する方法は、複数のベースライン・スタブをコンパイルすることです。 コードの一部がモノモーフィックである (つまり、常に同じ型で呼び出される) 場合、それは 1 つのスタブを取得します。 それがポリモーフィックである場合 (あるコードから別のコードへと異なるタイプで呼び出されます) 、その操作を経たタイプの各組み合わせに対してスタブを取得します。
これは JIT はスタブを選ぶ前に多くの質問をする必要が有ることを意味します。
最適化コンパイラでは、関数全体が一緒にコンパイルされます。 型チェックは、ループの前に実施するように移動されます。
arr
がこれらの配列のいずれかである場合、JIT は arr
が整数かどうかを確認する必要はありません。これは、JIT がループに入る前にすべての型チェックを実行できることを意味します。結論
しかし、これらの改良によっても、JavaScript のパフォーマンスは予測できません。さらに高速化するために、JIT は実行時にいくつかのオーバーヘッドが増えています :
- 最適化と最適化解除
-
救済措置が起こったときのモニタの記録と復旧情報に使用されるメモリ
-
関数のベースラインバージョンと最適化バージョンを格納するために使用されるメモリ
次の記事では、アセンブリとコンパイラがどのように動作するかについて、さらに詳しく説明します。
Lin Clark に関して
WebAssembly の漫画での紹介
[この記事は”A cartoon into to WebAssembly“の翻訳です]
WebAssembly は高速です。あなたはおそらくこのことは聞いたことがあるでしょう。しかし何が WebAssembly を高速に動作することをできるようにしているのでしょうか?
このシリーズでは、なぜ WebAssembly が高速に動作するのかの説明を行いたいと思います。
WebAssembly とは何ですか?
WebAssembly は JavaScript 以外のプログラミング言語で書かれたコードをブラウザ上で実行する方法です。人々が WebAssembly が高速と言っているときは、JavaScript と比較しています。
しかし、これら2つを比較することは有益であり、WebAssembly が持つであろうインパクトを理解することができるでしょう。
パフォーマンスの歴史を少し
JavaScript は1995年に作成されました。しかし高速に動くように設計されてはおらず、最初の10年は速くありませんでした。
その後ブラウザはより競争的になりました。
これらの JIT の導入により、JavaScript のパフォーマンスに変革がもたらされました。 JavaScript の実行速度は 10 倍になりました。
もしかすると我々は今、WebAssembly によって新たな変曲点にいるのかもしれません。
では、WebAssembly を高速化する要素を理解するために詳細を掘り下げてみましょう。
背景:
過去の WebAssembly:
未来の WebAssembly:
Lin Clark に関して
Firefox 54 アドオン互換性情報
Mozilla では Firefox 57 へ向けたロードマップ を投稿しています。もしまだ読んでいなければ目を通してみてください。
Firefox 54 は 6 月 13 日 [日本時間同日深夜] リリース となります。Firefox 54 の変更点でアドオンの互換性に影響を及ぼす可能性のあるものを以下にまとめました。Firefox 54 for Developers により詳しい情報が載っていますので、こちらも併せてご覧ください。
一般
-moz-appearance
が削除されました。この変更はchrome://
URL で読み込まれたスタイルシートには適用されませんが、XUL や JavaScript コード内のインライン CSS には影響します。
XPCOM とモジュール
nsIFormHistory2
が削除されました。URLTYPE_AUTHORITY URLs
で空のホスト名が許容されなくなりました。これはnsIContentPolicy
を使って特定のリクエストをリダイレクトしているアドオンに影響します (そうした手法は推奨されませんが、一部のアドオンで使われていることが判明しています)。nsIX509CertDB.importPKCS12File
とexportPKCS12File
からトークン引数が削除されました。
WebExtensions
runtime.onMessageExternal
/onConnectExternal
が実装されました。バグのコメント 34 を引用すると、これによりbrowser.runtime.id
プロパティの意味が変わるため、拡張機能がそれら独自の URL 内で使われている ID とこのプロパティが同じであることを前提としていた場合には問題となるでしょう。webRequest
がホストの許可設定を確認していない問題が修正されました。従来通りwebRequest
を使い続けるには、必要な許可設定を追加する必要があります。今のところこれは Chrome API と互換性がありません。
この一覧に載っていない変更点や間違いを見つけたらコメント欄でお知らせください。もしあなたのアドオンが Firefox 54 で動かなくなった場合は、筆者の方でも調査したいと思います。
AMO に登録されているアドオンの 自動互換性テストと対応バージョンの更新 は数週間以内に行われますので、AMO に Firefox 53 対応のアドオンを登録している方は後日メールをチェックしてみてください。
Firefox 53 アドオン互換性情報
Mozilla では Firefox 57 へ向けたロードマップ を投稿しています。もしまだ読んでいなければ目を通してみてください。Firefox 53 は重要なマイルストーンで、AMO で旧式アドオンの「新規」受付が停止され、マルチプロセス Firefox が初期設定で有効化され、WebExtensions API を経由しないアドオンからのバイナリアクセスが制限されます。
Firefox 53 は 4 月 18 日 [日本時間同日深夜] リリース となります。Firefox 53 の変更点でアドオンの互換性に影響を及ぼす可能性のあるものを以下にまとめました。Firefox 53 for Developers により詳しい情報が載っていますので、こちらも併せてご覧ください。
一般
- マルチパッケージ XPI への対応が廃止されました。これは完全テーマと拡張機能をひとつのパッケージにまとめているいくつかのアドオンでまだ使われていますが、他の旧式アドオン技術とともに削除されることとなりました。
file://
URL に別のコンテンツプロセスが使われるようになりました。これにより、_openURIInNewTab
の挙動が若干変わっています。-moz-calc()
への対応が廃止されました。dom-storage2-changed
通知がプライベートウィンドウで役に立たない問題が修正されました。これにより、プライベートウィンドウ向けに別のdom-private-storage2-changed
通知が追加されました。urlbarbindings.xml
から未使用のバインディングが削除されました。<splitmenu>
と<menuitem-tooltip>
が削除されています。type
ブラウザー属性の content とcontent-primary
の区別がなくなりました。type="content-primary"
はtype="content"
とまったく同じように動作するようになり、将来的に廃止されます。MimeTypeArray
が列挙不能な名前付きプロパティとなりました。PluginArray
とPlugin
が列挙不能な独自プロパティとなりました。
パスワードマネージャー
以下 3 つの変更は関連しており、アドオンが findSlotByName("")
を呼び出してマスターパスワードが設定されているかどうかを確認できなくなったことが主な影響として挙げられます。該当するコードの変更方法は こちら の例を見てください。
nsIPK11TokenDB.findTokenByName
の引数として空のトークン名を渡すことができなくなりました。nsIPKCS11Slot.status
の不必要な使用を置き換えるためnsIPK11Token.hasPassword
が導入されました。changepassword
内のトークン選択機能が廃止されました。
XPCOM とモジュール
- PSM インターフェイスから NSS 証明書ニックネーム API を使用できなくなりました。これにより、
nsIX509Cert
内の様々なメソッドに変更が加えられています:addCert
、addCertFromBase64
、findCertByNickname
、findEmailEncryptionCert
、findEmailSigningCert
- AddonManager API がコールバックとプロミスの両方に対応しました。これにより、
getInstallForURL
のコールバックが非同期で呼び出されるようになりました。 getURIForKeyword
API が廃止されました。代わりにPlacesUtils.keywords.fetch
を使ってください。nsIStyleSheetService::PreloadSheet
にServoStyleSheets
への対応が追加されました。これにより、preloadSheet
の戻り値が変わりましたが、それをaddSheet
へ渡していない限り影響はないはずです。
WebExtensions
- レコードの削除が暗号化されるようになりました。
storage.sync
API はまだ一般には使用できませんが、一部のプレリリース版ユーザーに使われている可能性があります。この変更により古い同期済みデータが失われます。
この一覧に載っていない変更点や間違いを見つけたらコメント欄でお知らせください。もしあなたのアドオンが Firefox 53 で動かなくなった場合は、筆者の方でも調査したいと思います。
AMO に登録されているアドオンの 自動互換性テストと対応バージョンの更新 は数週間以内に行われますので、AMO に Firefox 52 対応のアドオンを登録している方は後日メールをチェックしてみてください。
JavaScript ゲームにおける操作方法
[この記事は、Control mechanisms in JavaScript games の翻訳です。]
ラップトップやデスクトップをはじめ、スマートフォンやタブレット、TV や冷蔵庫でさえも、共通してブラウザを持つようになりました。ということは、それらの端末上で HTML5 ゲームをプレイできるようになったという風にも思えますが、実際にプレイするためには、ゲーム画面を描画するのと同時に、何らかの方法でゲームを操作できなければなりません。キーボードやマウスはもちろん、タッチ操作、ゲームパッド、TV リモコン、さらにはバナナに至るまで、それぞれのプラットフォームごとに適した選択肢はたくさん存在します(MDN では、control mechanisms に関する一連の記事でそれらのインターフェースを取り上げています)。
今回は、実際のプロジェクトにおける例として、Phaser を用いて作成された Captain Rogers: Battle at Andromeda のデモを使ってぞれぞれの操作方法の実装方法について紹介します。プラットフォームの違いによってゲームの操作性がどう変わっていくかをご確認ください。また、異なるプラットフォームに対応する際に別々にビルドする必要がないということもお見せします。ウェブのマルチプラットフォームという特性によって、コーディングに多くの労力を費やすことなく複数のデバイスに対応できることがお分かりいただけると思います。
また、GitHub には、小さいですが純粋な JavaScript によるデモがオープンソースとして公開されています。操作方法がどのように実装されているかを実際に確認できるので、ご自身のゲーム開発プロジェクトでもお使いいただけます。以下に、主な項目をハイライトでご紹介しますので、このままお読みいただいても良いですし、今すぐコーディングを開始しても良いでしょう。
モバイル タッチ
HTML5 ゲームではモバイルファースト開発が人気ですので、まずはモバイル タッチへの対応から見ていきましょう。
document.addEventListener("touchstart", touchHandler); document.addEventListener("touchmove", touchHandler); function touchHandler(e) { if(e.touches) { playerX = e.touches[0].pageX - canvas.offsetLeft - playerWidth / 2; playerY = e.touches[0].pageY - canvas.offsetTop - playerHeight / 2; } }
たった数行の JavaScript コードですが、これがゲーム内でタッチ操作を実現するために必要な最低限のコードです。これだけでモバイル端末上で機能します。最初の 2 行は、タッチ操作に対するイベントリスナーの設定です。それぞれ、画面をタッチしたとき、および、指で画面をなぞったときに対応します。関数内では、タッチ操作が行われたことを確認し、プレイヤーの座標を設定しています。これにより、ゲームの Canvas 上でプレイヤーの機体が正しい位置に描画されます。
上記はベースとなるコードですので、ここからマルチタッチやジェスチャーなど様々な発展形を実装することができます(むしろ、実装した方がいいでしょう)。ゲームの種類や、何をどのように操作するかということを考慮して、実装する操作方法を決めましょう。また、移動用の矢印や発射ボタンなど、特定のアクションを起こすためのボタンを画面上に設置することもできます。詳しくは、Mobile touch controls をご覧ください。
デスクトップ マウスとキーボード
移動用の矢印について触れましたが、モバイル端末向けに画面上に矢印を表示するのはもちろん、デスクトップ向けにそれらを実装することも可能です。ゲーム内のキャラクターを動かす方法としては、カーソルキーや WASD キーがよく使われます。例えば、以下のコードではカーソルキーを使用しています。
document.addEventListener("keydown", keyDownHandler); function keyDownHandler(e) { if(e.keyCode == 39) { rightPressed = true; } else if(e.keyCode == 37) { leftPressed = true; } if(e.keyCode == 40) { downPressed = true; } else if(e.keyCode == 38) { upPressed = true; } }
押されたキーを定常的に検出しその情報を保存するということですので、これは描画ループとして以下のように処理することが可能です。
function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); if(rightPressed) { playerX += 5; } else if(leftPressed) { playerX -= 5; } if(downPressed) { playerY += 5; } else if(upPressed) { playerY -= 5; } ctx.drawImage(img, playerX, playerY); requestAnimationFrame(draw); }
プレイヤーの位置 x
および y
を表す変数が調整され、機体画像が新たな位置に表示されます。
一方、デスクトップにおけるマウスは、コーディングにおいてモバイルのタッチと非常に似ています。タッチやクリックが起こった場所に関する情報を取得し、プレイヤーの位置を更新するだけです。
document.addEventListener("mousemove", mouseMoveHandler); function mouseMoveHandler(e) { playerX = e.pageX - canvas.offsetLeft - playerWidth / 2; playerY = e.pageY - canvas.offsetTop - playerHeight / 2; }
マウスポインターの位置が変化するたびに mousemove
イベントが検出されるので、プレイヤーの位置がマウスポインターの中央の位置に来るように調整します。こうして、デスクトップのキーボードとマウスの両方で操作可能なゲームを実現することができます。さらに詳しく知りたい方は、Desktop mouse and keyboard controls をご覧ください。
ゲームパッド
個人的に大好きな操作方法です。プレゼンテーションを行う際は、HTML5 ベースの自分のスライドを Gamepad APIを使って操作しているくらいです。ゲームパッドについては、すでに数回お話したこともありますし、ゲームもいくつか実装したこともあります。詳しくは、Gamepad API Content Kit に全て記載しています。 とにかく、コンピューター上でコンソール気分を体験できることは本当に素晴らしいと思いますし、それがウェブ技術によって実現されているということも素晴らしいと思います!ゲームパッドを使用すれば、Captain Rogers のプレイ体験がさらに向上しますし、単純に良い気分になります。
Gamepad API への対応はタッチやキーボードよりもやや複雑になりますが、それでも非常に簡単です。
window.addEventListener("gamepadconnected", gamepadHandler); function gamepadHandler(e) { controller = e.gamepad; } function gamepadUpdateHandler() { buttonsPressed = []; if(controller.buttons) { for(var b=0; b<controller.buttons.length; b++) { if(controller.buttons[i].pressed) { buttonsPressed.push(b); } } } } function gamepadButtonPressedHandler(button) { var press = false; for(var i=0; i<buttonsPressed.length; i++) { if(buttonsPressed[i] == button) { press = true; } } return press; }
ゲームパッドが接続されるとイベントが発生し、ゲームパッドに関するデータの参照が変数に保存されます。以降は、その変数を使用してゲームパッドに関するデータにアクセスします。更新が起こると、押されたボタンの配列が新たに生成されるため、常に最新の状態が保持されます。また、あるボタンが押されたかどうかを判定するために配列をループ処理する関数もあります。これらはキーボードチェックと同様に、描画ループの中で使用できます。
function draw() { // ... gamepadUpdateHandler(); if(gamepadButtonPressedHandler(0)) { playerY -= 5; } else if(gamepadButtonPressedHandler(1)) { playerY += 5; } if(gamepadButtonPressedHandler(2)) { playerX -= 5; } else if(gamepadButtonPressedHandler(3)) { playerX += 5; } if(gamepadButtonPressedHandler(11)) { alert('BOOM!'); } // ... }
こうすることで、プレイヤーの機体を DPad
ボタン(十字キー)で操作したり、A
ボタンで爆弾を爆発させることができるようになります。このほか、スティック操作を検知することもできますし、独自の小さいライブラリを作ってゲームパッドからの入力を処理することもできます。詳しくは、Desktop gamepad controls をご覧ください。
特殊な操作方法
さらにもっと進んで、リビングにある大型 TV のリモコンを使ったり、ラップトップの前で手を振ったり、食材とPCを線で繋いでその食材を押したりすることで、ゲームをプレイすることも可能です。
例えば、Panasonic 製の TV リモコンはキーボードイベントを再利用しているので、対応が非常に簡単です。リモコンの方向矢印はキーボードのカーソルキーと全く同じコード(37
、38
、39
、40
)を使用しているので、すぐに対応できます。リモコン特有のボタンについては、こちらに一覧がありますので、詳細情報とともにご覧ください。
また、リモコンのボタンを押す代わりに、Leap Motion 機器の機能を使用して自分の手の位置や他のパラメーターを検知することで、何にも触れることなくプレイヤーの機体を操作することもできます。あらかじめ定義されている loop 内で、手に関する情報を取得できるので、
Leap.loop({ hand: function(hand) { horizontalDegree = Math.round(hand.roll() * toDegrees); verticalDegree = Math.round(hand.pitch() * toDegrees); grabStrength = hand.grabStrength; } });
それを使用してプレイヤーの位置を更新します。
function draw() { // ... if(horizontalDegree > degreeThreshold) { playerX -= 5; } else if(horizontalDegree < -degreeThreshold) { playerX += 5; } if(verticalDegree > degreeThreshold) { playerY += 5; } else if(verticalDegree < -degreeThreshold) { playerY -= 5; } if(grabStrength == 1) { alert('BOOM!'); } // ... }
その他にも、Unconventional controlsでは、 ドップラー効果や Proximity API、MaKey MaKeyなど興味深い操作方法の実装方法を紹介しています。
まとめ
最近は、HTML5 ゲームをプレイするのに使える機器がますます増えています。ご自身の腕時計も使えるかもしれませんし、はたまた音声認識によるウェブゲームも良いかもしれません。まさに、可能性は無限です。覚えておいて頂きたいのは、多くの操作方法に対応しているほど、ゲームにとっては良いことだということです。なぜなら、幅広い種類の機器を用いて、どんなプラットフォームでもプレイ可能になるからです。ぜひ、ブラウザがもたらす可能性を存分に活用しましょう。