Unity3D
Unity
gRPC

UnityでgRPCを動かす

準備

前提

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というインターフェースが無く、コンパイルエラーが出ますので、以下のような定義を追加します。

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 を以下のように書き換えます。まあ、中身はまるっとその処理を無視するような内容です。

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を抜き出すということをしました

参考

なんだかんだあって、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

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

UnityからCallする処理を入れる

以下のようなコードを書きます

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

その他

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で出来たらなと思います。