Rustというプログラム言語が面白そうなので使い始めてみました。Rustから直接WebAssemblyコードを出力できるので、実行環境はWebブラウザに、グラフィック処理はWebGLを使用します。Webブラウザ上で3Dゲームが作成できるようになることが目標です。それとWebプログラム初心者なので、npm、wasm-bindgenなどのツールやバインダーは使用せず、基本的に手作業で作成していきます。というか使い方がよくわかってないだけですが……
今回行うこと Rustの環境作成&ビルド、ブラウザ上で実行、Rust⇔JavaScript間の関数呼び出し、WebGL導入まで
インストール 環境構築
Windows10でのインストール手順になります。
rustup-init.exe
Windows用のインストーラーです。
https://rustup.rs/ ここからrustup-init.exeをダウンロード
実行して表示される内容(英語)に従ってインストールを行います。そのまま 1) Proceed with installation (default) を選択すると%USERPROFILE%(C:\Users\ユーザー名)にインストールされます。詳しいことは、https://github.com/rust-lang/rustup.rsに書かれています。Windows10の場合 default host triple: x86_64-pc-windows-msvc になっていると思うので、VS2013以降または、Visual C++ Build Tools 2019のインストールが必要になります。※wasmのコンパイルのみであればインストール不要かもしれない
指定した場所にインストールしたい場合は、インストール前に環境変数(RUSTUP_HOMEとCARGO_HOME)を登録することで可能になります。環境変数がない場合 C:\Users\ユーザー名 に.cargoと.rustupというフォルダが作成されそこにインストールされます。
CARGO_HOME | 実行ファイルのインストール場所 |
RUSTUP_HOME | rustup関連のファイル |
WebAssemblyターゲットの追加
コマンドプロンプトまたはPowerShellで
> rustup target add wasm32-unknown-unknown
を実行するとRustからWebAssemblyのバイナリーコードに変換できるようになります。
インストールされる主なツール
Rust関連ツールの利用はPowerShell、またはコマンドプロンプトから行います。
rustup
新しいツールのインストールや既存ツールのアップデート、アンインストールを行うツールです。
> rustup --version バージョン確認
> rustup update アップデート
> rustup self uninstall アンインストール
rustc
Rustコンパイラですが直接使用することはあまりないと思います。
cargo
Rustのビルドシステム兼パッケージマネージャです。これでプロジェクトの作成、管理、ビルドを行います。crates.ioで公開されているライブラリ(Rustではクレート) は、設定ファイル(Cargo.toml)に記入すると、自動的にダウンロード&ビルドしてくれます。そのほかにもローカルファイルのパス指定、gitリポジトリ指定もできます。
使用例> cargo --version
> cargo update
> cargo new project_name
> cargo build --target ターゲット名 --release
Visual Stusio Codeのインストール
※必須ではありません。ソースコードエディタとWebサーバーの一例です。
詳しい説明は不要だと思います。ソースコードの編集、PowerShellターミナルでビルドの実行や作業の自動化などができます。
Rust(rls)とLive Server
Rustソースコードの色付け、補間、事前のエラー表示をしてくれる追加機能が便利です。Extention:Marketplaceでrustを検索すると色々出てきます。その中のRust(rls)を使用しています。
それと簡易ローカルサーバーを起動できるLive Serverが便利です。ボタン1つでWebサーバーが立ち上がり、ファイル更新を監視してブラウザを自動的に更新してくれます。
Hello World
準備が整ったので、実際にコーディングしていきます。
プロジェクト作成
プロジェクトを作成したい場所でcargoの新規作成コマンドを実行します。実行ファイルを作成するプロジェクトではないので–libを指定。
> cargo new hello_world --lib
hello_worldフォルダが作成され、いくつかの初期ファイルが生成されます。それとGitリポジトリも作成されます。
新規作成時のファイル構成hello_world
| .gitignore
| Cargo.toml
|
+---.git
| | Gitリポジトリ 中身は省略
|
\---src
lib.rs
build.cmd、index.htmlとindex.jsの追加
cargoにはビルド後にファイルコピーなどの処理ができない様なのでバッチコマンドでcargo実行とファイルコピーを行います。それとwwwフォルダにブラウザに表示させるためのhtmlとJavaScriptを追加します。これであとはコードを書いてビルドすれば完了です。
最終的なファイル構成 Git関連は省略hello_world
| Cargo.toml
| build.cmd
|
+---src
| lib.rs
|
\---www
index.html
index.js
コーディング
用意したファイルに書き込んでいきます。
cargo設定ファイル
wasm実行バイナリーを出力するために[lib]の項目を追加しています。その他はプロジェクト作成時に生成されたものです。
Cargo.toml[package] name = "hello_world" version = "0.1.0" authors = ["namae"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] # *.rlib ではなく *.wasmを出力する crate-type = ["cdylib"] [dependencies] # メモ # ローカルファイルのクレート指定 # mycreate = { path = "../lib/mylib" } # gitリポジトリ (たぶん、まだ試してない) # mycreate = { git = 'リポジトリの場所' }
ビルド用バッチファイル
少し調べたのですが、cargoだけではビルド後出力された実行ファイルを別の場所にコピーできない様なので、バッチコマンドで対応します。hello_world.wasmがビルドによって出力されるwasmバイナリーです。copyコマンドでWebサーバーのルートとなるwwwフォルダへコピーします。–releaseを消すとdebugビルド。
build.cmdcargo build --target=wasm32-unknown-unknown --release
copy target\wasm32-unknown-unknown\release\hello_world.wasm www\
html
ブラウザに表示させるためのindex.htmlです。JavaScriptの指定とWebGL用のキャンバスを定義します。
www/index.html<!DOCTYPE html>
<html>
<head>
<title>Hello Rust + wasm + WebGL!</title>
</head>
<body>
<canvas id="canvas" width="320" height="240"></canvas>
<script src="./index.js" type="module"></script>
</body>
</html>
Rust
JavaScriptから呼ばれる関数を定義します。呼ばれた関数はJavaScript側の関数を呼び出して終了です。
src/lib.rs// JavaScript側の関数
extern "C" {
fn hello_js();
}
//JavaScriptから呼ばれる関数
#[no_mangle]
fn hello_rust() {
//JavaScriptの関数呼び出しは危険を伴うのでunsafe
unsafe {
hello_js();
}
}
JavaScript
最後にJavaScriptです。fetch命令を使ってwasmバイナリーの読み込んでいます。wasmの準備が終了したらrust側のhello_rust()を呼び出し、rust側ではhello_js()を呼び出しているので、WebGLで画面のクリアとアラート表示が実行されます。www/index.js// rust(wasm)から呼ばれる関数
// WebAssembly.instantiate()へ渡す
const imports = {
env: {
hello_js : hello_js, //rust側の関数名 : jsの関数
},
}
// wasm読み込み
fetch('./hello_world.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports))
.then(main);
function main(results)
{
var wasm = results.instance;
// Rust(wasm)の関数呼び出し
wasm.exports.hello_rust();
}
function hello_js()
{
var canvas = document.getElementById('canvas');
var gl = canvas.getContext('webgl');
gl.clearColor(0.6, 0.8, 0.9, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.finish();
alert("Hello World!");
// 終了処理 16回リロードで発生するwarning対策 for Firefox
// リロードしたときにWebGLContextが破棄されない?
window.addEventListener('beforeunload', function(e){
gl.getExtension('WEBGL_lose_context').loseContext();
});
}
ビルド&実行
準備が整ったのでビルドを行います。build.cmdを実行してビルドが成功すると以下のような文字が出力されます。
// hello_world(build.cmdとCargo.tomlがある)フォルダで
> ./build
hello_world>cargo build --target=wasm32-unknown-unknown --release
Compiling hello_world v0.1.0 (hello_world)
Finished release [optimized] target(s) in 7.04s
hello_world>copy target\wasm32-unknown-unknown\release\hello_world.wasm www\
1 個のファイルをコピーしました。
>
Webサーバー起動(Visual Studio Code)
hello_world/wwwフォルダをVSCodeで開き、Live Serverがインストールされていれば右下に◎GoLiveが表示され、それをクリックするとWebサーバーが立ち上がります。ブラウザも同時に起動します。デフォルトのアドレスはhttp://127.0.0.1:5500/、ポート番号はGo Liveがあったところに表示されています。
ブラウザでキャンバスの部分の色が変わり、Hello Worldのアラートが表示されれば成功です。ブラウザやサーバーによってアラート表示の後にクリアされます。
ここで実行結果を確認できます。(hello_world/www/)
□hello-rust-wasm-webgl
動作確認
Firefox 68.02(64bit)
Google Chrome 76.0.3809.132(Official Build) (64 ビット)
Microsoft Edge 44.18362.267.0 Microsoft EdgeHTML 18.18362
wasmコード削減
環境によって変わるかもしれませんが、出力されたwasmファイルは、デバッグ情報などが含まれているため簡単なコードにかかわらず1.4MBほどになります。大きいです。いくつかコード削減ツールがありますが、その中のwasm-gcを紹介します。
ツールのインストール
cargoのinstallコマンドを使います。
> cargo install --git 'https://github.com/alexcrichton/wasm-gc'
使い方
> wasm-gc wasmファイル -o 出力ファイル
build.cmdのcopyコマンドの代わりにwasm-gcを使用。1.4MBから242byteに削減されました。
build.cmd> cargo build --target=wasm32-unknown-unknown --release
> wasm-gc target\wasm32-unknown-unknown\release\hello_world.wasm -o www\hello_world.wasm
まとめ
必要最低限のツールでのRust、Wasm、WebGLプログラムの開発環境を整えてみました。必須なのはRustツールチェインのみです。ここから必要に応じてパッケージングツールやWebAPIのバインダーの利用を考えます。しばらくは勉強のために、できるだけ手作業で進めていきます。
今回作成したWebページ(hello_world/www/フォルダ)
□hello-rust-wasm-webgl
>> Rust(wasm)とJavaScript(WebGL)のデータ受け渡し Rust+WebGLでポリゴン描画 (1/2)