Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

⾃⼰紹介

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

特徴

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Protocol Buffersについて

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

サンプル

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

コンパイルイメージ

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

パフォーマンス

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

検証結果

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

gRPCの導⼊を決める

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Kotlinがいいんじゃないか︖

Slide 41

Slide 41 text

なぜいいいと考えたか︖

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

まとめると

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

3-①UnityのgRPC通信

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

検証から始める

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

クライアントコードの実装 ①接続の設定 ②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); ① ② ③

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

SSL対応について

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

まとめると

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

⼿順

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

コードジェネレータの設定 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/" }

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

実⾏するコードの実装 ①⾃動⽣成の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() } } ① ②

Slide 76

Slide 76 text

まとめると

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

構成図(超簡易版)

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

NLBはTLS終端にできない

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

Gatlingを採⽤

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

ダウンロードのフロー

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

デシリアライズの問題

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

問題が発⽣

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

対応

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

⾊々作ってみてのまとめ

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

第1部 終

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

No content

Slide 126

Slide 126 text

No content

Slide 127

Slide 127 text

2019/9/4 融和と統⼀ 1

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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); クライアントもサーバーも⾃ 然に繋がっているように⾒え る(デバッガもサーバー/クラ イアント共有でステップ実⾏ で繋がって動いていく)

Slide 130

Slide 130 text

No content

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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を元にテンプレートに当ては める情報を⽣成(⾃前実装)

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

vs Unity .csproj • UnityはUnityの管理下のソースコードのみ参照可能 • サーバーのcsprojは柔軟に記述できる • よって共有するコードの実体をUnityのほうに置いて、サー バー側ではコードリンクで参照する シンボリックリンクとか、ビルドしてマ ネージドDLLを配置とかやるとトラブルの 元なので、シンプルな⼿段が⼀番 シェアするディレクトリはasmdef で切っておくとより良い

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

2019/9/4 まとめ 5

Slide 158

Slide 158 text

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)

Slide 159

Slide 159 text

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

Slide 160

Slide 160 text

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