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. Unity C# × gRPC × サーバーサイドKotlinによる 次世代のサーバー/クライアント通信 〜ハイパフォーマンスな通信基盤の開発と MagicOnionによるリアルタイム通信の実現〜 株式会社Cysharp

    代表取締役 河合 宜⽂ 株式会社アプリボット チーフエンジニア ⽵端 尚⼈ 2019/9/4 CEDEC 2019
  2. 今回の構成 第1部 Unity C# × gRPC × サーバーサイドKotlinによる 次世代サーバー/クライアント通信の開発 株式会社アプリボット

    ⽵端尚⼈ 第2部 Unity C#と.NET Core(MagicOnion) C# そしてKotlinによるハーモニー 株式会社Cysharp 河合宜⽂
  3. 第1部 Unity C# × gRPC × サーバーサイドKotlinによる 次世代サーバー/クライアント通信の開発 株式会社アプリボット チーフエンジニア

    ⽵端 尚⼈
  4. ⾃⼰紹介

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

    2011.01 サイバーエージェント 2014.04 アプリボット 現在開発中の SEVEN′s CODE(セブンスコード) のサーバーサイドエンジニア
  6. 登壇、執筆など • CEDEC 2018登壇 https://speakerdeck.com/n_takehata/cedec- 2018 • Kotlin Fest 2018登壇(LT)

    • 雑誌Software Design 2019年2〜4⽉号に てサーバーサイドKotlinについての短期連 載執筆
  7. 会社紹介 株式会社アプリボット ▪事業内容 • スマートフォン向けゲームアプリの企画、制作、運⽤ • 2010年7⽉サイバーエージェント⼦会社として設⽴ ▪公式HP https://www.applibot.co.jp/ ▪技術ブログ

    https://blog.applibot.co.jp/
  8. 使⽤している技術セット(の⼀部) サーバー ・Kotlin ・Java ・Spring Boot ・gRPC クライアント ・Unity ・Cocos-2dx

    インフラ ・AWS ・GCP
  9. 使⽤している技術セット(の⼀部) サーバー ・Kotlin ・Java ・Spring Boot ・gRPC クライアント ・Unity ・Cocos-2dx

    インフラ ・AWS ・GCP
  10. 今⽇は新規のプロダクトで使⽤している Unity、gRPC、サーバーサイドKotlinを 使った開発について紹介します

  11. 現在開発中のSEVEN′s CODE(セブンスコード)でも使⽤している技術

  12. アジェンダ 1.なぜgRPCを導⼊したのか? 2.なぜサーバーサイドKotlinを導⼊したのか? 3.Unity C#、サーバーサイドKotlinでのgRPCの使い⽅ 4.データダウンロード機構から⾒るgRPCのノウハウ

  13. 2019/9/4 なぜgRPCを導⼊したのか? 1

  14. gRPCとは︖ • Google製のRPCフレームワーク • HTTP/2、Protocol Bufferesを標準でサポートし、ハイパ フォーマンスな通信を実現 • RESTに代わる通信⽅式の⼀つとしても期待されている

  15. 特徴

  16. • Protocol Bufferesによるインターフェース定義共通化 • Java、C#、Goなど様々な⾔語に対応 • 各⾔語のコード⾃動⽣成が可能 • HTTP/2による⾼速で柔軟な通信

  17. Protocol Buffersについて

  18. Protocol Buffersとは︖ • 通信のインターフェースを定義できるIDLの⼀種 • 定義した構造のバイナリデータで通信する • protocというコマンドで、定義ファイルからインターフェース に合わせた様々な⾔語のコードを⽣成できる (Java、C#、C++、Python、Go、Ruby、PHP、Dart)

  19. サンプル

  20. ①リクエスト、レスポンスのパラメータを定義 ②APIのインターフェースを定義 .protoという拡張⼦で定義 service Greeter { rpc SayHello (HelloRequest) returns

    (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; } ① ②
  21. 使いたい⾔語を指定してコンパイル

  22. protocコマンドの例(Golang) protoc --go_out=plugins=grpc:../pb hello.proto

  23. ⽣成されるコード • Messageのデータ構造に合わせたModelクラス • データのシリアライズ、デシリアライズ • gRPCのサーバー実装のBaseクラス(プラグイン必要) • gRPCのクライアントStub (プラグイン必要)

    • etc…
  24. コンパイルイメージ

  25. ⽣成したファイルの実⾏イメージ ⾃動⽣成のServer、Stubのプログラムを介して 通信を実装できる

  26. 通信部分の実装が容易に • 定義ファイルさえ書けば、パラメータのクラスなどを作成する ⼿間が不要 • サーバー、クライアント間の実装の齟齬も防げる • GraphQLやSwaggerとも通づる機構

  27. パフォーマンス

  28. REST(HTTP1.1、JSON)との ⽐較をしました

  29. • クライアントはUnity C#、サーバーサイドはKotlinで実装 • 通信のデータ形式に、gRPCではProtocol Buffers、RESTではJSON を使⽤ • リクエストデータのシリアライズ、レスポンスデータのデシリ アライズの時間も含める

  30. サーバーサイド • ⾔語はKotlin、フレームワークとしてSpring Bootを使⽤ • gRPC部分にはgrpc-spring-boot-starterを使⽤ https://github.com/LogNet/grpc-spring-boot-starter

  31. クライアントサイド • 公式のgRPCライブラリを使⽤ https://github.com/grpc/grpc • Unityアプリの実機動作に⼀⼿間が必要なため(後述)、検証時 はUnity Editor上で動作確認

  32. 検証結果

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

    ※2018年3⽉当時の検証結果です リクエストが約2倍、レスポンスが約4倍速かった
  34. • 通信速度の向上によりUXの改善につながる • データダウンロードなど、膨⼤なデータをダウンロードする際 には特に絶⼤な効果が期待できる ※ただし、条件あり(後で話します)

  35. gRPCの導⼊を決める

  36. 2019/9/4 なぜサーバーサイドKotlinを導⼊したのか? 2

  37. 前提 以前はJavaを使って開発していました

  38. Javaを使っていた理由 • コンパイル⾔語であり、処理性能がLL⾔語に⽐べて⾼い • 静的型付け⾔語であり、⼤⼈数での開発にも向いている • 歴史の⻑い⾔語なので事例やノウハウも多い

  39. 悩みも・・・ • 歴史の⻑い⾔語でもあるがゆえ、レガ シーな部分はある • 正直新しい技術触りたい

  40. Kotlinがいいんじゃないか︖

  41. なぜいいいと考えたか︖

  42. 移⾏のメリット • Javaからの移⾏コストが低く抑えられる • Javaと同等の性能が維持できる • モダンな開発ができる • 将来性も期待できる

  43. 移⾏コストを低く抑えられる • JetBrains社がJavaの資産を活かすことを想定して開発した⾔ 語である • Javaとの相互互換 • Spring5.0のKotlinサポート

  44. Javaと同等の性能が維持できる • Java、Scala、Groovy等と同じJVM⾔語 • 最終的にコンパイルされた実⾏ファイルはJavaとほぼ同等にな る

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

    • データクラス • etc… • 安全 • Null安全 • val、var
  46. 将来性も期待できる • Androidの公式の開発⾔語として採⽤された • Spring5.0のKotlinサポート

  47. まとめると

  48. まとめると • モダンな実装ができる • Javaのメリットの多くがそのまま残り、資産(ライブラリ等)も活⽤ できる • 且つ移⾏コストも低く抑えられる (Javaを使っているシステムには) いいこと尽くし︕

  49. 2019/9/4 Unity C#、サーバーサイドKotlinでのgRPCの使い⽅ 3

  50. ①UnityのgRPC対応 ②サーバーサイドKotlinのgRPC対応 ③gRPCを使うためのインフラ構成 for AWS ④負荷試験ツールの選定

  51. ①UnityのgRPC対応 ②サーバーサイドKotlinのgRPC対応 ③gRPCを使うためのインフラ構成 for AWS ④負荷試験ツールの選定

  52. 3-①UnityのgRPC通信

  53. UnityでのgRPCの現状 • 実験的サポートの状態 • Unity Editor上での動作はgRPCライブラリを使うことで可能 • iOS、Androidの実機での動作は、⾃前のカスタマイズが必要 (だった)

  54. あまり試されていない (情報が少ない)

  55. 検証から始める

  56. Unity Editorでの起動 • Protocol BuffersからC#のStubを⽣成 • Unityプロジェクトのコード上から実⾏ 参考 : https://grpc.io/docs/quickstart/csharp/

  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); ① ② ③
  58. 実機ではそのままでは動かない

  59. 検証当時(2018年夏)の対応 • いくつかの静的ライブラリを⾃前でmakeして作る必要があっ た • 現在は公式がネイティブライブラリを⽤意してくれているので、 そちらを使える 参考 : https://blog.applibot.co.jp/2018/10/31/grpc-in-unity/

  60. 現在は公式からダウンロードできる デイリービルドから最新パッケージのgrpc_unity_package.x.xx.x-dev.zipをダウンロード https://packages.grpc.io/

  61. 現在の対応 • ダウンロードしたgrpc_unity_package.x.xx.x-dev.zipを解凍し、 Pluginsに配置する • iOS、Androidの実機ビルドができるようになる • ただし、まだExperimental 参考 :

    https://github.com/grpc/grpc/tree/master/src/csharp/experimental - unity
  62. SSL対応について

  63. SSL対応 • 証明書はgRPCライブラリに含まれる Grpc.Core.roots.pemを使⽤ する • Channel⽣成時にSslCredentialsクラスを使⽤して証明書を読 み込ませる

  64. SSL対応の実装 ①Resourcesに証明書ファイルを配置し、読み込む ②SslCredentialsに証明書の内容を渡し、Channel⽣成し使⽤する var pem = Resources.Load<TextAsset>("Grpc.Core.roots.pem"); var credentials =

    new SslCredentials(pem.text); _channel = new Channel(server, credentials); ① ②
  65. まとめると

  66. まとめると • Protocol BuffersからC#のコードを⽣成 • gRPCのStubを使⽤して実⾏ • 実機ビルドする場合はgrpc_unity_package(Experimental)を ダウンロードして展開

  67. ①UnityのgRPC対応 ②サーバーサイドKotlinのgRPC対応 ③gRPCを使うためのインフラ構成 for AWS ④負荷試験ツールの選定

  68. 3-②サーバーサイドKotlinのgRPC通信

  69. KotlinでのgRPCを使うには • ⾃動⽣成のコードはJavaで作成する(Kotlinには⾮対応) • Spring Bootを使っている場合はgrpc-spring-boot-starterを使う のが⼿軽 • コード⽣成はprotocを直接使わず、Gradleプラグインで実⾏で きる

  70. ⼿順

  71. Gradleにプラグイン、依存関係を追加 compile("io.github.lognet:grpc-spring-boot-starter:3.3.0") id("com.google.protobuf") version "0.8.9" ①Protocol Buffersをコンパイルするためのプラグイン ②Spring BootでgRPCを扱うためのStarter

  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/" }
  73. コンパイルタスクの実⾏ generateProtoタスクがprotocを実⾏ ./gradlew generateProto

  74. ファイルが⽣成される • xxxOuterClass.java • service、messageで定義したクラスを含むクラス • シリアライズ、デシリアライズなどをやってくれる • xxxGrpc.java •

    xxxOuterClassを使⽤してgRPCで通信するサーバー処理やクライアン トStubなどを含んだクラス
  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() } } ① ②
  76. まとめると

  77. サーバーサイドKotlinでgRPCを使うには • Gradleにprotobufのプラグイン、grpc-spring-boot-starterの依存関係 を追加 • protoファイルを定義しコンパイル • ⽣成されたコードを継承し、アノテーションを付けて実装 参考: https://blog.takehata-engineer.com/entry/server-side-kotlin-grpc-project-

    creation-to-start-confirmation
  78. ①UnityのgRPC対応 ②サーバーサイドKotlinのgRPC対応 ③gRPCを使うためのインフラ構成 for AWS ④負荷試験ツールの選定

  79. 3-③gRPCを使うためのインフラ構成 for AWS

  80. 構成図(超簡易版)

  81. gRPCを使った場合の注意点 • ALBではL4ロードバランシングでgRPCに対応していないため、 使⽤できなかった • L7ロードバランシングに対応したNLBを使⽤ • TLSの終端にはEC2内のnginxを使⽤(NLBではできない)

  82. NLBはTLS終端にできない

  83. NLBとALPN • TLSでHTTP/2使⽤するにはALPN(TLSの拡張)が必須となる • NLBではALPNをサポートしていないため、プロトコルのネゴ シエーションに失敗する

  84. ①UnityのgRPC対応 ②サーバーサイドKotlinのgRPC対応 ③gRPCを使うためのインフラ構成 for AWS ④負荷試験ツールの選定

  85. 3-④負荷試験ツールの選定

  86. 負荷試験ツールについて • Jmeter、Locust、Gatlingなど、既存の負荷試験ツールで標準 でgRPCに対応しているものはない • ⾃前で作成 or カスタマイズをする必要がある

  87. Gatlingを採⽤

  88. Gatlingとは • Scala製の負荷試験ツール • Scalaのコードベースでシナリオを書くことができる • Javaのコードを呼び出すことも可能(⾃動⽣成したgRPC関連の コードも使える)

  89. ScalaからgRPCの実⾏ val channel = ManagedChannelBuilder.forAddress("localhost", 6565).build() GreeterServiceGrpc.newBlockingStub(channel).sayHello(request) JavaのStubから実⾏できる

  90. GatlingでgRPCのプラグインを作成して使⽤

  91. 2019/9/4 データダウンロード機構から⾒るgRPCのノウハウ 4

  92. データダウンロード機構 • 「データ」は「データベース」内のデータのこと • データベースのデータをダウンロードし、クライアントに保存、 同期する機構のこと

  93. ①マスタデータ、ユーザーデータダウンロード ②マスタデータ差分ダウンロード ③ユーザーデータ差分同期

  94. ①マスタデータ、ユーザーデータダウンロード ②マスタデータ差分ダウンロード ③ユーザーデータ差分同期

  95. 4-①マスタデータ、ユーザーデータダウンロード

  96. ゲーム起動時のデータダウンロード機構 • 対象テーブル取得API、テーブルデータのダウンロードAPIを⽤ 意 • クライアント側は対象テーブル取得APIで取得したテーブルの 分、取得APIを実⾏する

  97. ダウンロードのフロー

  98. ゲーム起動時のデータダウンロード機構 • 対象テーブル取得API、テーブルデータのダウンロードAPIを⽤ 意 • クライアント側はテーブル名取得APIで取得したテーブルの分、 取得APIを実⾏する • レスポンスはJSONでシリアライズした⽂字列で返却する

  99. デシリアライズの問題

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

    177 REST 73 90 132 158 ※1⾏⽬はデータ容量(Byte)
  101. 容量が増えるとRESTの⽅が早くなる

  102. デシリアライズに絞った検証 ※1⾏⽬はデータ容量(Byte) 形式 10 100 300 500 ProtocolBuffers 4 32

    94 156 JSON 4 19 43 69
  103. デシリアライズはJSONの⽅が早い

  104. データダウンロードはJSONに • 基本的に1回の通信でダウンロードするデータ容量はできるだ け⼩さくする • 容量の⼤きくなるデータダウンロードAPIではJSON⽂字列とし てProtocol Buffers上は通信し、受信後はJSONとしてデシリア ライズして使う 参考:

    https://blog.applibot.co.jp/2018/11/21/grpc-ios/
  105. ①マスタデータ、ユーザーデータダウンロード ②マスタデータ差分ダウンロード ③ユーザーデータ差分同期

  106. 4-②マスタデータ差分ダウンロード

  107. マスタデータのバージョン管理 • マスタデータは運営側のタイミングでしか更新しないため、差 分のあるタイミングだけダウンロードする • 各テーブルのバージョンを管理 • 現状の最新バージョンを常にTrailerで返却し、クライアント側 で差分があればダウンロードフローに⼊る

  108. マスタデータの差分ダウンロードのフロー

  109. ①通常のクライアント/サーバー通信 ②データダウンロード機構 ③ユーザーデータ同期

  110. 4-③ユーザーデータ同期

  111. 更新データのサーバー、クライアント同期 • ユーザーデータの更新時、クライアントに更新を同期する機構 • サーバー側で更新処理があった際、更新レコードの情報を共通 項⽬としてTrailerに⼊れて返却する • クライアントはTrailerに情報がある場合は更新する

  112. Trailerで返却するユーザーデータの例 更新したレコードのデータを更新情報としてTrailerに設定して返却 1 {"updateInfo"{"userItem":[{"userId":1,"itemId":101,"count",5},{"userId" :1,"itemId":102,"count",6},{"userId":1,"itemId":103,"count",7},{"userId ":1,"itemId":104,"count",8},{"userId":1,"itemId":105,"count",9}...]}}

  113. 問題が発⽣

  114. Trailerの容量制限 • Treilerの容量は8KBまで • ⼤量のデータ更新があった場合(主に初期登録時など)、返却で きずエラーになってしまう

  115. 対応

  116. リクエストを分散 • Trailerで返却する値はテーブル名のリストのみとする • テーブル名のリストからデータ取得APIを実⾏する • サーバー側はダウンロード完了するまで更新データを Memcachedで保持しておく

  117. 修正後の同期処理のフロー

  118. おまけ: Bodyも4MB(デフォルト)の制限があるので注意

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

    new ChannelOption(ChannelOptions.MaxSendMessageLength, 10000000) }); 本来の思想にも反するのであまり・・・
  120. ⾊々作ってみてのまとめ

  121. まとめ • Protocol Buffersをベースにサーバー、クライアントで共通化 することで、実装コストは下がる • gRPCは各データの容量に制限があるので注意 • 基本は通信回数が増えても細かい単位のデータでやり取りする のがベター

  122. 第1部 終

  123. 第2部 Unity C#と.NET Core(MagicOnion) C# そしてKotlinによるハーモニー 株式会社Cysharp 代表取締役 河合 宜⽂

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

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

  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#のためのエンジンとして⾼い品質を誇る
  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); クライアントもサーバーも⾃ 然に繋がっているように⾒え る(デバッガもサーバー/クラ イアント共有でステップ実⾏ で繋がって動いていく)
  130. None
  131. Why not (plain) gRPC • protoはC#ではない︕︕︕ • 故に⾔語の持つ • 全ての型(Primitive,

    Nullable, Dictionaryなど)が使えない • 属性(Attribute, Annotation)付与ができない • リクエスト毎に必ず⼀つの型が必要になる • 故にIDE(Visual Studio, Rider, etc...)の持つ • シンタックスハイライトが効かない • コードレンズなどのコード追跡機能が効かない • リファクタリング⽀援(名前⼀括変更など)が効かない • サーバー/クライアント⼤統⼀ならクライアント/サーバー超えて変更 される︕
  132. C#がIDLなら C# as a Schema クライアント/サーバーの実装⾔語を 共にC#に固定することで、 通信スキーマそのものをC#で表現する プロジェクト参照で済むので IDLのバージョン管理が不要というのも運⽤上メリット

  133. しかしいきなりC#は投げ込めない • C#⼤統⼀理論 • そんな過激派な意⾒がいきなり通じるわけがない • 良いゲームを作るための開発であってC#を使うための開発 ではない • ↑私はC#を使うための(げふんげふん)

    • 各社それぞれの都合に合わせた融和のための施策から始める • そしてあわよくば(げふんげふん)
  134. ざっくり通信系の整理 Microser vices Realtime Server Unity API Server

  135. C#⼤統⼀理論 Microser vices Realtime Server Unity API Server 全部MagicOnionによってC#で繋 がって脳みそお花畑ハッピー︕

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

    (Ruby, PHP, Java, Go, Kotlin, etc…) リアルタイムサーバーはMagicOnion 使うでいいんじゃないか
  137. Agenda • C#(Unity)と他⾔語(今回はKotlin)の付き合い⽅ • C#(Unity) to C#(.NET Core)のコード共有 • MagicOnionによるリアルタイムサーバー実装

    • まとめ
  138. 2019/9/4 他⾔語間ロジック共有いろいろ 2

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

    • 共有⽅法を考える • 案A. ⼿書きで両⽅に作る • 変更耐性が低い︕うっかり忘れが発⽣しても気づかなそう • 案B. 中間定義からKotlin, C#のコードを作る • よくわからないオレオレ中間⾔語は誰も書きたくない
  140. C#とKotlinによるロジック共有 • ロジック(計算式など)をKotlinサーバーとUnityで共有したい︕ • 例えば戦闘⼒や経験値算出などをクライアントで計算して表⽰ • 何かを消費するなどして適⽤する場合、サーバー側でもバリデーショ ンとして計算して適⽤する • などなど、同じ計算式が必要な場合はままある

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

    Unity C# 両⽅C#なのでコードを共有する
  142. 案C. C#内部ロジックサービスとの通信 Kubernetes Pod Kotlin API Server C# Internal Service

    Unity C# Kubernetesの同⼀Pod内にサイドカーと して外部と通信するKotlinサーバーと、内 部のみのC#によるサービスを⽴ち上げ、 gRPC(Unix Domain Socket)で通信して 計算結果を取得
  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が取れる
  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を元にテンプレートに当ては める情報を⽣成(⾃前実装)
  145. MagicOnionならC#で書いたコードが 全てそのまま無条件でサーバーとク ライアントで共有できてハッピー︕

  146. 2019/9/4 Unity C#と.NET Core C#のコード共有いろは 3

  147. コード共有 • 最も分かりやすいクライアント/サーバーの⾔語統⼀の利点 • メリットを最⼤限に⽢受できるプロジェクト構成にする ⼀つのソリューションファイルで サーバーのcsprojもクライアントの csprojもホストする これによりIDEが両プロジェクトを認識して ・リファクタリングが統⼀的に効く

    ・参照のジャンプなどが効く ・デバッグ実⾏でサーバー/クライアントが繋がる
  148. vs Unity .csproj • UnityはUnityの管理下のソースコードのみ参照可能 • サーバーのcsprojは柔軟に記述できる • よって共有するコードの実体をUnityのほうに置いて、サー バー側ではコードリンクで参照する

    <ItemGroup> <Compile Include="..¥Unity¥Assets¥Scripts¥Shared¥**¥*.cs" /> </ItemGroup> シンボリックリンクとか、ビルドしてマ ネージドDLLを配置とかやるとトラブルの 元なので、シンプルな⼿段が⼀番 シェアするディレクトリはasmdef で切っておくとより良い
  149. Unity Shimsの実装 • Unity依存コードの除去 • 理想的には完全除去が望ましいが、特に開発中でUnity側主導でコード ができあがっている場合、残ってしまっていることが多い • ダミーの型を⽤意して回避するのが⼿っ取り早くは楽 namespace

    UnityEngine { public class ScriptableObject {} public class MonoBehaviour {} public sealed class SerializeFieldAttribute : Attribute { } // etc... } 意外と問題なく動いたり動かなかったり。 とりあえずコンパイル通す→動かして問題出た とこを何とかする、で⼯数的にそんな多くかか らず何とかなる、ことも少なくないかな、と。
  150. 2019/9/4 MagicOnionによるリアルタイムサーバー実装 4

  151. サーバーロジックを書くということ • MagicOnionはサーバーにロジックを書くためのフレームワーク • データを右から左に流すだけのものではないし、サーバーを「透 明にしない」ことをポリシーにしている • ゲームを⾯⽩くするには、クライアントだけが主でもサーバーだ けが主でもない、両⽅が協調して、乗せるべきコードを乗せるべ き場所に適切に書いていくことが⼤事

    • MagicOnionはそれを最もやりやすくするための選択(の⼀つ) • C#による統⼀はサーバー/クライアント間の壁を透明にするための選択
  152. イベント型とサーバーループ サーバーがクライアントからのリクエス トを受け取ったら

  153. イベント型とサーバーループ なにか処理をしたうえで(あるいは何も 処理をせずに)接続している各クライア ントに配信するスタイル

  154. イベント型とサーバーループ サーバーがクライアントからのリクエスト を受け取ったらキューにコマンドを蓄積

  155. イベント型とサーバーループ 処理した結果を各クライアントに配信 ⼤量クライアントのコマンドも、サーバー で適正に処理してまとめて流すことで帯域 爆発などが防ぎやすい

  156. C#アプリクライアント Unityクライアント(1)とC#クライアント (3)などの構成で、より開発しやすく、負 荷テストなども作りやすく

  157. 2019/9/4 まとめ 5

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

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