view Slide/master-slide.pdf.html @ 62:74fb935dc5b5 default tip

update
author riono <e165729@ie.u-ryukyu.ac.jp>
date Wed, 02 Mar 2022 13:15:50 +0900
parents
children
line wrap: on
line source






<!DOCTYPE html>
<html>
<head>
   <meta http-equiv="content-type" content="text/html;charset=utf-8">
   <title>継続を使用する並列分散フレームワークのUnity実装</title>

   <meta name="generator" content="Slide Show (S9) v4.1.0 on Ruby 2.6.3 (2019-04-16) [universal.x86_64-darwin19]">
   <meta name="author"    content="Ryo Yasuda, Shinji Kono" >

<!-- style sheet links -->
<link rel="stylesheet" href="s6/themes/screen.css"       media="screen">
<link rel="stylesheet" href="s6/themes/print.css"        media="print">
<link rel="stylesheet" href="s6/themes/blank.css"        media="screen,projection">

<!-- JS -->
<script src="s6/js/jquery-1.11.3.min.js"></script>
<script src="s6/js/jquery.slideshow.js"></script>
<script src="s6/js/jquery.slideshow.counter.js"></script>
<script src="s6/js/jquery.slideshow.controls.js"></script>
<script src="s6/js/jquery.slideshow.footer.js"></script>
<script src="s6/js/jquery.slideshow.autoplay.js"></script>

<!-- prettify -->
<link rel="stylesheet" href="scripts/prettify.css">
<script src="scripts/prettify.js"></script>

<style>
  .slide {page-break-after: always;}
</style>




</head>
<body>

<div class="layout">
  <div id="header"></div>
  <div id="footer">
    <div align="right">
      <img src="s6/images/logo.svg" width="200px">
    </div>
  </div>
</div>

<div class="presentation">

  <div class='slide cover'>
    <table width="90%" height="90%" border="0" align="center">
      <tr>
        <td>
          <div align="center">
              <h1><font color="#808db5">継続を使用する並列分散フレームワークのUnity実装</font></h1>
          </div>
        </td>
      </tr>
      <tr>
        <td>
          <div align="left">
               Ryo Yasuda, Shinji Kono
               並列信頼研
            <hr style="color:#ffcc00;background-color:#ffcc00;text-align:left;border:none;width:100%;height:0.2em;">
          </div>
        </td>
      </tr>
    </table>
  </div>


<div class='slide'>
  
<!-- _S9SLIDE_ -->
<h2 id="並列分散フレームワークchristieとunity">並列分散フレームワークChristieとUnity</h2>
<ul>
  <li>Unityとはゲーム開発でよく使用されているゲームエンジン</li>
  <li>ChristieとはDataGearのTake/Putを使用した分散フレームワーク</li>
  <li>UnityでServer抜きのネットワークゲーム開発を行いたい
    <ul>
      <li>Java版を接続するのは困難</li>
      <li>C#でChristieを実装した</li>
    </ul>
  </li>
  <li>Unityに並列分散処理をスムーズに導入できた</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="christieによるunityネットワークゲーム実装の利点">ChristieによるUnityネットワークゲーム実装の利点</h2>
<ul>
  <li>PUN2では企業ベースのServerが必要(開発がしづらい)</li>
  <li>WAN上なので低速
    <ul>
      <li>ChristieにするとLAN上での高速通信が可能</li>
      <li>TopologyManagerを使うことによりLAN/WANで動的構成される</li>
      <li>スケーラビリティーもTopologyManagerで実現できる(分散ルーティング)</li>
      <li>Take/Putなので通信切断や遅延に対して対処しやすい</li>
    </ul>
  </li>
  <li>Unityはフレーム単位の処理なため並列処理が苦手
    <ul>
      <li>Christieにより画面書き換えと非同期な並列処理や通信が可能</li>
    </ul>
  </li>
  <li>ChristieのMessagePack(データ通信ライブラリ)のバージョンの問題を解決</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="christie-の計算モデル">Christie の計算モデル</h2>
<ul>
  <li>DataGear(通信単位となるオブジェクト)が揃った時点でCodeGearが処理を開始</li>
  <li>DataGearは決まったkeyでDataGearManagerにQueueとして格納</li>
  <li>相手nodeのDataGearManagerのproxyで通信</li>
</ul>
<center><img src="../Paper/images/Remote_DataGearManager.pdf" alt="message" width="800" height="450" /></center>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="datagearのapi">DataGearのAPI</h2>
<ul>
  <li>PutでQueueに追加</li>
  <li>TakeでQueueからの取り出し</li>
  <li>PeekでQueueから取り出さない読み出し
    <ul>
      <li>Takeされない限り同じDataGearを読み出し続ける</li>
    </ul>
  </li>
  <li>フレームごとにPeekでゲームオブジェクトの状態をUnity側で見れる</li>
  <li>通信停止/遅延でもUnityの動作に影響を与えない</li>
  <li>スケーラビリティやLAN/WAN構成はTopologyManagerのDGM porxyの管理</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="javacのannotation">Java/C#のannotation</h2>
<ul>
  <li>クラスやフィールドあるいはメソッドに付ける</li>
  <li>ユーザ定義可能なmeta計算属性</li>
  <li>MessagePackで通信可能なフィールドをannotationで指定する</li>
  <li>Christieで通信パターンを指定するのにannotationを用いる</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="dgのannotation">DGのannotation</h2>
<ul>
  <li>Take
    <ul>
      <li>先頭のDG を読み込み、そのDG を削除する</li>
    </ul>
  </li>
  <li>Peek
    <ul>
      <li>先頭のDG を読み込むがDG を削除しない</li>
      <li>操作をしない場合は同じデータを参照し続ける</li>
    </ul>
  </li>
  <li>TakeFrom
    <ul>
      <li>Take と同じ動作だが、remote 先のDGMを指定できる</li>
    </ul>
  </li>
  <li>PeekFrom
    <ul>
      <li>Peek と同じ動作だが、remote 先のDGMを指定できる</li>
    </ul>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="christieによる分散プログラミング">Christieによる分散プログラミング</h2>
<ul>
  <li>Take/Put/Peekがデータベースのトランザクション</li>
  <li>ここではトランザクションはゲームプレイヤー間の合意されたイベント</li>
  <li>複数のプレイヤーが関与する分散プログラムをTake/Put/Peekでローカルに記述できる</li>
  <li>ローカルな記述を組み合わせて分散プログラムを記述する</li>
  <li>ネットワークの変化、故障、性能の多様性を吸収する</li>
  <li>Christieのデバッガー(モデル検査など)も使用可能</li>
  <li>TopologyManagerで負荷軽減やチート対策が可能</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="ローカルnodeから見たchristie">ローカルnodeから見たChristie</h2>
<ul>
  <li>型のあるDataGear とKey を持つタプル空間、DataGearManager として格納</li>
  <li>他のノードのDGM のproxyに書き込むことで通信</li>
  <li>CodeGear  (クラスやスレッド)</li>
  <li>DataGear  (変数データ)</li>
  <li>CodeGearManager  (CG,DG,DGMを管理)</li>
</ul>
<center><img src="../Paper/images/GearsRelationships.pdf" alt="message" width="550" height="350" /></center>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="christie上でnetwork-topologyの形成">Christie上でNetwork Topologyの形成</h2>
<ul>
  <li>参加を表明したノードにDataGearManager proxyを決まった名前で作成</li>
  <li>ノード同士の配線を指示</li>
  <li>分散プログラムの開始終了</li>
  <li>Treeなどの動的Topologyは自動的に接続される</li>
</ul>
<center><img src="../Paper/images/DynamicTopology.pdf" alt="message" width="550" height="350" /></center>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="christie-sharp-のコード例">Christie Sharp のコード例</h2>
<ul>
  <li>Mainでport10001でChristie フレームワークを起動(CreateCGM)</li>
  <li>最初のCodeGearをCodeGearManagerに対して実行する
    <pre><code class="language-cs">public static void Main(string[] args) {
  StartCountUp start = StartCountUp(createCGM(10001));
}
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="codegear-startcountup">CodeGear StartCountUp</h2>
<ul>
  <li>CodeGearはCodeGearManagerを引数として持つ(baseでcgmに対してアクセスを許可)</li>
  <li>CodeGearManagerがRun(cgm)をThreadPoolを使って呼び出す
    <pre><code class="language-cs:StartCountUp.cs">public class StartCountUp : StartCodeGear {
  public StartCountUp(CodeGearManager cgm) : base(cgm) { }
  public override void Run(CodeGearManager cgm) {
      cgm.Setup(new CountUpper());  // 新しいCodeGearを生成
      CountObject count = new CountObject(1);
      put("count", count);  // DataGear countをkey countでPut
  }
}
</code></pre>
    

</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="codegear-countupper">CodeGear CountUpper</h2>
  </li>
  <li>key countのDataGearが来たらCountUpperのRunが実行される
    <pre><code class="language-cs:CountUpper.cs">public class CountUpper : CodeGear {
  [Take] public CountObject count; // key countをTakeするannotation
  public override void Run(CodeGearManager cgm) {
      Console.WriteLine(count.number);
      if (count.number &lt; 10) {
          cgm.Setup(new CountUpper());  // 毎回新しいCodeGearを生成
          count.number += 1;
          put("count", count);
      } else {
          cgm.GetLocalDGM().Finish();  // CodeGearManagerを終了
      }
  }
}
</code></pre>
    

</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="datagearcountobject">DataGear CountObject</h2>
  </li>
  <li>MessagePackを使って通信されるDataGear</li>
  <li>Take/Put/Peekはこれに対して行われる
    <pre><code class="language-cs:CountObject.cs">[MessagePackObject]
public class CountObject {
  public int number;
  public CountObject(int number) {  // コンストラクタ
      this.number = number;
  }
}
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="unityの従来の通信ライブラリ">Unityの従来の通信ライブラリ</h2>
<ul>
  <li>Unityはフレーム単位で処理を行う</li>
  <li>IEnumeratorでフレームを跨ぐ処理を記述する</li>
  <li>Send/Recieveなどでは通信を記述しづらい</li>
  <li>Photon Unity Network 2(PUN2)
    <ul>
      <li>Cloud ServerサービスPhoton Cloudがある</li>
      <li>Server側は開発できない</li>
    </ul>
  </li>
  <li>Mirror
    <ul>
      <li>OSSで開発が行われている</li>
      <li>メソッドをServerで実行し結果を反映</li>
    </ul>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="photon-unity-network2">Photon Unity Network2</h2>
<pre><code class="language-cs">public class PhotonController : MonoBehaviourPunCallbacks {
    void Start() {
        PhotonNetwork.ConnectUsingSettings();
    }
    void FixedUpdate() {
        if (photonView.IsMine) {   // 自身であるか
            float x = Input.GetAxis("Horizontal");
            float z = Input.GetAxis("Vertical");
            CmdMoveSphere(x, z);
        }
    }
    void CmdMoveSphere(float x, float z) {
        Vector3 v = new Vector3(x, 0, z);
        GetComponent&lt;Rigidbody&gt;().AddForce(v);
    }
    public override void OnConnectedToMaster() {  // Server接続時のcallback 
        PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions(), TypedLobby.Default);
    }
    public override void OnJoinedRoom() {    // Room接続時のcallback 
        PhotonNetwork.Instantiate("Charactor", Vector3.zero, Quaternion.identity);
    }
}
</code></pre>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="mirror">Mirror</h2>
<pre><code class="language-cs">public class PlayerController : NetworkBehaviour {
    [SyncVar] float speed = 1f;
    public override void OnStartServer() {  // Server接続時のcallback 
        speed = Random.Range(1f, 10f);
    }
    void FixedUpdate() {
        if (isLocalPlayer) {
            float x = Input.GetAxis("Horizontal");
            float z = Input.GetAxis("Vertical");
            CmdMoveSphere(x, z);
        }
    }
    [Command]     // Server側で実行される
    void CmdMoveSphere(float x, float z) {
        Vector3 v = new Vector3(x + speed, 0, z + speed);
        GetComponent&lt;Rigidbody&gt;().AddForce(v);
    }
}
</code></pre>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="cへの書き換える利点">C#への書き換える利点</h2>
<ul>
  <li>ChristieはJavaで実装されている
    <ul>
      <li>C#に書き換えを行うべきか</li>
    </ul>
  </li>
  <li>JavaをC#上で呼び出す方式
    <ul>
      <li>annotationが使用できない</li>
    </ul>
  </li>
  <li>Unityはandroidの開発向けにjarファイルからJavaのメソッドをC#上で呼び出せる機能がある
    <ul>
      <li>stringを使用してリソースディレクトリから検索し、使用</li>
      <li>高速化が求められる並列分散プログラミングには不適</li>
    </ul>
  </li>
  <li>記述方法が似ているため移植が行いやすい</li>
  <li>ThreadPoolをC#で統一できる</li>
</ul>



</div>

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



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="take-annotationの実装">Take annotationの実装</h2>
<ul>
  <li>Christie ではDGを取得するためにannotation を使用している
    <ul>
      <li>C# ではannotation と同様の機能にattribute があり、Take をattribute で実装した</li>
    </ul>
  </li>
  <li>Take はフィールド変数に対して適用する</li>
  <li>Javaの場合
    <pre><code class="language-java:Take.java">@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Take { }
</code></pre>
  </li>
  <li>C#の場合
    <pre><code class="language-cs:Take.cs">[AttributeUsage(AttributeTargets.Field)]
public class Take : Attribute { }
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="messagepackの相違点">MessagePackの相違点</h2>
<ul>
  <li>Christie ではMessagePack を使用してデータをシリアライズし送受信している
    <ul>
      <li>インスタンス内のpublic 変数に対してシリアライズ可能</li>
    </ul>
  </li>
  <li>Java版はバージョンが古いため、現在はサポートされていないため最新版とは記述方法が異なる
    <pre><code class="language-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();
      byte[] bytes = msgpack.write(src);          // シリアライズ
      // Deserialize
      MyMessage dst = msgpack.read(bytes, MyMessage.class);   // デシリアライズ
}
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="messagepackの相違点-1">MessagePackの相違点</h2>
<ul>
  <li>C# のMessagePack は複数存在している
    <ul>
      <li>java 版と似たような書き方をするMessagePack-CSharp を選択した
        <pre><code class="language-cs">[MessagePackObject]  // シリアライズしたいクラスにつける
public class MyClass {
  [Key(0)]   // keyを指定
  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);    // byte[] にシリアライズ
  MyClass mc2 = MessagePackSerializer.Deserialize&lt;MyClass&gt;(bytes);  // デシリアライズ
  // [99,"hoge","huga"]
  var json = MessagePackSerializer.ConvertToJson(bytes);  // Jsonとしても展開可能
  }
}
</code></pre>
      </li>
    </ul>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="threadpoolからtaskへの書き換え">ThreadPoolからTaskへの書き換え</h2>
<ul>
  <li>Christie ではThreadPool を使用していた
    <pre><code class="language-java:PriorityThreadPoolExecutors.java">public class PriorityThreadPoolExecutors {
  private static class PriorityThreadPoolExecutor extends ThreadPoolExecutor {
      private static final int DEFAULT_PRIORITY = 0;
      public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize,  // コンストラクタ
                              int keepAliveTime, TimeUnit unit) {
          super(corePoolSize, maximumPoolSize, keepAliveTime, unit, 
          (BlockingQueue) new PriorityBlockingQueue&lt;ComparableTask&gt;(10,
      ComparableTask.comparatorByPriorityAndSequentialOrder()));  
      }  // 優先度によって処理順を入れ替え可能なQueueを指定
      @Override
      public void execute(Runnable command) {
              super.execute(command);
      }
  }
}
</code></pre>
    

</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="threadpoolからtaskへの書き換え-1">ThreadPoolからTaskへの書き換え</h2>
  </li>
  <li>Christie Sharp ではThreadPoolより高機能なTask を用いて書き換えを行った</li>
  <li>Task は複雑な非同期処理を通常のコーディングと同じ感覚で直感的に記述</li>
  <li>裏でThreadPool が動作するため大きく動作は変わらない
    <pre><code class="language-cs:ThreadPoolExecuters.cs">public class ThreadPoolExecutors {
  public ThreadPoolExecutors(int nWorkerThreads, int nIOThreads) {  // コンストラクタ
      ThreadPool.SetMinThreads(nWorkerThreads, nIOThreads);
  }
  public void Execute(CodeGearExecutor command) {
      Task.Factory.StartNew(() =&gt; command.Run());  // Taskを使用
  }
}
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="christie-sharpのunityのコード例">Christie SharpのUnityのコード例</h2>
<ul>
  <li>ゲームオブジェクトの座標をもう1つのゲームオブジェクトに追従させる例題</li>
  <li>StartやUpdateはMonoBehaviourを継承する</li>
</ul>

<pre><code class="language-cs:TransformMoveTest.cs">public class TransformMoveTest : MonoBehaviour {
    private CodeGearManager cgm;
    public Transform otherTransform;
    private Vector3 pos;
    void Start() {    // ゲーム実行時の最初の1回呼び出し
        cgm = StartCodeGear.CreateCgm(10000);
        cgm.Setup(new PositionAssignCodeGear());   // 新しいCodeGearの生成
        cgm.GetLocalDGM().Put("transform", transform);
    }
    public void Update() {   // 毎フレーム呼び出し
        pos = otherTransform.position;
        Vector3 newPos = new Vector3(pos.x + 3, pos.y, pos.z + 3);
        cgm.GetLocalDGM().Put("pos", newPos);
    }
    private void LateUpdate() {  // 次のフレーム直前に呼び出し
        cgm.Setup(new PositionAssignCodeGear());
    }
}

</code></pre>


</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="christie-sharpのunityのコード例-1">Christie SharpのUnityのコード例</h2>
<ul>
  <li>TransformなどのUnity AIPはMainThreadでのみ動作
    <pre><code class="language-cs:PositionAssingCodeGear.cs">public class PositionAssignCodeGear : CodeGear {
  [Take] private Vector3 pos;
  [Peek] private Transform transform;
  public override void Run(CodeGearManager cgm) {
      MainThreadDispatcher.Post(_ =&gt; {    // MainThreadへ処理を移譲
          transform.position = pos;
      }, null);
  }
}
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="christie-sharpの利点">Christie Sharpの利点</h2>
<ul>
  <li>Unityのフレーム同期処理とChristieの並列分散処理が共存
    <ul>
      <li>CodeGear/DataGearを利用した待ち合わせ処理</li>
      <li>CodeGearの処理はTaskによってThreadPoolで行われる</li>
      <li>Christieを用いた非同期な並列処理が可能</li>
      <li>通信が切断した際にUnityの動作に影響を与えない</li>
      <li>通信先の参照データをPeekで待ち合わせないで取得する</li>
      <li>Take/PutによりトランザクションとしてDataGearをUpdate</li>
      <li>TopologyManagerによりDGMのproxyネットワークが構成される</li>
      <li>TopologyManagerにCookieの機能があるため、改良すれば切断から復帰可能</li>
    </ul>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="まとめ">まとめ</h2>
<ul>
  <li>Christie をUnity で使用するためにC# に書き換えを行った</li>
  <li>書き換え方針としては、attribute やMessagePack などC# 独自の機能に対応しつつ元のソースコードと同じ機能になるように実装</li>
  <li>Unityで動作検証を行い、正しく動作することを確認した
    <ul>
      <li>Christie Sharpを利用したゲームの開発が可能</li>
      <li>ゲーム班などでネットワークゲームを容易にプログラムできる</li>
    </ul>
  </li>
  <li>TopologyManagerで自由にネットワーク環境を構築できる</li>
  <li>Take/Peek/Putを利用したゲームと相性の良いプログラミング</li>
  <li>Unity既存のライブラリとの比較を行った</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="今後の課題">今後の課題</h2>
<ul>
  <li>TopologyManagerの完成
    <ul>
      <li>2nd keyを用いたTreeMapの通信</li>
    </ul>
  </li>
  <li>Christie Sharpの性能検証を行う
    <ul>
      <li>Aliceで動作していた水族館の例題</li>
      <li>Christie Sharpを用いた100人規模のゲーム開発</li>
    </ul>
  </li>
</ul>


</div>


</div><!-- presentation -->
</body>
</html>