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 full-size slide

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

    View full-size slide

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

    View full-size slide

  4. ⾃⼰紹介

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  16. Protocol Buffersについて

    View full-size slide

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

    View full-size slide

  18. サンプル

    View full-size slide

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


    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  23. コンパイルイメージ

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  26. パフォーマンス

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  31. 検証結果

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  34. gRPCの導⼊を決める

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  46. まとめると

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  51. 3-①UnityのgRPC通信

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  54. 検証から始める

    View full-size slide

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

    View full-size slide

  56. クライアントコードの実装
    ①接続の設定
    ②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 full-size slide

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

    View full-size slide

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

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  61. SSL対応について

    View full-size slide

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

    View full-size slide

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


    View full-size slide

  64. まとめると

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  69. 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 full-size slide

  70. コードジェネレータの設定
    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 full-size slide

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

    View full-size slide

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

    View full-size slide

  73. 実⾏するコードの実装
    ①⾃動⽣成の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 full-size slide

  74. まとめると

    View full-size slide

  75. サーバーサイド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 full-size slide

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

    View full-size slide

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

    View full-size slide

  78. 構成図(超簡易版)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  85. Gatlingを採⽤

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

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

    View full-size slide

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

    View full-size slide

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

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  110. 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 full-size slide

  111. 問題が発⽣

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  122. 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 full-size slide

  123. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  136. 案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 full-size slide

  137. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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



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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  150. 2019/9/4
    まとめ
    5

    View full-size slide

  151. 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 full-size slide

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

    View full-size slide

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

    View full-size slide