Go言語でJavaFXを動かしてみた


GopherJSを使って、Go言語のコードをJSに変換し、Nashornを使うことで、JavaFXを動かしてみた。
同様の内容を天下一altjs武闘会で話した。

* http://connpass.com/event/6402/

## GopherJS
GopherJSは、Go言語のコードからJSのコードを生成するツールである。
`go/ast`パッケージが使われている。
go get でインストールする。

```
$ go get github.com/gopherjs/gopherjs
```

GopherJSは、Node.jsかブラウザで動くJSを履く前提で作られている。
Hello, worldを作ってみる。

```
package main

func main() {
    println("hello, GopherJS")
}
```

ビルドしてみる。

```
$ go build -o /dev/null && gopherjs build hello.go
```

`go build`してるのは、GopherJSのビルドのエラーログが分かりにくいため、とりあえずGo言語としてコンパイルして、その段階でコンパイルエラーになるものを発見しやすくするためである。

ビルドすると`hello.js`と`hello.js.map`が生成される。`hello.js`には自分で書いたコードと、ランタイムが付加される。上記のコードで、48KBほどである。`hello.js.map`はsource mapファイルで、これを使えばデバッグがしやすくなる。

Node.jsで実行すると、`hello, GopherJS`と出力される。

```
$ node hello.js
hello, GopherJS
```

## Nashorn

JavaのJavaScriptエンジンである。私はあまり詳しくないが、JavaからJSのオブジェクトを使えたり、JSからJavaのオブジェクトを使うことができる。`jjs`コマンドを使うと、JSファイルをNashornで実行できる。
なお、デフォルトでは`jjs`にはパスが通ってないため、パスを通しておく必要がある。
また、`-fx`オプションをつければ、JavaFXも使うことができる。

Nashornで、GopherJSで生成したJSファイルを実行しようとすると、グローバルオブジェクトが設定されずに落ちる。そのため、GopherJSに手をいれて、トップレベルの`this`をグローバルオブジェクトが入る変数`go$global`に入るようにしてやる必要がある。

また、`console`もないので、`console.log`とかは`print`とかに置き換える必要があるが、今回は面倒なのでやってない。そのため、上記のコードは今回は動かせない。

## GopherJSでJSのオブジェクトを使う
`js.Object`インタフェースがJSのオブジェクトに対応している。
`Get`や`Set`、`Call`、`Index`、`Length`などを使って、プロパティに値を設定したり、メソッドを呼んだりする。使い方は、`reflect`パッケージの`Value`型によく似ている。

例えば、以下のように使うことができる。

```
package main

import (
    "github.com/gopherjs/gopherjs/js"
)

func main() {
    Object := js.Global.Get("Object")
    obj := Object.New()
    obj.Set("name", "hoge")
    println(obj.Get("name"))

    JSON := js.Global.Get("JSON")
    println(JSON.Call("stringify", obj))
}
```

```
go build -o /dev/null sample1.go && gopherjs build sample1.go && node sample1.js
hoge
{"name":"hoge"}
```

## GopherJSとJavaFX
それでは、JavaFXのWebViewを表示するサンプルを作ってみよう。

```
package main

import (
    "github.com/gopherjs/gopherjs/js"
)

var java = js.Global.Get("Java")

func main() {
    js.Global.Set("start", start)
}

func start(stage js.Object) {
    stage.Set("title", "Hello, GopherJS")
    WebView := java.Call("type", "javafx.scene.web.WebView")
    webview := WebView.New()
    webview.Call("getEngine").Call("load", "http://gopherjs.github.io/playground")
    Scene := java.Call("type", "javafx.scene.Scene")
    scene := Scene.New(webview, 600, 600)
    stage.Call("setScene", scene)
    stage.Call("show")
}
```

`java`という変数は、Javaのクラスを使うためのオブジェクトである。グローバル変数なので、`js.Global`のプロパティから取得している。
JavaFXのプログラムは`main`関数ではなく、`start`関数から始まる。そのため、`start`関数をトップレベルに作る必要があるため、`js.Global`のプロパティとして、`start`関数を設定している。GopherJSから吐出されるJSは必ず`main`パッケージの`main`関数が実行されるようなコードになっている。

`start`関数の中は、`WebView`クラスのインスタンスを作って、`Scene`に貼り付ける簡単な実装が書かれている。

実行してみる。

```
$ go build -o /dev/null sample2.go && gopherjs build sample2.go && jjs -fx sample2.js
```

![sample2.png](https://qiita-image-store.s3.amazonaws.com/0/14952/63477c91-3e3c-f9cf-fe54-e287971319df.png "sample2.png")

いい感じにでている。ちなみに、表示しているのは、`GopherJS Playground`で`Go Playground`的なことができる上に、吐き出すJSを見ることができる。

## GopherJSとJavaFX 3D

せっかくなので、3Dのプログラムも書いてみる。今回は以下のGopherの3Dオブジェクトを表示させていみる。

* https://github.com/golang-samples/gopher-3d

```
package main

import (
    "github.com/gopherjs/gopherjs/js"
)

var java = js.Global.Get("Java")

var (
    Group             = java.Call("type", "javafx.scene.Group")
    Scene             = java.Call("type", "javafx.scene.Scene")
    ObjModelImporter  = java.Call("type", "com.interactivemesh.jfx.importer.obj.ObjModelImporter")
    DrawMode          = java.Call("type", "javafx.scene.shape.DrawMode")
    PerspectiveCamera = java.Call("type", "javafx.scene.PerspectiveCamera")
    Point3D           = java.Call("type", "javafx.geometry.Point3D")
)

func start(stage js.Object) {
    root := Group.New()

    objImporter := ObjModelImporter.New()
    objImporter.Call("read", "gopher.obj")
    objMesh := objImporter.Call("getImport")
    objImporter.Call("close")

    for i := 0; i < objMesh.Length(); i++ {
        root.Call("getChildren").Call("addAll", objMesh.Index(i))
        objMesh.Index(i).Call("drawModeProperty").Call("set", DrawMode.Get("FILL"))
    }
    root.Call("setRotationAxis", Point3D.New(0.0, 1.0, 0.0))
    root.Call("setRotate", 210.0)

    scene := Scene.New(root, 600, 600)

    camera := PerspectiveCamera.New(true)
    scene.Call("setCamera", camera)
    camera.Call("setTranslateZ", -10.0)

    stage.Call("setScene", scene)
    stage.Set("title", "Hello, Gopher3D")
    stage.Call("show")
}

func main() {
    js.Global.Set("start", start)
}
```

`ObjModelImporter`というクラスを使うには、以下のサイトからjarファイルを落としてくる必要がある。objファイル以外の`Importer`クラスもある模様。

* http://www.interactivemesh.org/models/jfx3dimporter.html

実行してみる。なお、`-cp`オプションで落としてきたjarファイルにクラスパスを通している。

```
$ go build -o /dev/null gopher3d.go && gopherjs build gopher3d.go && jjs -fx gopher3d.js -cp jimObjModelImporterJFX.jar
```

![gopher3d.png](https://qiita-image-store.s3.amazonaws.com/0/14952/ac59c905-2b12-7087-26bd-90476283e9bd.png "gopher3d.png")

いろいろ試したけど、こっち向いてくれない。

## 感想
誰得感があって非常に良かった。
気が乗ったら、GopherJSに手を入れたところをPR投げようと思う。
JavaFXのGo言語のバインディングを書こうと思ったけど、ジェネリクスがつらそうだなと思った。