UnityでgRPCを動かす

## 準備
- Unity本体
ただし、`Script Runtime Version`で.NET4.x系のものが使えるバージョン
(Unity2018など)

- 以下の2つのレポジトリをCloneしておく
gRPC : https://github.com/grpc/grpc
protobuf : https://github.com/google/protobuf

## 前提
Unity + gRPCでは、MagicOnionというライブラリが有名ですが、ここではそれを使いません。
ただし、MagicOnionの修正内容は参考にしました...

## 以下をUnityプロジェクトに入れる
- gRPCのC#のソースコード  
`grpc/src/csharp/Grpc.Core`
- protobufのC#のソースコード  
`protobuf/csharp/src/Google.Protobuf`

## エラーを粛々と取り除く
### IAsyncEnumeratorが無いと怒られる

```
Assets/Grpc.Core/IAsyncStreamReader.cs(53,46): error CS0246: The type or namespace name `IAsyncEnumerator' could not be found. Are you missing an assembly reference?
``` 
IAsyncEnumeratorというインターフェースが無く、コンパイルエラーが出ますので、以下のような定義を追加します。

```csharp
public interface IAsyncEnumerator<T> : IDisposable
{
    T Current { get; }
    Task<bool> MoveNext(CancellationToken token);
}
```

## MoveNextの引数がないと言われる
```
Assets/Grpc.Core/Internal/ServerCallHandler.cs(69,69): error CS1501: No overload for method `MoveNext' takes `0' arguments
```
これは、Grpc.CoreがRx.NETに依存しているためこのようなエラーが表示されます。
なので、さらに以下のコードを追加します(namespaceやファイルの配置場所はそれぞれの事情に合わせてやってね)

```
    public static class AsyncEnumerator
    {
        public static Task<bool> MoveNext<T>(this IAsyncEnumerator<T> enumerator)
        {
            if (enumerator == null)
                throw new ArgumentNullException(nameof(enumerator));

            return enumerator.MoveNext(CancellationToken.None);
        }
    }
```
これは、Rx.NETのコードの中身そのままです。
Rx.NETまでUnityに入れようか悩みましたが、UniRxの競合とかとも考えられるためこの程度にしています。

## この時点でコンパイルが通る
エラーが出なくなりました。では、次に実行してみましょう。

## デバッグ実行用のSSLの証明書がないと言われる
実行するとこのエラーが出ました
```
IOException: Error loading the embedded resource "Grpc.Core.roots.pem"
```
SSLの証明書がない場合のエラーと考察し、どうしようかと考えていましたがここで、`MagicOnion` の修正方法を参考にさせていただきました。`DefaultSslRootsOverride.cs` を以下のように書き換えます。まあ、中身はまるっとその処理を無視するような内容です。

```csharp

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;

namespace Grpc.Core.Internal
{
    /// <summary>
    /// Overrides the content of default SSL roots.
    /// </summary>
    internal static class DefaultSslRootsOverride
    {
        //const string RootsPemResourceName = "Grpc.Core.roots.pem";
        static object staticLock = new object();

        /// <summary>
        /// Overrides C core's default roots with roots.pem loaded as embedded resource.
        /// </summary>
        public static void Override(NativeMethods native)
        {
            lock (staticLock)
            {
                //var stream = typeof(DefaultSslRootsOverride).GetTypeInfo().Assembly.GetManifestResourceStream(RootsPemResourceName);
                //if (stream == null)
                //{
                //    throw new IOException(string.Format("Error loading the embedded resource \"{0}\"", RootsPemResourceName));
                //}
                //using (var streamReader = new StreamReader(stream))
                //{
                //    var pemRootCerts = streamReader.ReadToEnd();
                //    native.grpcsharp_override_default_ssl_roots(pemRootCerts);
                //}
            }
        }
    }
}
```
これはMagicOnionのソースコードそのままです。
これで、この実行時のエラーは出なくなります。

## DllNotFoundException: grpc_csharp_ext
grpc_charp_exが無いとエラーが表示されます。
```
DllNotFoundException: grpc_csharp_ext
Grpc.Core.Internal.NativeLogRedirector.Redirect (Grpc.Core.Internal.NativeMethods native) (at Assets/Grpc.Core/Internal/NativeLogRedirector.cs:49)
```
なので、それを入手してDLLをAssets配下に置く必要があるのですが、ここで自分は手間取りました。
Ubuntsu on Windowsを使っていたせいなのかはわからないですが、grpc本家のREADMEを読みつつビルドを試しましたが、うまくいかず、結局サンプルコードをVisual Studioでビルドし、NuGetパッケージの中かからdllを抜き出すということをしました

### 参考
- https://github.com/grpc/grpc/blob/master/README.md
- https://github.com/grpc/grpc/tree/master/src/csharp

## なんだかんだあって、grpc_csharp_ext.dllをUnityプロジェクトに入れる
ここで、grpcのレポジトリ内にあるサンプルプロジェクトをビルドし、サーバーを立ち上げます。
https://github.com/grpc/grpc/tree/master/examples/csharp/helloworld

この中身をVisual Studioでビルドするだけでサーバー側のプログラムまでビルドされるので、ビルドしたサーバー側のexeファイルを立ち上げます。Windows以外の環境であれば、READMEの手順の通りに `.NET Core` インストールしつつビルドすることで実行できるかと思います。

## クライアント側のサンプルコードを参考にしつつUnity側のプログラムを書く
まず、gRPC自体のサンプルプロジェクトには `.proto` と言われるサーバー側との通信規約をもとにしたコードもあるので、それをUnityプロジェクトに入れます。
![3bdce428a37fe5aba1e59e6acdbf54b8.png](https://qiita-image-store.s3.amazonaws.com/0/16583/323fca03-45b4-890b-e22e-0e74f60d6318.png)

ここにある `HelloWorld.cs/HelloWorldGRPC.cs` がそれです。
これは、gRPCのコード生成プログラムによって吐かれるコードですがサンプルプロジェクトには含まれています。

## UnityからCallする処理を入れる
以下のようなコードを書きます

```csharp

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Grpc.Core;
using Helloworld;


public class TestGrpc : MonoBehaviour 
{
    void Start () 
    {
        Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure);

        var client = new Greeter.GreeterClient(channel);
        String user = "Test UserName";

        var reply = client.SayHello(new HelloRequest { Name = user });
        Debug.Log("Greeting: " + reply.Message);
    }
}
```
これは、gRPCのC#サンプルプロジェクトの中身をそのままUnityのStartに入れ込んだだけです。
適当なGameObjectにこれをアタッチして実行します。
サーバー側のプログラムを立ち上げた上で実行すると、以下のようにちゃんとレスポンスを受け取れました！

![d77a882b1dc6e1bce7cc29c7ad487ddf.png](https://qiita-image-store.s3.amazonaws.com/0/16583/8f930dee-b545-4dc1-032a-195ac74963fe.png)

## その他
Android/iOSで動かすには、`grpc_csharp_ext.dll` をそれぞれのプラットフォームごとコンパイルするという苦行が待ってます。
https://github.com/grpc/grpc/blob/master/src/csharp/experimental/README.md
こちらにそのやり方は書かれてますが、まだiOSはTBD扱いです。。。
UniRxの作者の方が、いろいろやり取りされているので、できるんだと思います。
(grpc_csharp_extの中身はCのコードなのでXCodeでゴニョゴニョするとできるはず)

次は各プラットフォームの対応 + リアルタイムチャットなどをgRPCで出来たらなと思います。