GopherJSを使って、Go言語のコードをJSに変換し、Nashornを使うことで、JavaFXを動かしてみた。
同様の内容を天下一altjs武闘会で話した。
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
いい感じにでている。ちなみに、表示しているのは、GopherJS Playground
でGo Playground
的なことができる上に、吐き出すJSを見ることができる。
GopherJSとJavaFX 3D
せっかくなので、3Dのプログラムも書いてみる。今回は以下の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
クラスもある模様。
実行してみる。なお、-cp
オプションで落としてきたjarファイルにクラスパスを通している。
$ go build -o /dev/null gopher3d.go && gopherjs build gopher3d.go && jjs -fx gopher3d.js -cp jimObjModelImporterJFX.jar
いろいろ試したけど、こっち向いてくれない。
感想
誰得感があって非常に良かった。
気が乗ったら、GopherJSに手を入れたところをPR投げようと思う。
JavaFXのGo言語のバインディングを書こうと思ったけど、ジェネリクスがつらそうだなと思った。