Unity C# × gRPC × サーバーサイドKotlinによる次世代のサーバー/クライアント通信 〜ハイパフォーマンスな通信基盤の開発とMagicOnionによるリアルタイム通信の実現〜

Unity C# × gRPC × サーバーサイドKotlinによる次世代のサーバー/クライアント通信 〜ハイパフォーマンスな通信基盤の開発とMagicOnionによるリアルタイム通信の実現〜

CEDEC 2019の発表資料です。

セッションの内容(公式サイトより):
昨今のゲーム開発では、クライアント/サーバー間のデータ通信にPHP、Ruby、Javaといった言語で実装したREST APIを使用しているプロダクトがほとんどだと思います。

その中で株式会社アプリボットでは新たなる通信の技術として、Google製のRPCフレームワークであるgRPC、サーバーサイドの言語としてはKotlinを導入しました。また、株式会社Cysharpでオープンソースとして公開している、C#とgRPCを使用したMagicOnionという通信フレームワークを導入し、リアルタイム通信を実現しています。

Unityでの事例が少ないgRPCや、サーバーサイドの言語としては国内での実績が少ないKotlinをなぜ採用したのか、その理由と実際に導入した上でのノウハウ、そしてMagicOnionを使用したリアルタイム通信の開発について、事例とともに紹介します。

https://cedec.cesa.or.jp/2019/session/detail/s5c9dede391631

共同講演者:
株式会社Cysharp 代表取締役 河合宜文
https://twitter.com/neuecc
https://www.slideshare.net/neuecc

D1531f9547e24397c7e85881fac03096?s=128

Takehata Naoto

September 04, 2019
Tweet

Transcript

  1. 2.

    今回の構成 第1部 Unity C# × gRPC × サーバーサイドKotlinによる 次世代サーバー/クライアント通信の開発 株式会社アプリボット

    ⽵端尚⼈ 第2部 Unity C#と.NET Core(MagicOnion) C# そしてKotlinによるハーモニー 株式会社Cysharp 河合宜⽂
  2. 5.

    概要 ⽵端 尚⼈ 職種:バックエンドエンジニア 好きな⾔語:Kotlin Twitter:@n_takehata 2006.04 公務員 2007.12 SES

    2011.01 サイバーエージェント 2014.04 アプリボット 現在開発中の SEVEN′s CODE(セブンスコード) のサーバーサイドエンジニア
  3. 6.

    登壇、執筆など • CEDEC 2018登壇 https://speakerdeck.com/n_takehata/cedec- 2018 • Kotlin Fest 2018登壇(LT)

    • 雑誌Software Design 2019年2〜4⽉号に てサーバーサイドKotlinについての短期連 載執筆
  4. 15.
  5. 33.

    平均リクエスト時間 (ms) 平均レスポンス時間 (ms) gRPC 2.92 3.6 REST 7.16 12.78

    ※2018年3⽉当時の検証結果です リクエストが約2倍、レスポンスが約4倍速かった
  6. 45.

    モダンな開発ができる • コードがシンプル • 型推論 • String Template • プロパティ

    • データクラス • etc… • 安全 • Null安全 • val、var
  7. 57.

    クライアントコードの実装 ①接続の設定 ②Stubのインスタンス⽣成 ③Stubのメソッド実⾏ Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure);

    var client = new Greeter.GreeterClient(channel); String user = "you"; var reply = client.SayHello(new HelloRequest { Name = user }); Console.WriteLine("Greeting: " + reply.Message); ① ② ③
  8. 70.
  9. 72.

    コードジェネレータの設定 protobuf { protoc { artifact = "com.google.protobuf:protoc:3.5.1-1" } plugins

    { grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } } generateProtoTasks { all().each { task -> task.plugins { grpc } } } generatedFilesBaseDir = "$projectDir/src/" }
  10. 75.

    実⾏するコードの実装 ①⾃動⽣成のBaseクラスを継承 ②gRPCのサービスとして起動時に読み込ませるアノテーション @GRpcService class GreeterService: GreeterGrpc.GreeterImplBase() { override fun

    sayHello(request: HelloRequest, responseObserver: StreamObserver<HelloReply>) { val replyBuilder = HelloReply.newBuilder().setMessage("Hello " + request.name) responseObserver.onNext(replyBuilder.build()) responseObserver.onCompleted() } } ① ②
  11. 100.

    パフォーマンス検証の続報 形式 10 100 300 500 gRPC 17 46 113

    177 REST 73 90 132 158 ※1⾏⽬はデータ容量(Byte)
  12. 115.
  13. 119.

    Bodyの容量は⼀応変更できる new Channel("localhost", ChannelCredentials.Insecure, new [] { new ChannelOption(ChannelOptions.MaxReceiveMessageLength, 10000000),

    new ChannelOption(ChannelOptions.MaxSendMessageLength, 10000000) }); 本来の思想にも反するのであまり・・・
  14. 124.

    講演者プロフィール • 河合 宜⽂ / Kawai Yoshifumi / @neuecc •

    Cysharp, Inc. – CEO/CTO • 株式会社Cygamesの⼦会社としてC#関連の研究開発やコンサ ルティングを⾏う • メインミッションはC#⼤統⼀理論(サーバー/クライアント共にC#で 実装する)の推進 • 株式会社アプリボットの新規タイトル(タイトル未発表)におけ るMagicOnionの導⼊と周辺の基盤開発を担当
  15. 125.
  16. 126.
  17. 128.

    MagicOnion • Unified Realtime/API Engine for .NET Core and Unity

    • https://github.com/Cysharp/MagicOnion/ • Cysharp開発のC#によるOSSのネットワークエンジン • リアルタイム系もAPI系も両⽅OK • HTTP/2 gRPCの上に構築され、⾼性能とスタンダードを両⽴ • .NET CoreによるLinuxホスティングと完全なコンテナ対応 • 現在GitHub Starが1000以上と、国内外でも注⽬度上昇中 • パフォーマンスとC#としての使い勝⼿にこだわって開発 • C#のエキスパートが最初からUnity C#も前提に組み⽴てているた め、C#のためのエンジンとして⾼い品質を誇る
  18. 129.

    public class TestService : ITestService { // パブリックメソッドがそのままgRPC定義 public async

    UnaryResult<int> Sum(int x, int y) { // async/awaitにも⾃然に対応 // マジカル技術によりasync Task<T>じゃなくてもawait可能 await Task.Yield(); return x + y; } } // 普通のgRPCの接続を作る(MagicOnion⽤の特別なことはない) var channel = new Channel("127.0.0.1:12345"); // ⾃然な書き味で、タイプセーフにRPC通信を実現 // C#のasync/await構⽂により、⾮同期通信も⾃然に⾒える var client = MagicOnionClient.Create<ITestService>(channel); var result = await client.Sum(100, 200); クライアントもサーバーも⾃ 然に繋がっているように⾒え る(デバッガもサーバー/クラ イアント共有でステップ実⾏ で繋がって動いていく)
  19. 130.
  20. 131.

    Why not (plain) gRPC • protoはC#ではない︕︕︕ • 故に⾔語の持つ • 全ての型(Primitive,

    Nullable, Dictionaryなど)が使えない • 属性(Attribute, Annotation)付与ができない • リクエスト毎に必ず⼀つの型が必要になる • 故にIDE(Visual Studio, Rider, etc...)の持つ • シンタックスハイライトが効かない • コードレンズなどのコード追跡機能が効かない • リファクタリング⽀援(名前⼀括変更など)が効かない • サーバー/クライアント⼤統⼀ならクライアント/サーバー超えて変更 される︕
  21. 136.

    現実解 Microser vices Realtime Server Unity API Server API Serverは各社の得意な⾔語

    (Ruby, PHP, Java, Go, Kotlin, etc…) リアルタイムサーバーはMagicOnion 使うでいいんじゃないか
  22. 139.

    C#とKotlinによるロジック共有 • ロジック(計算式など)をKotlinサーバーとUnityで共有したい︕ • 例えば戦闘⼒や経験値算出などをクライアントで計算して表⽰ • 何かを消費するなどして適⽤する場合、サーバー側でもバリデーショ ンとして計算して適⽤する • などなど、同じ計算式が必要な場合はままある

    • 共有⽅法を考える • 案A. ⼿書きで両⽅に作る • 変更耐性が低い︕うっかり忘れが発⽣しても気づかなそう • 案B. 中間定義からKotlin, C#のコードを作る • よくわからないオレオレ中間⾔語は誰も書きたくない
  23. 140.

    C#とKotlinによるロジック共有 • ロジック(計算式など)をKotlinサーバーとUnityで共有したい︕ • 例えば戦闘⼒や経験値算出などをクライアントで計算して表⽰ • 何かを消費するなどして適⽤する場合、サーバー側でもバリデーショ ンとして計算して適⽤する • などなど、同じ計算式が必要な場合はままある

    • 共有⽅法を考える • 案A. ⼿書きで両⽅に作る • 変更耐性が低い︕うっかり忘れが発⽣しても気づかなそう • 案B. 中間定義からKotlin, C#のコードを作る • よくわからないオレオレ中間⾔語は誰も書きたくない MagicOnionならC#で書いたコードが 全てそのまま無条件でサーバーとク ライアントで共有できてハッピー︕
  24. 142.

    案C. C#内部ロジックサービスとの通信 Kubernetes Pod Kotlin API Server C# Internal Service

    Unity C# Kubernetesの同⼀Pod内にサイドカーと して外部と通信するKotlinサーバーと、内 部のみのC#によるサービスを⽴ち上げ、 gRPC(Unix Domain Socket)で通信して 計算結果を取得
  25. 143.

    案D. C# to Kotlinでコード共有(採⽤) • C#を正としてUnity側中⼼にロジックを書いてしまい、Kotlin 側はC#からのコードジェネレートで⽣成する • MicroBatchFramework •

    https://github.com/Cysharp/MicroBatchFramework • C#(.NET Core)でCLIツール作るのに便利なフレームワーク • Roslyn(Microsoft.CodeAnalysis.CSharp) • C#によるC#コンパイラ • C#のAbstract Syntax Treeが取れる
  26. 144.

    class Program : BatchBase { static async Task Main(string[] args)

    { await BatchHost.CreateDefaultBuilder().RunBatchEngineAsync<Program>(args); } public void Generate( [Option("i", "入力するフォルダ")]string inputDirectory, [Option("o", "出力するフォルダ")]string outputDirectory) { foreach (var inputFilePath in Directory.GetFiles(inputDirectory, "*.cs", SearchOption.AllDirectories)) { var tree = CSharpSyntaxTree.ParseText(File.ReadAllText(inputFilePath)); var parseInfo = Parse(tree); foreach (var item in parseInfo) { var template = new KotlinTemplate { ClassInfo = item }; var code = template.TransformText(); var outPath = Path.Combine(outputDirectory, item.ClassName) + ".kt"; File.WriteAllText(outPath, code, Encoding.UTF8); } CSharpSyntaxTree.ParseTextで.cs ファイルからSyntaxTreeを取得 テンプレートエンジン(T4)を使って Kotlinコードを⽣成 MicroBatchFrameworkによる CLI定義と実装 ASTを元にテンプレートに当ては める情報を⽣成(⾃前実装)
  27. 148.

    vs Unity .csproj • UnityはUnityの管理下のソースコードのみ参照可能 • サーバーのcsprojは柔軟に記述できる • よって共有するコードの実体をUnityのほうに置いて、サー バー側ではコードリンクで参照する

    <ItemGroup> <Compile Include="..¥Unity¥Assets¥Scripts¥Shared¥**¥*.cs" /> </ItemGroup> シンボリックリンクとか、ビルドしてマ ネージドDLLを配置とかやるとトラブルの 元なので、シンプルな⼿段が⼀番 シェアするディレクトリはasmdef で切っておくとより良い
  28. 149.

    Unity Shimsの実装 • Unity依存コードの除去 • 理想的には完全除去が望ましいが、特に開発中でUnity側主導でコード ができあがっている場合、残ってしまっていることが多い • ダミーの型を⽤意して回避するのが⼿っ取り早くは楽 namespace

    UnityEngine { public class ScriptableObject {} public class MonoBehaviour {} public sealed class SerializeFieldAttribute : Attribute { } // etc... } 意外と問題なく動いたり動かなかったり。 とりあえずコンパイル通す→動かして問題出た とこを何とかする、で⼯数的にそんな多くかか らず何とかなる、ことも少なくないかな、と。
  29. 158.

    MagicOnion is... • High Performance • gRPC(HTTP/2) + MessagePack-CSharp •

    Modern Architecture • .NET Core, Container, OpenTelemetry • C# Friendly • C# as a Schema • Unity Friendly • Runtime/CodeGen(for IL2CPP)
  30. 159.

    未来を構築する • 新しい時代の新しいフレームワーク • 今やゲームにおいてリアルタイム通信はほぼ必須 • 5Gも迫っていてフレームワークも変化しなければいけないタイミング • ならばこそ、より⼤きな未来を描いていきたい •

    完全統合形フレームワークという⼀つの理想 • クライアントとサーバーを • APIとリアルタイムを • 全てをC#で統合するという夢想を具現化するのがMagicOnion • 理想を理想のままにせず、現実の結果に残すためにCysharpはOSSに よる公開から、開発サポートまで様々なことをしていきます︕