Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Takehata Naoto
September 04, 2019

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

Takehata Naoto

September 04, 2019
Tweet

More Decks by Takehata Naoto

Other Decks in Programming

Transcript

  1. Unity C# × gRPC × サーバーサイドKotlinによる
    次世代のサーバー/クライアント通信
    〜ハイパフォーマンスな通信基盤の開発と
    MagicOnionによるリアルタイム通信の実現〜
    株式会社Cysharp 代表取締役 河合 宜⽂
    株式会社アプリボット チーフエンジニア ⽵端 尚⼈
    2019/9/4 CEDEC 2019

    View Slide

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

    View Slide

  3. 第1部
    Unity C# × gRPC × サーバーサイドKotlinによる
    次世代サーバー/クライアント通信の開発
    株式会社アプリボット チーフエンジニア
    ⽵端 尚⼈

    View Slide

  4. ⾃⼰紹介

    View Slide

  5. 概要
    ⽵端 尚⼈
    職種:バックエンドエンジニア
    好きな⾔語:Kotlin
    Twitter:@n_takehata
    2006.04 公務員
    2007.12 SES
    2011.01 サイバーエージェント
    2014.04 アプリボット
    現在開発中の
    SEVEN′s CODE(セブンスコード)
    のサーバーサイドエンジニア

    View Slide

  6. 登壇、執筆など
    • CEDEC 2018登壇
    https://speakerdeck.com/n_takehata/cedec-
    2018
    • Kotlin Fest 2018登壇(LT)
    • 雑誌Software Design 2019年2〜4⽉号に
    てサーバーサイドKotlinについての短期連
    載執筆

    View Slide

  7. 会社紹介
    株式会社アプリボット
    ■事業内容
    • スマートフォン向けゲームアプリの企画、制作、運⽤
    • 2010年7⽉サイバーエージェント⼦会社として設⽴
    ■公式HP
    https://www.applibot.co.jp/
    ■技術ブログ
    https://blog.applibot.co.jp/

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. 特徴

    View Slide

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

    View Slide

  17. Protocol Buffersについて

    View Slide

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

    View Slide

  19. サンプル

    View Slide

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


    View Slide

  21. 使いたい⾔語を指定してコンパイル

    View Slide

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

    View Slide

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

    View Slide

  24. コンパイルイメージ

    View Slide

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

    View Slide

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

    View Slide

  27. パフォーマンス

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. 検証結果

    View Slide

  33. 平均リクエスト時間
    (ms)
    平均レスポンス時間
    (ms)
    gRPC 2.92 3.6
    REST 7.16 12.78
    ※2018年3⽉当時の検証結果です
    リクエストが約2倍、レスポンスが約4倍速かった

    View Slide

  34. • 通信速度の向上によりUXの改善につながる
    • データダウンロードなど、膨⼤なデータをダウンロードする際
    には特に絶⼤な効果が期待できる
    ※ただし、条件あり(後で話します)

    View Slide

  35. gRPCの導⼊を決める

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. モダンな開発ができる
    • コードがシンプル
    • 型推論
    • String Template
    • プロパティ
    • データクラス
    • etc…
    • 安全
    • Null安全
    • val、var

    View Slide

  46. 将来性も期待できる
    • Androidの公式の開発⾔語として採⽤された
    • Spring5.0のKotlinサポート

    View Slide

  47. まとめると

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. 3-①UnityのgRPC通信

    View Slide

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

    View Slide

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

    View Slide

  55. 検証から始める

    View Slide

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

    View Slide

  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);



    View Slide

  58. 実機ではそのままでは動かない

    View Slide

  59. 検証当時(2018年夏)の対応
    • いくつかの静的ライブラリを⾃前でmakeして作る必要があっ

    • 現在は公式がネイティブライブラリを⽤意してくれているので、
    そちらを使える
    参考 : https://blog.applibot.co.jp/2018/10/31/grpc-in-unity/

    View Slide

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

    View Slide

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

    View Slide

  62. SSL対応について

    View Slide

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

    View Slide

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


    View Slide

  65. まとめると

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  70. ⼿順

    View Slide

  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

    View Slide

  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/"
    }

    View Slide

  73. コンパイルタスクの実⾏
    generateProtoタスクがprotocを実⾏
    ./gradlew generateProto

    View Slide

  74. ファイルが⽣成される
    • xxxOuterClass.java
    • service、messageで定義したクラスを含むクラス
    • シリアライズ、デシリアライズなどをやってくれる
    • xxxGrpc.java
    • xxxOuterClassを使⽤してgRPCで通信するサーバー処理やクライアン
    トStubなどを含んだクラス

    View Slide

  75. 実⾏するコードの実装
    ①⾃動⽣成のBaseクラスを継承
    ②gRPCのサービスとして起動時に読み込ませるアノテーション
    @GRpcService
    class GreeterService: GreeterGrpc.GreeterImplBase() {
    override fun sayHello(request: HelloRequest, responseObserver: StreamObserver) {
    val replyBuilder = HelloReply.newBuilder().setMessage("Hello " + request.name)
    responseObserver.onNext(replyBuilder.build())
    responseObserver.onCompleted()
    }
    }


    View Slide

  76. まとめると

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  80. 構成図(超簡易版)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  87. Gatlingを採⽤

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  96. ゲーム起動時のデータダウンロード機構
    • 対象テーブル取得API、テーブルデータのダウンロードAPIを⽤

    • クライアント側は対象テーブル取得APIで取得したテーブルの
    分、取得APIを実⾏する

    View Slide

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

    View Slide

  98. ゲーム起動時のデータダウンロード機構
    • 対象テーブル取得API、テーブルデータのダウンロードAPIを⽤

    • クライアント側はテーブル名取得APIで取得したテーブルの分、
    取得APIを実⾏する
    • レスポンスはJSONでシリアライズした⽂字列で返却する

    View Slide

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

    View Slide

  100. パフォーマンス検証の続報
    形式 10 100 300 500
    gRPC 17 46 113 177
    REST 73 90 132 158
    ※1⾏⽬はデータ容量(Byte)

    View Slide

  101. 容量が増えるとRESTの⽅が早くなる

    View Slide

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

    View Slide

  103. デシリアライズはJSONの⽅が早い

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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}...]}}

    View Slide

  113. 問題が発⽣

    View Slide

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

    View Slide

  115. 対応

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  119. Bodyの容量は⼀応変更できる
    new Channel("localhost", ChannelCredentials.Insecure, new []
    {
    new ChannelOption(ChannelOptions.MaxReceiveMessageLength, 10000000),
    new ChannelOption(ChannelOptions.MaxSendMessageLength, 10000000)
    });
    本来の思想にも反するのであまり・・・

    View Slide

  120. ⾊々作ってみてのまとめ

    View Slide

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

    View Slide

  122. 第1部

    View Slide

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

    View Slide

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

    View Slide

  125. View Slide

  126. View Slide

  127. 2019/9/4
    融和と統⼀
    1

    View Slide

  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#のためのエンジンとして⾼い品質を誇る

    View Slide

  129. public class TestService : ITestService
    {
    // パブリックメソッドがそのままgRPC定義
    public async UnaryResult Sum(int x, int y)
    {
    // async/awaitにも⾃然に対応
    // マジカル技術によりasync Taskじゃなくても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(channel);
    var result = await client.Sum(100, 200);
    クライアントもサーバーも⾃
    然に繋がっているように⾒え
    る(デバッガもサーバー/クラ
    イアント共有でステップ実⾏
    で繋がって動いていく)

    View Slide

  130. View Slide

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

    View Slide

  132. C#がIDLなら
    C# as a Schema
    クライアント/サーバーの実装⾔語を
    共にC#に固定することで、
    通信スキーマそのものをC#で表現する
    プロジェクト参照で済むので
    IDLのバージョン管理が不要というのも運⽤上メリット

    View Slide

  133. しかしいきなりC#は投げ込めない
    • C#⼤統⼀理論
    • そんな過激派な意⾒がいきなり通じるわけがない
    • 良いゲームを作るための開発であってC#を使うための開発
    ではない
    • ↑私はC#を使うための(げふんげふん)
    • 各社それぞれの都合に合わせた融和のための施策から始める
    • そしてあわよくば(げふんげふん)

    View Slide

  134. ざっくり通信系の整理
    Microser
    vices
    Realtime
    Server
    Unity
    API
    Server

    View Slide

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

    View Slide

  136. 現実解
    Microser
    vices
    Realtime
    Server
    Unity
    API
    Server
    API Serverは各社の得意な⾔語
    (Ruby, PHP, Java, Go, Kotlin, etc…)
    リアルタイムサーバーはMagicOnion
    使うでいいんじゃないか

    View Slide

  137. Agenda
    • C#(Unity)と他⾔語(今回はKotlin)の付き合い⽅
    • C#(Unity) to C#(.NET Core)のコード共有
    • MagicOnionによるリアルタイムサーバー実装
    • まとめ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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が取れる

    View Slide

  144. class Program : BatchBase
    {
    static async Task Main(string[] args)
    {
    await BatchHost.CreateDefaultBuilder().RunBatchEngineAsync(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を元にテンプレートに当ては
    める情報を⽣成(⾃前実装)

    View Slide

  145. MagicOnionならC#で書いたコードが
    全てそのまま無条件でサーバーとク
    ライアントで共有できてハッピー︕

    View Slide

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

    View Slide

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

    View Slide

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



    シンボリックリンクとか、ビルドしてマ
    ネージドDLLを配置とかやるとトラブルの
    元なので、シンプルな⼿段が⼀番
    シェアするディレクトリはasmdef
    で切っておくとより良い

    View Slide

  149. Unity Shimsの実装
    • Unity依存コードの除去
    • 理想的には完全除去が望ましいが、特に開発中でUnity側主導でコード
    ができあがっている場合、残ってしまっていることが多い
    • ダミーの型を⽤意して回避するのが⼿っ取り早くは楽
    namespace UnityEngine
    {
    public class ScriptableObject {}
    public class MonoBehaviour {}
    public sealed class SerializeFieldAttribute : Attribute
    {
    }
    // etc...
    }
    意外と問題なく動いたり動かなかったり。
    とりあえずコンパイル通す→動かして問題出た
    とこを何とかする、で⼯数的にそんな多くかか
    らず何とかなる、ことも少なくないかな、と。

    View Slide

  150. 2019/9/4
    MagicOnionによるリアルタイムサーバー実装
    4

    View Slide

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

    View Slide

  152. イベント型とサーバーループ
    サーバーがクライアントからのリクエス
    トを受け取ったら

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  157. 2019/9/4
    まとめ
    5

    View Slide

  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)

    View Slide

  159. 未来を構築する
    • 新しい時代の新しいフレームワーク
    • 今やゲームにおいてリアルタイム通信はほぼ必須
    • 5Gも迫っていてフレームワークも変化しなければいけないタイミング
    • ならばこそ、より⼤きな未来を描いていきたい
    • 完全統合形フレームワークという⼀つの理想
    • クライアントとサーバーを
    • APIとリアルタイムを
    • 全てをC#で統合するという夢想を具現化するのがMagicOnion
    • 理想を理想のままにせず、現実の結果に残すためにCysharpはOSSに
    よる公開から、開発サポートまで様々なことをしていきます︕

    View Slide

  160. ご清聴ありがとうございました

    View Slide