view Paper/chapter/2-RewriteCS.tex @ 30:2a9f335e45bd

add source code and update Unity chapter
author riono <e165729@ie.u-ryukyu.ac.jp>
date Fri, 04 Feb 2022 20:47:04 +0900
parents cdf988e1ccf2
children 78fa97061c97
line wrap: on
line source

\chapter{ChristieのC\#への書き換え}

\section{Unity}
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{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実行時に処理の優先度を設定することが可能となっている。

\newpage

\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を使用しており、現在はサポートされていない。
そのため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を選択した。

\newpage

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