WebAssemblyから、Goのメソッドを呼び出す (2)

https://qiita.com/neko-suki/items/7fb822b9adfa6f1c12eb の続きです。

今回は、以下の2点を扱います。


- Goで定義した構造体の定義は変更せずに、WebAssemblyからGoで定義した構造体のメソッドを呼ぶ
- 構造体のメソッドの引数に、同じ構造体の別のインスタンスを渡す

両者とももっと賢く書く方法がある気がしますが、自分で調べた範囲では見つけられませんでした。

# Goで定義した構造体の定義は変更せずに、WebAssemblyからGoで定義した構造体のメソッドを呼ぶ
特にいいやり方が思いつかなかったので、ラッパーを書きました。

## 構造体の定義

構造体の定義は元のままです。これをWebAssemblyから使えるようにします。元の構造体事態は何らかの事情で変更したくない場合を想定しています。

```golang

type Test struct {
	Num int
}

func (t *Test) Print() {
	fmt.Println(t)
}

func (t *Test) Twice() {
	t.Num *= 2
}

func (t *Test) Add(val int) {
	t.Num += val
}

func (t *Test) GetNum() int {
	return t.Num
}
```

## WebAssembly用の実装

WebAssembly側から呼び出せるようにラッパー関数をそれぞれ書きました。

```golang

package main

import (
	"syscall/js"
)

func (t *Test) PrintWrapper(this js.Value, args []js.Value) interface{} {
	t.Print()
	return nil
}

func (t *Test) TwiceWrapper(this js.Value, args []js.Value) interface{} {
	t.Twice()
	return nil
}

func (t *Test) AddWrapper(this js.Value, args []js.Value) interface{} {
	t.Add(args[0].Int())
	return nil
}

func (t *Test) GetNumWrapper(this js.Value, args []js.Value) interface{} {
	return js.ValueOf(t.GetNum())
}

func registerCallbacks() {
	var test = &Test{
		Num: 1,
	}
	js.Global().Set("test", js.ValueOf(
		map[string]interface{}{
			"print":  js.FuncOf(test.PrintWrapper),
			"twice":  js.FuncOf(test.TwiceWrapper),
			"add":    js.FuncOf(test.AddWrapper),
			"getNum": js.FuncOf(test.GetNumWrapper),
		},
	))
}

func main() {
	c := make(chan struct{}, 0)
	registerCallbacks()
	<-c
}
```

# 構造体のメソッドの引数に、同じ構造体の別のインスタンスを渡す
例えば、以下のように同じ構造体の別のインスタンスを受け取って内部で利用する場合を考えます。

```golang

func (t *Test) AddAnotherTest(t2 *Test) {
	t.Num += t2.Num
}
```

## WebAssemblyの実装
まずは、関数登録部分です。構造体自信のポインタを取得するための関数 `_ptr`を追加しています。これによって、登録した構造体のインスタンスがjavascript側から引数として渡されたときにポインタを取得できるようになります。`_ptr`に直接ポインタの値を代入しないのはjavascript側で値を改変されないようにするためです。（プライベートメンバとして登録できれば必要ないはずですが、今回はそこまで調べていないです。）

```golang
func registerCallbacks() {
	var test = &Test{
		Num: 1,
	}
	js.Global().Set("test", js.ValueOf(
		map[string]interface{}{
			"_ptr":           js.FuncOf(test.getPtr),
			"print":          js.FuncOf(test.PrintWrapper),
			"twice":          js.FuncOf(test.TwiceWrapper),
			"add":            js.FuncOf(test.AddWrapper),
			"getNum":         js.FuncOf(test.GetNumWrapper),
			"addAnotherTest": js.FuncOf(test.AddAnotherTestWrapper),
		},
	))

	var test2 = &Test{
		Num: 2,
	}
	js.Global().Set("test2", js.ValueOf(
		map[string]interface{}{
			"_ptr":           js.FuncOf(test2.getPtr),
			"print":          js.FuncOf(test2.PrintWrapper),
			"twice":          js.FuncOf(test2.TwiceWrapper),
			"add":            js.FuncOf(test2.AddWrapper),
			"getNum":         js.FuncOf(test2.GetNumWrapper),
			"addAnotherTest": js.FuncOf(test2.AddAnotherTestWrapper),
		},
	),
	)
}
```

次にポインタ取得用関数の実装です。`unsafe.Pointer`を`js.ValueOf()`で`js.Value`型に変換して、javascript側に渡します。

```golang

func (t *Test) getPtr(this js.Value, args []js.Value) interface{} {
	return js.ValueOf(unsafe.Pointer(t))
}

```

最後に、構造体を受け取って処理する部分です。javascript側から`test.AddAnotherTest(test2)`という形で呼ばれる想定です。

まず、`args[0].Get('_ptr')`で `test2._ptr`関数を呼び出しして、`test2`のポインタを取得します。ただしこの段階では`js.Value`型なので変換が必要です。`unsafe.Pointer`はfloat64で実装されているようなので、`.Float()`を呼び出します。その結果を`unsafe.Pointer`型に変換して、最後に`Test`のポインタに変換します。

```golang

func (t *Test) AddAnotherTestWrapper(this js.Value, args []js.Value) interface{} {
	ptrJSValue := args[0].Call("_ptr")
	t2 := (*Test)(unsafe.Pointer(ptrJSValue.Float()))
	t.AddAnotherTest(t2)
	return nil
}

```

# テスト用のhtml
テスト用に使うhtmlです。`addAnotherTest()` というのが追加されています。例えば、`twice`, `addAnotherTest`, `print`という順番でボタンを押すと、consoleに`&{4}`が表示されます。

```html
<!doctype html>
<html>
<head>
	<meta charset="utf-8">
	<title>Go wasm</title>
</head>

<body>
	<script src="wasm_exec.js"></script>
	<script>
		const go = new Go();
		WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
			go.run(result.instance);
		});
	</script>
	<script>
		function getNum(){
			console.log(test.getNum())
		}
		function addAnotherTest(){
			console.log(test2)
			test.AddAnotherTest(test2)
		}
	</script>
	<button onClick="test.print();" id="runButton" >print</button><br>
	<button onClick="test.twice()" id="runButton">twice</button><br>
	<button onClick="test.add(1)" id="runButton">add</button><br>
	<button onClick="getNum()" id="runButton">get test.Num</button><br>
	<button onClick="addAnotherTest()" id="runButton"> addAnotherTest</button><br>

</body>
</html>
```


# ソース
下に置いてあります。
https://github.com/neko-suki/wasm_struct