はじめに
Electron触ってみようの会でやってみたことのまとめです。
当日は全然時間が足りなかったので持ち帰ってから完成させました。
とりあえずElectronは初めてでしたが、せっかくなので普通のWebアプリではできないNative Addonを自分で作って使ってみようと思いました。
Native AddonとはC++などのネイティブ言語で実装されたNode.jsモジュールです。
Electronのバージョンは0.32です。
今回作成したソースコードは https://github.com/likr/20150915electron に置いています。
Native Addonを作る
何か重めの処理をさせようということで、雑にマンデルブロ集合を計算してみます。
Canvasと連携することを想定して、JavaScript側からビットマップを表すUint8ClampedArray
を受け取り、ピクセルごとに色を決める計算を行うようにします。
C++のソースコード全体は以下の通りです。
マンデルブロ集合の計算を行う部分と、JavaScriptのオブジェクトからポインタの取り出しを行っている部分、モジュールの登録を行う部分で成り立っています。
パラメータは決め打ちで、エラー処理などもすっ飛ばしています。
#include <node.h>
using namespace v8;
void runMandelbrot(unsigned char* array, int width, int height, double pixel_size, double x0, double y0) {
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
double cr = x0 + pixel_size * i;
double ci = y0 + pixel_size * j;
double zr = 0.0f;
double zi = 0.0f;
double zrzi, zr2, zi2;
int k;
for(k = 0; k < 256; k++) {
zrzi = zr * zi;
zr2 = zr * zr;
zi2 = zi * zi;
zr = zr2 - zi2 + cr;
zi = zrzi + zrzi + ci;
if(zi2 + zr2 >= 2.0) {
break;
}
}
if(k > 255){
k = 255;
}
int index = j * width + i;
array[4 * index] = array[4 * index + 1] = array[4 * index + 2] = 255 - k;
array[4 * index + 3] = 255;
}
}
}
void Method(const FunctionCallbackInfo<Value>& args) {
Local<Uint8ClampedArray> array = args[0].As<Uint8ClampedArray>();
unsigned char* ptr = (unsigned char*)array->Buffer()->GetContents().Data();
runMandelbrot(ptr, 300, 200, 0.01, -2.0, -1.0);
}
void init(Local<Object> exports) {
NODE_SET_METHOD(exports, "mandelbrot", Method);
}
NODE_MODULE(addon, init)
Native AddonのAPIは何度か大幅な変更が入っていて、Electronで使用されているnode.jsのバージョンと利用するAPIを合わせる必要があります。
Electron 0.32ではio.js 3.3.0が使われているので、C++の実装もそれに合わせて行っています。
C++のコードはio.js DocumentationとV8 API Reference Guideを参考にしながら書きました。
Native Addonはnode-gyp
を使ってビルドすることができます。
今回は、以下のような設定ファイルになります。
{
"targets": [
{
"target_name": "mandelbrot",
"sources": ["mandelbrot.cc"]
}
]
}
node-gyp
コマンドを実行するだけでビルドができます。
$ node-gyp
ビルドに成功すると./build/Release/mandelbrot
にモジュールが配置され、以下のように通常のモジュールと同じように使うことができます。。
var mandelbrot = require('./build/Release/mandelbrot');
var b = new Uint8Array(300 * 200 * 4);
mandelbrot.mandelbrot(b);
console.log(b);
しかし、残念ながらこのままではElectronから読み込むことができないので、以下のようにElectron向けにビルドをします。
他にもelectron-rebuildを使う方法などがあります。(Using Native Node Modules)
$ HOME=~/.electron-gyp node-gyp rebuild --target=0.32.0 --arch=x64 --dist-url=https://atom.io/download/atom-shell
Electronから呼び出す
Electronアプリを作っていきます。
package.json
を作成し、Quick Startを参考にし、以下のようなmain.js
を作りました。
var app = require('app');
var BrowserWindow = require('browser-window');
require('crash-reporter').start();
var mainWindow = null;
app.on('window-all-closed', function() {
if (process.platform != 'darwin') {
app.quit();
}
});
app.on('ready', function() {
mainWindow = new BrowserWindow({width: 800, height: 600});
mainWindow.loadUrl('file://' + __dirname + '/index.html');
mainWindow.on('closed', function() {
mainWindow = null;
});
});
以下が画面側のコードです。
ボタンを押すとマンデルブロ集合を計算した結果をCanvasに描画します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Native Mandelbrot</title>
</head>
<body>
<div>
<button onclick="run()">Run</button>
</div>
<div>
<canvas id="screen" width="300" height="200"></canvas>
</div>
<script>
var remote = require('remote');
var mandelbrot = require('./build/Release/mandelbrot');
function run() {
var canvas = document.getElementById('screen');
var context = canvas.getContext('2d');
var imageData = context.createImageData(300, 200);
mandelbrot.mandelbrot(imageData.data);
context.putImageData(imageData, 0, 0);
}
</script>
</body>
</html>
Native Addonの使い方はtest-mandelbrot.js
と特に変わっていません。
imageData.data
がピクセルを表すUint8ClampedArray
です。
Native Addon内の処理が終わったらcontext.putImageData
でCanvasに描画します。
Native AddonはMain Processだけでしか使えないのかと思いきやRender Processでもrequire
してしまえば普通に使えるんですね。
実行してボタンを押すとこんな感じになります。
まとめ
ElectronもNative Addonもどちらも意外と簡単にできました。
Native Addonで科学技術計算などのJavaScriptでは追いつかないような重い処理をさせながら、UIをWebベースで作るといったことも実現できそうでした。