Screeps:ArenaをTinyGoのWebAssemblyで書こうとして断念した話

# あらすじ

- Screeps:Arena チュートリアルやったけどおもろそうやん
- CtFやろうと思ったけどクラスないとしんどいなぁ
- WebAssemblyでほかの言語も行けるらしいし勉強がてらGoでやるか
- 環境整えるのめんどいからDockerで作るか
- ビルドできたけどwasm_exec.js動かんやんけ！
- 断念

# 目次
- [Dockerの準備](#Dockerの準備)
- [ビルド](#ビルド)
- [他に必要なコードの取得](#他に必要なコードの取得)
- [結果](#結果)
- [展望](#展望)

# 環境
- Windows10 Home
- TinyGo 0.22.0
- Docker Desktop 4.7.0

# Dockerの準備
- https://docs.docker.jp/docker-for-windows/install.html からダウンロード＆インストール
- https://tinygo.org/getting-started/install/using-docker/ を参考にTinyGo環境のDockerイメージをPull

# ビルド
適当なサンプルコードを用意。
`//export [関数名]` を書くと関数として使用できるようになるらしい
```golang
package main

import "fmt"

func main() {
}

// export test
func test() {
  fmt.Printf("Hello World\n")
}
```

Dockerでビルド
```shell
docker run --rm -v "$(pwd):/src" tinygo/tinygo:0.22.0 tinygo build -o wasm.wasm -target=wasm examples/wasm/export
```
※引っ掛かりポイント
- Powershell の pwd は `"($pwd)"` とダブルクォーテーションで囲わないと文字列で返してくれない
- `examples/wasm/export` はそのままでないとダメだった
    - `go: go.mod file not found in current directory or any parent directory; see 'go help modules'` って言われる
- コマンド実行時のカレントディレクトリは Screeps:Areana で main.mjs が作られるディレクトリ

うまくいくとカレントディレクトリに wasm.wasm ファイルが作られる

# 他に必要なコードの取得

## wasmファイルインポートするためのコード
- https://text.baldanders.info/golang/webassembly-with-tinygo/ の方の initWASM() を~~丸パクリ~~ 参考にさせていただきましたorz
- 一部変更していますが動作確認できていません。
```javascript
export function initWASM(url) {
    let inst;
    const go = new Go();

    if ('instantiateStreaming' in WebAssembly) {
        WebAssembly.instantiateStreaming(fetch(url), go.importObject).then(function (obj) {
            inst = obj.instance; // 追記
            go.run(obj.instance);
        })
    } else {
        fetch(WASM_URL).then(resp =>
            resp.arrayBuffer()
        ).then(bytes =>
            WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
                inst = obj.instance; // 追記
                go.run(inst);
            })
        )
    }

    return inst; // 追記
}
```
## TinyGoのWebAssembly用コード
- [wasmファイルインポートするためのコード](#wasmファイルインポートするためのコード) の `new Go()` の定義
- Dockerコンテナからコピーしてくる

コンテナを起動し続けるように実行する
```shell
docker run -itd -v "$(pwd):/src" tinygo/tinygo:0.22.0 /bin/bash
```
起動したコンテナ名を確認
※CONTAINER 列の値がコンテナ名
```shell
docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
```
起動したコンテナからファイルをコピーしてくる
```shell
docker cp [コンテナ名]:/usr/local/tinygo/targets/wasm_exec.js .
```

## Screepsのメインのコード
```javascript
import { } from '/game/utils';
import { } from '/game/prototypes';
import { } from '/game/constants';
import { } from '/arena';
import "./wasm_exec.js";
import { initWASM } from './wasm.js';

const wasm = initWASM('wasm.wasm');

export function loop() {
    console.log(wasm.exports.test());
}
```

# 結果
## 直接的な原因
Screeps:Arena で実行したところ下記のエラー
```
ReferenceError: require is not defined
    at file:///user/wasm_exec.js:104:22
    at file:///user/wasm_exec.js:535:3
    at (<isolated-vm boundary>)
    at init (file:///opt/screeps/arena-engine/src/runner/runner-loop.mjs:244:22)
```

crpyto が必要だけど require() が存在しないのでエラーになる。
多分ブラウザだと global.crpyto の中身があるんだろうなぁ。
https://github.com/tinygo-org/tinygo/blob/a9ba6ebad9ad85cacd9674a1af9229489a811f68/targets/wasm_exec.js#L103-L110

試しにコメントアウトしてみたけど下記でエラー
https://github.com/tinygo-org/tinygo/blob/a9ba6ebad9ad85cacd9674a1af9229489a811f68/targets/wasm_exec.js#L121-L127

## 悪あがき
せめて html 上で動かないかなーと思って書き作ってみたけど fetch() が CORS で取れないからあきらめた…。
Webサーバー立てれば動くかもだけど力尽きました。。。
```html
<html>
    <head>
        <meta charset="utf-8"/>
        <script src="wasm_exec.js"></script>
        <script src="wasm.js"></script>
        <script>
            var mod = initWASM('wasm.wasm');
            console.log(mod.exports.test());
        </script>
    </head>
    <body>
    </body>
</html>
```


# 展望

https://github.com/screeps/screeps/issues/120 の話が進んで nodejs の utils ライブラリが使えるようになれば解消するかも？
いかんせん require が使えるようにならないと Tinygo WebAssembly は Screeps では厳しそう。

# 余談

- https://www.kabuku.co.jp/developers/annoying-go-wasm を呼んでTinyGoダメダメやん…って絶望したけど、最後まで読んだら希望が残っててほっとした。束の間だったけど。。。
- Screeps ってNodeJSで動いてるんじゃないのかぁ。 require() 使えてほしいんだけどなぁ。 Javascriptぽいスクリプトなだけなのかな。