view Slide/master-slide.md @ 56:01087d212c1a

slide update
author riono <e165729@ie.u-ryukyu.ac.jp>
date Mon, 14 Feb 2022 17:25:33 +0900
parents eb6cedaa62cc
children 309de2ffb2bd
line wrap: on
line source

title: 継続を使用する並列分散フレームワークのUnity実装
author: Ryo Yasuda, Shinji Kono
profile: 並列信頼研
lang: Japanese
code-engine: coderay

### 本研究における成果
* 並列分散フレームワークChristieをUnityで動作可能に
    * Unity上でp2pを基礎としたオンラインゲームの開発が可能に

* Christie Sharpと既存のUnityの通信フレームワークとの機能的な比較を行った
    * Christie Sharpの利点
        * 単体でも並列ライブラリとして機能する
        * 通信切断が起こった場合でもゲームロジックが停止しない

---

### 概要
* オンラインゲームにおける通信にはクライアントサーバ方式が主流
	* データの共有はサーバを経由するため低速
* 当研究室で開発を行っているChristie の分散計算を使用することで、高速に通信できると考えた
* Christie をUnity で使用するためにC# で書き換えを行った
* Christie SharpとUnity既存の通信フレームワークとの機能的な比較を行った

---

### オンラインゲームにおけるデータ通信
* オンラインゲームは複数のプレイヤーが関与する分散プログラム
	* 分散プログラムを正しく書くことは難しい
        * ネットワークの変化、故障、性能の多様性を吸収する
        * スケーラビリティー リソースの追加のみでサービスの質の直感的に維持できる性能基準を備える
	* Debugも困難な場合が多い
* クライアントの負荷軽減やチート対策のため、クライアントサーバ方式が主流
	* データの同期にはサーバを経由するためp2pに比べ低速

---

### オンラインゲームにおけるデータ通信
* 当研究室では並列分散通信フレームワークChristie を開発中である
	* 型のあるDataGear とKey を持つタプル空間、DataGearManager として格納している
	* 他のノードはDGM のproxyを持っており、proxy に書き込むことで通信を実現している
	* DGM はトポロジーマネージャーによって自動的に構築される
		* プログラム自体はDGM の名前を知っていれば良い
		* 他のノードのIP addressなどを知る必要はない
* ネットワークが切断されてもゲームは継続可能  
* 本研究ではChristieをC#で再実装を行い、Unityの通信ライブラリとして使用可能にする
* 既存のUnityの通信ライブラリとの機能面での比較を行い、Christie Sharpのオンラインゲーム向けの通信ライブラリとしての考察を行う

---

### Christie の基礎概念
* Christie は当研究室で開発をしている並列分散通信フレームワークである
	* 同じく当研究室で開発しているGearsOS に導入予定のため次のような概念を持っている

	
* CodeGear  (クラスやスレッド)
* DataGear  (変数データ)
* CodeGearManager  (CG,DG,DGMを管理)
* DataGearManager  (DGを管理,localとremoteの2種類がある, put操作によりDGを格納)  

<center><img src="../Paper/images/GearsRelationships.pdf" alt="message" width="550" height="350" /></center>
<center>
各Gearの関係性
</center>

---

### Christie の基礎概念
* 全てのCGM はThreadPool と他のCGM をList として共有している
* ThreadPool はCPU に合わせた並列度でqueue に入ったThread を逐次実行していく
	* 1つのThreadPool で処理を行うことでCPU のコア数に適したThread を管理でき、並列度を下げ流ことを防ぐ
* ThreadPoolを共有することメタレベルで全てのCG/DG にアクセス可能 
<center><img src="../Paper/images/ChristieClass.pdf" alt="message" width="600" height="450"></center>
<center>
Christie を同一プロセスで複数インスタンス立ち上げた際の接続の構造図
</center>


---

### Christie の基礎概念 annotationについて
DG を取り出すためにCG内に宣言した変数データにannotation をつける。annotationには以下の4つがある。

* Take
	* 先頭のDG を読み込み、そのDG を削除する
	* DG が複数ある場合Take を使用する
* Peek
	* 先頭のDG を読み込むがDG を削除しない
	* 操作をしない場合は同じデータを参照し続ける
* TakeFrom
	* Take と同じ動作だが、remote 先のDGMを指定できる
* PeekFrom
	* Peek と同じ動作だが、remote 先のDGMを指定できる

---

### Topology Manager
* Christie 上でNetwork Topology を形成する
	* 参加を表明したノードに名前を与える
	* 必要があればノード同士の配線を自動で行う

* 静的Topology と動的Topology 2種類がある

---

### Topology Manager 静的Topology
* 静的Topology は以下のようなdot ファイルを与えることでNode の関係を構築できる
* それぞれのNode への通信にはIP address などは使用せずright というlabel を使用することで接続できる

<center>dot 形式による node 間接続の記述</center>
```ring.dot
digraph test {
	node0 -> node1 [label="right"]
	node1 -> node2 [label="right"]
	node2 -> node0 [label="right"]
}
```

<center><img src="../Paper/images/ring.pdf" alt="message" width="480" height="300"></center>
<center>
ring状の接続
</center>


---

### Topology Manager 静的Topology
* 動的Topologyは以下の図のように自動的に接続される
* 現在はTree型の接続のみに対応している

<center><img src="../Paper/images/DynamicTopology.pdf" alt="message" width="1000" height="450" /></center>
<center>動的Topologyの接続手順</center>


---

### Christie のコード例
* コンストラクタで与えた数字をインクリメントし、10回printする例題
``` java:StartCountup.java
public class StartCountup extends StartCodeGear {
    public StartCountup(CodeGearManager cgm) { super(cgm); }

    public static void main(String args[]) {
        StartCountup start = StartCountup(createCGM(10001));
    }
    
    @Override
    protected void run(CodeGearManager cgm) {
        cgm.setup(new CountUpper());
        CountObject count = new CountObject(1);
        put("count", count);
    }
}
```
```java:CountUpper.java
public class CountUpper extends CodeGear {
    @Take
    public CountObject count;

    @Override
    protected void run(CodeGearManager cgm) {
        System.out.println(count);
        
        if (count < 10) {
            cgm.setup(new CountUpper());
            count.number += 1;
            put("count", count);
        } else {
            cgm.getLocalDGM().finish();
        }
    }
}

```
```java:CountObject.java
@Message
public class CountObject {
    public int number;

    public CountObject(int number) {
        this.number = number;
    }
}
```

---

### Christie のコード例

シーケンス図を使った説明


---

### C\#への書き換えの意義
* ChristieはJavaで実装されている
    * C\#に書き換えを行うべきか

* Unityはandroidの開発向けにjarファイルからJavaのメソッドをC\#上で呼び出せる機能がある
    * stringを使用してリソースディレクトリから検索し、使用
    * 高速化が求められる並列分散プログラミングには不適
* JavaとC\#の2つの管理が必要になる
    * C\#とJavaは記述方法が非常に似ており、APIもほとんど同様な機能が実装されている
    * リポジトリをネスト化して対応


### 書き換えの方針
* C\#で記述するChristieをChristie Sharpとする
* Christie設計時の意図や、互換性を保つためChristieと同じ動作をさせる
* ChristieはJava9から開発されており、非推奨なコードやが含まれている
* C\#に対応しつつ、処理動作の向上や最適化を行うために以下の変更を行った
    * MessagePackのバージョンアップ
    * ThreadPoolからTaskへ変更

### Java からの変更点
* Java とC# は基本的に書き方は変わらない


```java:ex.java
Java
public class StartCountup extends StartCodeGear { }

@Override
protected void run(CodeGearManager cgm) { }

@Take String countObject;
```

```cs:ex.cs
C#
public class StartCountup : StartCodeGear { }

public override void Run(CodeGearManager cgm) { }

[Take] string countObject;
```

---

### Christie Sharp のコード例
```cs:StartCountUp.cs
public class StartCountUp : StartCodeGear {
    public StartCountUp(CodeGearManager cgm) : base(cgm) { }

    public static void Main(string[] args) {
        StartCountUp start = StartCountUp(createCGM(10001));
    }

    public override void Run(CodeGearManager cgm) {
        cgm.Setup(new CountUpper());
        CountObject count = new CountObject(1);
        put("count", count);
    }
}
```

```cs:CountUpper.cs
public class CountUpper : CodeGear {
    [Take] public CountObject count;

    public override void Run(CodeGearManager cgm) {
        Console.WriteLine(count.number);

        if (count.number < 10) {
            cgm.Setup(new CountUpper());
            count.number += 1;
            put("count", count);
        } else {
            cgm.GetLocalDGM().Finish();
        }
    }
}
```

```cs:CountObject.cs
[MessagePackObject]
public class CountObject {
    public int number;

    public CountObject(int number) {
        this.number = number;
    }
}
```
<!--
1. Main関数でCGM のインスタンス生成
2. 2つのCG をsetupして待ち状態にする
3. key:hellowWorld data:"hello" がTake される
4. 変数が揃ったためStartHelloWorld のRun が実行される
5. "hello" がprintされ、再び待ち状態になる。 key:hellow data:"hello"がput される
6. key:hellowWorld data:"world" がTake され、4,5と同様に処理される
7. 変数hello とworld がput され揃ったため、FinishHelloWorld のRun が実行され、プログラムは終了する

-->

---

### Take annotationの実装
* Christie ではDGを取得するためにannotation を使用している
	* C# ではannotation と同様の機能にattribute があり、Take をattribute で実装した

* Take はフィールド変数に対して適用する
	
	
```java:Take.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Take { }
```

```cs:Take.cs
[AttributeUsage(AttributeTargets.Field)]
public class Take : Attribute { }
```

---

### MessagePackの相違点 
* Christie ではMessagePack を使用してデータを圧縮し送受信している
	* インスタンス内のpublic 変数に対して圧縮可能
* バージョンが古いため、現在はサポートされていない
	* そのため、最新版とは記述方法が異なる

* 圧縮するクラスには@Message annotatoinをつける
* MessagePack インスタンスを作成後、write、read することでデータの圧縮解凍が可能
	* 圧縮されたデータはbyte[] 型になる

```java:MessagePackEx.java
public class MessagePackExample {
    @Message
    public static class MyMessage {
        public String name;
        public double version;
    }
 
    public static void main(String[] args) throws Exception {
        MyMessage src = new MyMessage();
        src.name = "msgpack";
        src.version = 0.6;
 
        MessagePack msgpack = new MessagePack();
        // Serialize
        byte[] bytes = msgpack.write(src);
        // Deserialize
        MyMessage dst = msgpack.read(bytes, MyMessage.class);
    }
}
```

---

### MessagePackの相違点 
* C# のMessagePack は複数存在している
	* java 版と似たような書き方をするMessagePack-CSharp を選択した

* 圧縮を行いたいクラスに対してMessagePackObject attribute を付ける
* 圧縮する変数に対してkey を設定できる
	* 解凍時にjson として展開できる
* データの圧縮にはMessagePackSerializer.Serialize 関数を用い、byte[] に圧縮される
* データの解凍にはMessagePackSerializer.Deserialize 関数を使用する
	* Deserialize 関数はジェネリスク関数であるため<>内に解凍するデータの型情報を記述する

```cs.MessagePackEx.cs
[MessagePackObject]
public class MyClass {
    [Key(0)]
    public int Age { get; set; }
    [Key(1)]
    public string FirstName { get; set; }
    [Key(2)]
    public string LastName { get; set; }

    static void Main(string[] args) {
        var mc = new MyClass {
            Age = 99,
            FirstName = "hoge",
            LastName = "huga",
        };

        byte[] bytes = MessagePackSerializer.Serialize(mc);
        MyClass mc2 = MessagePackSerializer.Deserialize<MyClass>(bytes);

        // [99,"hoge","huga"]
        var json = MessagePackSerializer.ConvertToJson(bytes);
        Console.WriteLine(json);
    }
}
```

---

### ThreadPoolからTaskへの書き換え
* Christie ではThreadPool を使用していた
	* Christie # ではThreadPoolより高機能なTask を用いて書き換えを行った

* Task は複雑な非同期処理を通常のコーディングと同じ感覚で直感的に記述できる
* 裏でThreadPool が動くようになっている
	* 大きく動作は変わらない

---

### ThreadPoolからTaskへの書き換え

```java:PriorityThreadPoolExecutors.java
public class PriorityThreadPoolExecutors {

    private static class PriorityThreadPoolExecutor extends ThreadPoolExecutor {
        private static final int DEFAULT_PRIORITY = 0;
        private static AtomicLong instanceCounter = new AtomicLong();

        public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
                                int keepAliveTime, TimeUnit unit) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, (BlockingQueue) new PriorityBlockingQueue<ComparableTask>(10,
		ComparableTask.comparatorByPriorityAndSequentialOrder()));
        }

        @Override
        public void execute(Runnable command) {
                super.execute(command);
        }
    }
}
```



```cs:ThreadPoolExecuters.cs
public class ThreadPoolExecutors {
    
    public ThreadPoolExecutors(int nWorkerThreads, int nIOThreads) {
        ThreadPool.SetMinThreads(nWorkerThreads, nIOThreads);
    }
    
    public void Execute(CodeGearExecutor command) {
        Task.Factory.StartNew(() => command.Run());
    }
}
```

---


### Unity
* UnityはUnity Technologies が開発を行っているゲームエンジンである
	* 世界中で使用されているゲームエンジン
	* 非常に軽く、スペックが低いノートPCでもゲーム開発が可能
* プログラミング言語にはC# が採用されている
	* C# のAPI やUnity 向けに拡張されたAPIも使用可能
	* 開発した機能をUnity に組み込むことも可能

---

### Christie \# on Unityのコード例
```cs:UnityStartHelloWorld.cs
public class StartHelloWorld : StartCodeGear {
    
    public StartHelloWorld(CodeGearManager cgm) : base(cgm) { }

    public  void RunCodeGear(CodeGearManager cgm) {
        cgm.Setup(new HelloWorldCodeGear());
        cgm.Setup(new FinishHelloWorld());
        cgm.GetLocalDGM().Put("helloWorld", "hello");
        cgm.GetLocalDGM().Put("helloWorld", "world");
    }
}
```

```cs:UnityHelloWorld.cs
public class HelloWorld : MonoBehaviour {
    void Start() {
        CodeGearManager cgm = StartCodeGear.CreateCgm(10000);
        var helloWorld = new StartHelloWorld(cgm);
        helloWorld.RunCodeGear(cgm);
    }
}
```
* HelloWorldCodeGearと、FinishHelloWorld はそのまま使用
* StartHelloWorld をUnity で使用できるように書き換え
	* Unity ではMonoBehaviour 継承したクラスが動作可能
	* ゲーム開始時に1度だけ呼ばれるStart 関数
	* Start 関数でCGM のインスタンスを生成
	* Main 関数を名前を変えたRunCodeGear 関数を実行

---


### Unityで使用されているライブラリとの比較
Unityで使用されている既存のライブラリとして、Photon Unity Networking 2(PUN2)、MLAPIと、Christie # の比較を行う。

| | Christie # | PUN2 | MLAPI |
|--- |--- | --- |---|
|通信方式 |p2p |クライアントサーバ方式 | クライアントサーバ方式|
|プロトコル | TCP | TCP | TCP |
|特徴 |通信のためのIP address がプログラム直接記述されていない |Photon Cloud でサーバを自前で用意する必要がない | Unity公式でサポートされている  RPC が使用可能| 

---

<!--
### チート対策について
* オンラインゲームにおいてチート対策は必須
* 通常のオンラインゲームでのチート対策
	* クライアントをモニタリングする
	* ダメージ計算などは全てサーバで行う
	* ユーザからの通報

* Christie では型があるDataGear をkey と合わせてDGMに格納する方式を取っている
	* 他のノードとの通信にはDGM のporxy に書き込むことで可能
	* DGM の構成にはTopology Manager が自動的に構成する
* Topology Manager を使用することでクライアントは接続先を直接知る必要がない
	* IP address などチートに使用される情報をプログラムに含めることなく通信可能

<center><img src="https://i.imgur.com/L8GVFdL.png" alt="message" width="450" height="260"></center>
<center>label を使用したデータ通信</center>

-->

### 実装の現状
* Local DGMを使用してUnity 上でデータ通信を行うことができている
* Scketo とMessagePack を用いた通信に関しては、書き換え途中
	* 独自クラスをMessagePack でserialize できない
* 今後の予定
	* Christie で実装されている例題
	* Alice からChristie に書き換えた際に取り除かれた機能の洗い出しを行う
	* Unity でChristie #の検証として100人規模のFPS の作成 

---

### まとめ
* Christie をUnity で使用するためにC# に書き換えを行った
* 書き換え方針としては、attribute やMessagePack などC# 独自の機能に対応しつつ元のソースコードと同一になるようにした
* 実装としては、localDataGearManager を用いた同一プロセスで複数インスタンス立ち上げによる通信が可能
* Remote DataGearManager を使用した複数台の通信については書き換え途中であり、引き続き行っていく
* Christie の検証のためUnity で100人規模のFPS を作成する