WebAssemblyをNode-REDで使う (前編)
はじめに
Node-REDの独自ノードを作るには、一般にはJavaScriptとHTMLを使います。高速化のためにNode.jsのnative addonを使ってランタイム側にC++などで書いたコードをつかうこともできますが、環境に応じて再コンパイルする必要がでてきます。
そこで、WebAssemblyを使ってみることを考えてみます。WebAssemblyは
- JavaScriptより高速な処理が期待できる
- Node.js(8以上)が動けばどこでも動く
- C,C++,Rustなどの言語のコンパイル対象になっている
という特長があります。
ここでは、Rustで書いたコードからWebAssemblyのバイナリにコンパイルし、それをNode-REDから活用する方法を説明します。
環境整備
ここでは下記の環境を使いました。
- macOS Catalina version 10.15.7
- Node-RED v1.2.6
- Node.js v14.15.1
- Rustc 1.48.0 (target=wasm32-unknown-unknown)
- wasm-bindgen 0.2.69
各ツールのインストール方法はそれぞれのツールのWebページの説明をご覧ください。
第一段階: Node-REDから呼び出せることを確認する
最初から独自ノードを作っていくのも大変なので、まずは動くことを確認するためにfunctionノードをつかって無理やり使ってみましょう。
Rustのライブラリを作る
Cargoコマンドで、プロジェクトの雛形を作りましょう。この時点ではとくにWebAssemblyを意識することはありません。
% cargo new --lib hellowasm
Created library `hellowasm` package
%
C形式の動的ライブラリを生成してWebAssemblyにコンパイルできるようにするためにCargo.toml
を編集します。最後の2行は、最適化のための設定です。
[package]
name = "hellowasm"
version = "0.1.0"
edition = "2018"
[lib]
crate-type = ["cdylib"]
[profile.release]
lto = true
プログラム本体は、src/lib.rs
に書きます。ここでは単純な浮動小数点の掛け算をします。なお、WebAssembly自体には32,64bitの整数、浮動小数点しか型がありません。
#[no_mangle]
pub extern "C" fn mul_f64_f64(x: f64, y: f64) -> f64 {
x * y
}
WebAssemblyにコンパイルする
それでは、これをWebAssemblyにコンパイルしましょう。
% cargo build --release --target wasm32-unknown-unknown
Compiling hellowasm v0.1.0 (.../hellowasm)
Finished release [optimized] target(s) in 0.70s
WebAssemblyのバイナリがtarget/wasm32-unknown-unknown/release/hellowasm.wasm
に生成されています。
% ls -l target/wasm32-unknown-unknown/release/hellowasm.wasm
-rwxr-xr-x 2 ktoumura staff 243 Dec 3 16:03 target/wasm32-unknown-unknown/release/hellowasm.wasm
243byteと小さいですね。なお、最適化の設定(link time optimization)を行わないと1513648byteのファイルになります。
functionノードでwasmをロードする
今回は小さいファイルですので、Base64エンコードをして直接functionノードから読み込ませてしまいましょう。
% base64 target/wasm32-unknown-unknown/release/hellowasm.wasm
AGFzbQEAAAABBwFgAnx8AXwDAgEABAUBcAEBAQUDAQAQBhkDfwFBgIDAAAt/AEGAgMAAC38AQYCAwAALBzMEBm1lbW9yeQIAC211bF9mNjRfZjY0AAAKX19kYXRhX2VuZAMBC19faGVhcF9iYXNlAwIKCQEHACAAIAGiCwAPDi5kZWJ1Z19hcmFuZ2VzABUEbmFtZQEOAQALbXVsX2Y2NF9mNjQATQlwcm9kdWNlcnMCCGxhbmd1YWdlAQRSdXN0AAxwcm9jZXNzZWQtYnkBBXJ1c3RjHTEuNDguMCAoN2VhYzg4YWJiIDIwMjAtMTEtMTYp
%
これを使って、functionノードのsetupタブでWebAssemblyのロードを行います。Setup時にロードすることで、メッセージを受けるたびにWebAssemblyのロードが行われるコストを削減します。
const wasmcode = "AGFzbQ...(略)...";
WebAssembly.instantiate(Buffer.from(wasmcode, 'base64'),{})
.then(result => {
if (context.get("mul_f64_f64") === undefined) {
context.set("mul_f64_f64", result.instance.exports.mul_f64_f64)
}
});
このような形で関数定義を取り込み、ノードのコンテキストにセットしておきます。本体のコードはFunctionタブにセットしましょう。
const mul_f64_f64 = context.get("mul_f64_f64");
msg.payload = mul_f64_f64(msg.payload, 2.5);
return msg;
それではデプロイして実行してみましょう。
$12345.6 \times 2.5=30864$が計算できてますね。
第二段階: PNG画像のトリミングをする独自ノードの作成
さて、もう一歩実用的な使い方に進みましょう。次は、独自ノードの中でWebAssemblyを使うことにします。ここでは「PNG画像をうけとって、それをトリミングした画像を出力する」ノードを作ります。
前節の説明ではRust-WebAssembly-JavaScriptの間のつなぎを全て手作業で実施しました。このままですと文字列を渡すのも一苦労です。このような作業を補助してくれるツールとしてwasm-bindgenがあります。
RustでPNGのトリミングを記述
前節と同様に、Rustのプロジェクトを作りましょう。
% cargo new --lib pngcrop
Created library `pngcrop` package
%
今回はwasm-bindgen
を使うためにCargo.tomlを下記のように変更します。
[package]
name = "pngcrop"
version = "0.1.0"
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
image = "0.23.12"
wasm-bindgen = "0.2"
[profile.release]
lto = true
dependencies
に画像を扱うためのimage
クレートと、先ほど述べたwasm-bindgen
を加えています。
RustでのPNG画像のトリミングのコード(src/lib.rs
)は下記になります。まだRustに慣れていないので不自然なコードになっているかもしれません...
use image::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn croppng(buf: &[u8], x: u32, y: u32, width: u32, height: u32) -> Vec<u8> {
let mut img = load_from_memory(buf).unwrap().to_rgb8();
let cropped = imageops::crop(&mut img, x, y, width, height).to_image().into_raw();
let mut data = Vec::new();
let encoder = codecs::png::PngEncoder::new(&mut data);
encoder.encode(&cropped, width, height, ColorType::Rgb8).unwrap();
data
}
croppng()関数は、PNG形式のバイナリのスライスとトリミングのパラメータを受け取って、トリンミング済みのPNG形式のバイナリを返します。#[wasm_bindgen]
というアトリビュートをつけることで、後に置かれた関数がバインディング生成の対象であることを示します。
このライブラリをビルドし、wasm-bindgen
コマンドでNode.js用のバインディングを生成します。
% cargo build --release --target wasm32-unknown-unknown
Compiling autocfg v1.0.1
...
Compiling wasm-bindgen-macro v0.2.69
Compiling image v0.23.12
Compiling pngcrop v0.1.0 (.../pngcrop)
Finished release [optimized] target(s) in 44.06s
% wasm-bindgen target/wasm32-unknown-unknown/release/pngcrop.wasm --target nodejs --out-dir . --no-typescript
% ls -l
total 1640
-rw-r--r-- 1 ktoumura staff 11555 Dec 3 17:57 Cargo.lock
-rw-r--r-- 1 ktoumura staff 178 Dec 3 18:10 Cargo.toml
-rw-r--r-- 1 ktoumura staff 1945 Dec 3 19:07 pngcrop.js
-rw-r--r-- 1 ktoumura staff 815413 Dec 3 19:07 pngcrop_bg.wasm
drwxr-xr-x 3 ktoumura staff 96 Dec 3 16:52 src
drwxr-xr-x 6 ktoumura staff 192 Dec 3 18:55 target
%
pngcrop_bg.wasm
がコンパイルされたWebAssemblyバイナリ、pngcrop.js
がWebAssemblyへのインタフェース用のJavascriptプログラムになります。Javascriptのプログラム側からは、pngcrop.js
をrequire()
して使います。
後編では、この2つのファイルを使って、Node-REDの独自ノードに仕立てていきます。
Author And Source
この問題について(WebAssemblyをNode-REDで使う (前編)), 我々は、より多くの情報をここで見つけました https://qiita.com/k-toumura/items/5c8c2dfb5fd87796ef6d著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .