アプリ内にサーバを立ててcurlでかんたんRPCするEasyHttpRpc

## GUIデバッグツールには限界がある

Unityに限らず、開発においてデバッグツールは必須です。
特にXR系の開発だと、実機で動かしながらあれこれパラメータを調整したい、このタイミングでこのイベント発火させたい、となることが多いと思います。
デバッグ用のUI作って、`DebugManager`とか作って、設定ファイルパースして読み込んで、デバッグビルドのときだけ有効になるようにして……。
「そんなもん増えたり減ったりするのに毎回追従して作るのめんどくさくない……？」とわたしは常々思っていました。

1. 機能の修正
1. デバッグコードの修正
1. デバッグUIの修正[^fov]

3段階も必要なのは正直言って苦痛です。**「便利 > 手間」**であるべきデバッグツールが、**「手間 > 便利」**になって、結果、デバッグツールを作った人くらいしかメンテをしなくなるか、誰も使わなくなるか、ということがよくあります。

## EasyHttpRpc
というわけで、それを解決するためのデバッグツールを作りました。

[EasyHttpRpc](https://gist.github.com/nekomimi-daimao/e5726cde473de30a12273cd827779704)

アプリ内にサーバを立ててAPIを叩くことで、任意の引数を渡して任意のメソッドを実行し、ログを取得したりとかパラメータ変更したりとか、その他いろいろの操作ができるやつです。

### 要件

- 変更が簡易  
テストを書き足すくらいの気軽さでデバッグツールも更新できる
- 操作が簡単  
説明がなくても見ればだいたいわかる
- 標準機能のみ  
特定のライブラリやAssetに依存しない
- デバッグの環境の導入がかんたん  
デバッグツールの実行に特殊なツールを要求しない

### 使い方

```c#
private EasyHttpRPC _easyHttpRPC;

private void Start()
{
    // newするとサーバが起動する.CancellationTokenが必要.
    // 任意でポート番号も指定できる.デフォルトは1234.
    _easyHttpRPC = new EasyHttpRPC(this.GetCancellationTokenOnDestroy());

    // 外から呼び出すメソッドを登録する.
    _easyHttpRPC.RegisterRPC(nameof(Instantiate), Instantiate);
}

// 引数のNameValueCollectionにはGetのパラメータがそのまま入っている.
// Taskなので終了まで待ってからレスポンスを返せる.
private async Task<string> Instantiate(NameValueCollection arg)
{
    await UniTask.SwitchToMainThread();

    var argColor = arg["color"];
    if (string.IsNullOrEmpty(argColor) || !ColorUtility.TryParseHtmlString(argColor, out var color))
    {
        color = Color.white;
    }

    var argCount = arg["count"];
    if (!int.TryParse(argCount, out var count))
    {
        count = 0;
    }

    await _primitiveSupplier.Instantiate(color, count);
    return "SUCCESS";
}

```

## サンプルリポジトリ

[EasyHttpRpcExample](https://github.com/nekomimi-daimao/EasyHttpRpcExample)
サンプルのリポジトリです。
クラスも3つ、なにひとつ難しいことはしていないので、中身を見てもらえれば雰囲気はわかると思います。

- VSCodeのプラグインREST Clientをインストールします。  
[REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client)
VSCodeのプラグインをインストールします。これ自体は名前の通りREST APIを実行するためのツールなので、`curl`が実行できる環境があれば入れなくても大丈夫です。[^rest]

- このリポジトリをクローンします。
- Assets/Scenes/EasyHttpRPCExample.unityを開いて実行します。  
![スクリーンショット 2021-03-18 3.49.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146245/d1fd8563-6e22-0e29-6da9-2224a4526cd1.png)
実行すると`Game`タブの左上とDebuggerにくっついている`DebuggerPrimitiveSupplier`にサーバのアドレスが表示されます。

- http/rest.httpをVSCodeで開きます。
- 画面に表示されたアドレスをrest.httpに入力します。  

```
# enter your address
@baseurl = http://192.168.１１.15:1234/
```

先程のスクリーンショットの環境をもとに入力するならこんな感じです。

- APIを実行します。

![execute-api.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146245/cb396292-4306-19b1-f667-675f27314e3d.gif)

## まとめ
`RPC`って堂々と言うのは憚られるので`かんたんRPC`って書きました。
任意のタイミングで実行できて返り値も出せるので`RPC`っぽいなにかと名乗っても許してほしいところです。

基本的に`GET`を想定しています。引数に=?が含まれているとか改行コードがあるとかそういう面倒な問題に出くわしたときの回避策として`POST`もいちおう入れてありますが、ほんとに入れただけです。
引数も返り値も`string`で、使うところで使う人がパースする、と割り切りました。`json`はなにげに書くの面倒なので`GET`のパラメータをそのまま`NameValueCollection`として出しています。これくらいプリミティブにしておいたほうが変更に強いでしょう。

[EasyHttpRpc](https://gist.github.com/nekomimi-daimao/e5726cde473de30a12273cd827779704)はパラメータも込みで`curl`叩ければなんでもいいので、`html`とかも返すようにしてやると、ブラウザから操作できるようになるので、非エンジニアの人でも操作できるようになっていいかもしれません。UIメンテナンス問題が復活しますが、`Unity`の`GUI`いじるよりは`html`を適当にぽちぽちするほうがまだましでしょう。

容赦なく非UIスレッドから実行されるので、そこだけは気をつけてください。
どうせ現代のアプリにおいて非同期処理は必須なので、とりあえず`UniTask`使ったらいいと思います。

ちくちく夜なべして作った甲斐あって、少なくとも自分が今使っている分にはいい感じです。
XRデバイスでの文字入力、つらい。

おしまい。

## 参考
[スマホ実機でデバグ用のhttpサーバを動かしてみる](https://techblog.kayac.com/debugging-smart-devices-via-http-server)
[C# による簡易 HTTP サーバの実装](https://blog.clock-up.jp/entry/2016/12/01/csharp-http-server)
[[VSCode] REST Client は変数を使うとAPIの環境やパラメータ変更が楽になる！](https://dev.classmethod.jp/articles/vscode-rest-client-is-good/)
[【Unity】カラーコードとColor型を変換する【C#】](https://nekosuko.jp/1674/)
[GUIのフォントサイズ変更など](http://himascript.blog.fc2.com/blog-entry-6.html)


[^fov]: 特にVRゴーグルやARグラスはデバッグに使える視野が限られている、いわば「画面が狭い」デバイスなのでデバッグメニュー作るのほんとつらい
[^rest]: 余談ですがこのツールThis is itって感じでめちゃくちゃ使いやすいです。IntelliJのやつとほぼ一緒なのがポイント。HoppscotchとかPostmanとかいろいろありますが、API叩きたいだけなのにアカウント要求やらなんやらされると、めんどくさくてイヤ。