view Paper/chapter/2-RewriteCS.tex @ 51:9d71ffda7d97

update
author riono <e165729@ie.u-ryukyu.ac.jp>
date Thu, 10 Feb 2022 19:08:25 +0900
parents 1402268d1eca
children ea7e856b50e8
line wrap: on
line source

\chapter{ChristieのUnityへの適応}

\section{Unity}
Unity\cite{unity}はUnity Technologiesが開発、公開しているゲームエンジンである。
画像や3Dモデルの表示、物理演算、UIのイベント機能などゲーム制作に必要な機能が標準で備わっており、
個人でもゲーム開発が可能となっている。
さまざまなプラットフォームに対応可能であり、PC、iOS、Androidやその他コンシューマ機器も開発可能である。
また、非常に動作が軽いことも特徴であり、スペックが低いノートPCでも十分ゲーム開発が可能である。

プログラミング言語としてはC\#がサポートされている。
最新バージョンである2021.2.8ではC\# 9がサポートされており、
.NET Frameworkはバージョン4.6に対応している。
C\#向けの既存のAPIや外部ライブラリ、Unity用に開発されたAPIなども使用可能である。
拡張性が高く、開発に必要な機能を作成しUnityのメニューから実行することも可能である。


本研究では、開発環境を整えるためのハードルの低さ、公開されている通信ライブラリの豊富さなどを考慮しUnityを採用した。
%Unity上でChristieを動作させるために、Javaで記述されているChristieをC\#に書き換えを行う。


\section{Photon Unity Networking 2}
Photon Unity Networking 2\cite{pun2}(以下PUN2)はUnityで利用可能なネットワークライブラリである。
自動で他のClientへの接続や同期を可能とし、マッチメイキング機能なども備わっている。

\begin{figure}[htb]
  \begin{center}
      \includegraphics[width=115mm]{images/PUN2Connection.pdf}
  \end{center}
  \caption{PUN2のServer接続}
  \label{fig:PUN2serverconnect}
\end{figure}



図\ref{fig:PUN2serverconnect}はPUN2でのGame Serverまでの接続の過程を表した図である。
PUN2ではサーバクライアント型の通信を行っており、Photon CloudというCloud Serverに接続することで通信を可能にしている。
ClientはPhoton Cloudに接続を行うと、始めにName Serverに接続される。
Name Serverでは、そのClientが利用可能なリージョンを提供し、最低PingのMaster Serverへの接続が自動で行われる。
Master Serverでは、ゲーム全体における接続しているプレイヤーや、作成されているルーム情報などの監視を行っている。
マッチメイキングや、新しいRoomの作成、参加などが可能である。
各リージョンのMaster Serverは完全に分離しており、マッチメイキングはそれぞれのMaster Serverでのみ可能である。
Game Serverでは、Master Serverで作成された各ルームの管理が行われおり、実際にClient同士の通信を行ってゲームプレイを可能としているのはGame Serverである。

% 開発面でのできること  アタッチするだけとか
% 制限面 20mucみたいな 500messageとか
% ネット環境がないとlocal通信すらできない

開発面においてはUnity API対応がされており、座標や回転データなどを持っているTransform、アニメーション、
物理演算のRigidbodyの同期にはコードを書かずに用意されているScriptをGameObjectにAttatchするだけで同期可能となっている。

\lstinputlisting[label=src:Pun2Example, caption=PUN2の使用例]{src/Library/Pun2Example.cs}

ソースコード\ref{src:Pun2Example}は、PUN2でServerに接続を行いゲーム内にプレイヤーを生成する例である。
Callbackの使用にはMonoBehaviourPunCallbacksを継承する必要があり、MonoBehaviourPunCallbacksにはUnityのMonoBehaviourを継承しているため、
Startメソッドなどが使用可能である。
Startメソッドでは、事前に設定したName ServerおよびMaster Serverへの接続を行っている。
OnConnectedToMasterメソッドはMaster Serverへ接続した際に呼ばれ、Roomへ加入を試しなければ作成を行い、
OnJoinedRoomメソッドではGameServerにあるRoomに加入後に呼ばれ、プレイヤーの生成を行っている。
このようにPUN2では各Serverへの接続/切断時のコールバックが豊富に用意されており、柔軟に処理を記述することが可能である。

しかし開発時に検証を行う際にLocalマシンで通信を行う場合などでも、Photon Cloudへの接続が必須となる。
そのためネット環境がなければLocal通信でもPUN2を利用した通信はできない。


PUN2の基本料金は無料であるが、使用上の注意としていくつかの制限がある。
\begin{itemize}
  \item 同時接続人数が20人以下
  \item 1Roomの秒間Message数が500以下
  \item データの転送量が60GB以下
  \item etc ...
\end{itemize}

これらの制限を超えて利用する場合には、別途料金や有料プランに加入する必要が発生する。



\section{Mirror}
Mirror\cite{mirror}はUnityで使用できるOSSのネットワークライブラリである。
PUN2と同様にPC間の同期を自動で行い、主にMMO規模のネットワークを想定して開発が行われている。
主な機能として、ServerとClientを自動接続するScript、GameObjectの位置やアニメーションの同期を行うNetworkTransfromやNetworkAnimator、
Room機能などがある。

\begin{figure}[htb]
  \begin{center}
      \includegraphics[width=140mm]{images/MirrorConnection.pdf}
  \end{center}
  \caption{Mirrorの接続}
  \label{fig:Mirrorconnect}
\end{figure}


図\ref{fig:Mirrorconnect}はServerとClientの接続を表したものである。
Mirrorはサーバクライアント型の通信が行われている。
HostではServerと同時にLocal Clientが立ち上がる。
他のClinetはServerに接続を行うことで同期が可能となる。
ClinetはServerに対して操作やデータを送信し、Serverでの処理がClientに同期され反映される仕組みになっている。

\newpage

\lstinputlisting[label=src:MirrorExample, caption=Mirrorの使用例]{src/Library/MirrorExample.cs}

ソースコード\ref{src:MirrorExample}はMirrorを使用して、プレイヤーを表示、操作する例である。
NetworkBehaviourはMonoBehaviorを継承して作成されており、ネットワーク上で動作させる処理に継承させる必要がある。
OnStertServerメソッドはServerでこのGameObjectが生成された際に実行される。
このような様々なcallbackがNetworkBehaviourには用意されている。
また、フィールド変数にSyncVar attributeを付与することで自動的に変数を同期可能にしている。
SyncVar attributeによってサポートされている型はC\#のプリミティブ型やstring、Unity APIから提供されているVector3やQuaternion、
Mirrorから提供されているSyncListやSyncDictなどのリストや辞書である。

10行目のCommand attributeを関数に付与することで、ClientがServerに対してこの関数を呼び出すことが可能になる。
Server側で処理が行われ、その結果の状態がClientへ同期される。


Mirrorには上記のような通信フレームワークとして便利な機能が用意されており、OSSなためプロジェクトに合わせて自由に機能の拡張が可能である。
一方で公式Serverなどのサービスが用意されていないため、自前でServerを用意する必要がある。
そのため、LAN上で通信を行う場合には特に問題ないが、インターネット上に公開を手軽に行うことができない。


\section{ChristieをC\#に書き換える意義}
ChristieはJavaで実装されているが、Unity開発は基本的にC\#が使用される。
ここで、ChrsiteをJavaのまま利用するか、C\#に書き換えるかという疑問が生じた。

Unityはandroidの開発向けにJavaで書かれたメソッドをC\#で呼び出せる機能がある。
JavaやJava VMを呼び出すためにJava Native Interface(JNI)を利用している。
C\#側でJavaを呼び出すには、Unity APIが提供しているヘルパークラスを呼び出す必要があるが、stringを使用してリソースディレクトリから検索を行っているため、
計算負荷が高い。
そのため、ChristieをJavaから呼び出して使用するには不適合であると考える。

また、C\#に書き換えた際にバージョン管理の問題がある。
書き換えを行ったとして、ChristieにはJavaとC\#の2種類あることになり、機能の実装などそれぞれに対応する必要がある。
しかしJavaとC\#それぞれの対応はそこまで難しくないと考える。
Javaの記法とC\#の記法は非常によく似ており、APIもほとんど同じ機能を持ったものがそれぞれ実装されている。
そのため、Chrisiteに機能追加をする際にはJavaとC\#両方に対応することでバージョン間のすり合わせを行えると考える。

以上の理由により、ChristieをUnityでサポートされているC\#への書き換えを行う意義を見いだすことができた。


\section{Christie Sharpの書き換えの基本方針}
Javaで記述されたChristieと区別するため、C\#で記述するChristieをChrisite Sharpとする。
Chrisite Sharpではコードの保守性や、Christie設計時の意図などを守るため、Chrisiteと同じ挙動、同じ動作をする必要がある。
初めにC\#単体で動作するように、Christieの核となる部分の書き換えを行った。


ChristieはJava 9から開発されていたため、現在では非推奨なコードやバージョンアップが必要な箇所が存在する。
そこで書き換えを行う際に、C\#に対応しつつ処理動作の向上や最適化を行うために以下の改良を行った。

\begin{itemize}
  \item MessagePackの変更及びバージョンアップ
  \item ThreadPoolからTaskへの変更
\end{itemize}


\section{attributeの実装}
ChristieではDGを取得する際に、annotationを用いてTakeやPeekなどのコマンドを処理していた。
Christie Sharpはannotationではなく、代わりにattributeを利用してコマンドの処理を行っている。

\lstinputlisting[label=src:JavaTakeImple, caption=JavaにおけるTake annotationの実装]{src/java/Take.java}
\lstinputlisting[label=src:CSTakeImple, caption=C\#におけるTake attributeの実装]{src/cs/Take.cs}

ソースコード\ref{src:JavaTakeImple}はJavaにおけるTake annotationの実装である。
Javaでannotationを自作する際には、\@interfaceで宣言を行う。
1行目ではannotationの適用可能箇所を指定しておりフィールド変数に対して付与可能としている。
また、2行目はannotationの情報をどの段階まで保持するかを指定しており、Takeの場合JVMによって保存され、ランタイム環境で使用可能となっている。

ソースコード\ref{src:CSTakeImple}はC\#におけるTake attributeの実装である。
C\#でattributeを自作する際には、System.Attributeを継承する必要がある。
attributeの適用可能箇所については、1行目にてフィールド変数を指定している。


attributeの使用方法はannotationと同じく変数の宣言の前に、[ ]内に使用するattributeを宣言する(ソースコード\ref{src:CSTakeExample})。

\lstinputlisting[label=src:CSTakeExample, caption=Take attributeの例]{src/cs/TakeExample.cs}



\section{TaskによるCodeGearの処理}
ChrisiteではCGの実行にThreadPoolを利用していた。
しかしThreadPoolは管理や生成が煩雑であり、コストパフォーマンスの低下につながりやすい。
C\#にはThreadをより使いやすく高機能にしたTaskという機能がある。
TaskはC\#のThreadPoolを拡張しており、内部にThreadPoolと実行待ちQueueを持っている。
Task.RunメソッドやTask.Factory.StartNewメソッドで処理を実行でき、処理が渡されるとThreadPoolで処理されるため、Christieと同じ動作をすると考え、Christie SharpではTaskで書き換えを行った。

\lstinputlisting[label=src:JavaThreadPoolExecutor, caption=ChristieにおけるThreadPoolの実装の一部]{src/java/ThreadPoolExecutor.java}

ソースコード\ref{src:JavaThreadPoolExecutor}はChristieにおけるCodeGearを処理するThreadPoolの実装の一部である。
Javaでは独自にThreadPoolを実装する際にはThreadPoolExecutorを継承する。
またThreadの優先度を変更する機能が実装されており、CodeGear実行時に処理の優先度を設定することが可能となっている。


\lstinputlisting[label=src:CSThreadPoolExecutor, caption=Christie SharpにおけるThreadPoolの実装]{src/cs/ThreadPoolExecutor.cs}

ソースコード\ref{src:CSThreadPoolExecutor}はソースコード\ref{src:JavaThreadPoolExecutor}をC\#に書き換えを行ったものである。
CGの実行には14行目のExecuteを呼び出し、Taskで実行を行っている。


Threadの優先度による実行順変更については、実装の優先度が低かったため今回は実装を行っていない。
Taskのスケジューラーは自作可能であり、実行待ちQueueの処理順を変更することができるため実装可能である。


\section{Socket通信用のThreadをTaskに変更}
Christieでは、Socket通信を行っている箇所もThreadを使用したMultiThreadで動作している。
こちらも可読性や保守性のためTaskへの書き換えを行った。

\lstinputlisting[label=src:JavaAcceptThread, caption=ChristieにおけるAcceptThreadの実装の一部]{src/java/AcceptThread.java}

ソースコード\ref{src:JavaAcceptThread}はChristieにおけるSocket通信を行っているThreadの実行箇所である。
無限ループを行い、他nodeがSocketで接続される度にデータ送受信専用のThreadを作成する。
IncomingTCPConnectionではデータの受け取りを行っており、OutboundTCPConnectionではSocket通信が終了する際のコマンドの送信をそれぞれMultiThreadで行っている。

\lstinputlisting[label=src:CSAcceptThread, caption=Christie SharpにおけるAcceptThreadの実装の一部]{src/cs/AcceptThread.cs}

ソースコード\ref{src:CSAcceptThread}はソースコード\ref{src:JavaAcceptThread}をC\#に書き換えを行ったものである。
Christieでは、MultiThreadでSocket通信を行っていた。
Christie SharpではCodeGearの処理で使用していたThreadPoolと同様にTaskへの書き換えを行った。

\section{MessagePackの変更}
Christieではデータを他Nodeに送信する際に、MessagePackを使用してデータをSerializeし、送信を行っている。
Chrisiteで使用しているMessagePackはmsgpack java 0.6.12\cite{mspack}を使用しており、現在はサポートされていない。
そのためJavaでサポート対象となっているmsgpack java 0.7.x以上のMEssagePakckとは記述方法が異なっている。
ソースコード\ref{src:JavaMspackExample}はChristieで使用してるmsgpack java 0.6.12の使用例である。

\lstinputlisting[label=src:JavaMspackExample, caption=JavaにおけるMessagePackの使用例]{src/java/MessagePackExample.java}

MessagePackを使用するには、Serializeを行うクラスに対して明示的に@Message annotationを付ける必要がある。
これにより、クラス内で宣言したpublic変数がエンコードの対象となる。
ソースコード\ref{src:JavaMspackExample}の14 - 18行目はSerialize/Deserializeを行う例である。
MessagePackインスタンスを作成後、writeメソッドを使用することで引数に渡したオブジェクトをbyte[]型にSerializeできる。
Deserializeにはreadメソッドを使用し、引数としてSerializeされたbyte[]型とDeserialize対象のクラスを渡すことでデコードできる。
 

C\#のMessagePackは複数存在しており、msgpack java 0.6.12とほぼ同様の記述方法を採っているMessagePack CSharp 2.3.85\cite{csmessage}を選択した。


\lstinputlisting[label=src:CSMspackExample, caption=C\#におけるMessagePackの使用例]{src/cs/MessagePackExample.cs}
MessagePack CSharpではmsgpack javaと同様にクラスに対してSerializeを行うため、\ref{src:CSMspackExample}の1行目でMessagePackObject attributeを追加している。
また、Serializeする変数に対してkeyを設定することができ、indexesとしてのintやstringをkeyとして指定することができる。

データのSerializeにはMessagePackSerializer.Serializeメソッドを使用し、引数として渡したオブジェクトをbyte[]型にSerializeする。
DeserializeにはMessagePackSerializer.Deserializeメソッドを使用する。Deserializeメソッドはジェネリスク関数であるため、$<>$内にDeserialize対象のクラスを指定する。
ソースコード\ref{src:CSMspackExample}の22行目ではjson展開の例であり、変数それぞれにkeyを指定していることで展開可能となっている。

\section{送信パケットの修正}
MessagePackのバージョンを更新した影響により、Remote nodeにデータを送信するパケットの形式を変更する必要がある。
図\ref{fig:SendPackt}はChristieと、Chrisite Sharpにおける送信パケットの構成である。
msgpack javaではreadメソッドの引数にClass$<$T$>$を渡すことでデコード可能であり、RemoteMessageのDeserializeにデータ長を指定する必要はない。
DGはジェネリスクで記述しており毎回デコードするクラスが異なるため、デコードの際にデータ長を必要としている。


しかしMessagePack CSharpでは、DeserializeメソッドにClass$<$T$>$を渡してデコードすることができないため、データ長も付属させてデータを送信する。
それぞれのSizeのデータ長はintで指定しているが、MessagePackでSerializeした際に最大で5byteになる。

\begin{figure}[htb]
  \begin{center}
    \includegraphics[width=150mm]{images/SendPackt.pdf}
  \end{center}
  \caption{送信パケットの構成}
  \label{fig:SendPackt}
\end{figure}



\section{Chrisite SharpのDebug}
Christie Sharpの開発にはJetBrainsが開発・提供しているC\#向けのIDE、Rider\cite{rider}を用いている。
Christie Sharpは並列分散プログラミングを行っているため、MultiThreadでのDebugが必須である。
RiderにはDebugerの標準的な機能が搭載されているが、Christie Shapを開発する際、MainThread以外で処理をする箇所に対して張ったBreak point
でプロセスが停止しない状況に陥った。
解決方法がないか調査を行ったが原因は不明であり、MainThreadにBreak pointを張った場合には正しく停止するのに対し、
MultiThreadにBreak pointを張った際には停止できないと言うことがわかった。
そのため非効率ではあるが、Debugを行いたい処理の箇所にPrint文を挟むことでDebugを行った。


\section{Christie Sharpの記述方法}
ソースコード\ref{src:CSStartCGExample}、\ref{src:CSCGExample}、\ref{src:CSCountExample}はソースコード\ref{src:StartCGExample}、\ref{src:CGExample}、\ref{src:CounteObj}をChrisite Sharpで書き換えたものである。
JavaとC\#は大きく記述方法は変わらず、annotationがattributeになっている点が一番の違いである。
そのためChristieとChrisite Sharptには互換性があると言える。

\lstinputlisting[label=src:CSStartCGExample, caption=Chrisite SharpにおけるStartCodeGearの記述例]{src/cs/StartCountUp.cs}
\lstinputlisting[label=src:CSCGExample, caption=Chrisite SharpにおけるCodeGearの記述例]{src/cs/CountUpper.cs}
\lstinputlisting[label=src:CSCountExample, caption=Chrisite SharpにおけるDGとして送信されるオブジェクトのクラス]{src/cs/CountObject.cs}