テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

CodeDOMによるコードの自動生成の実験

コードの自動生成について、誰得って気もするが、せっかくやったのでメモに残しておこうと思う。


今回の目的はMsgpack_cliのPacer/Unpackerコードの生成。MessagePackSerializerが使えれば正直楽なのだが、AOTで動作するiOSのせいでコレが使えず、レガシー宜しくPackerとUnpackerを作る必要がある。但し、Msgpackの基本はArrayなので(mapも出来るが)順番が他のシリアライザと異なると読込が失敗する問題があり、サーバー側とクライアントの生成順番を一致させる必要がある。(つまり面倒くさい)

この問題の対策として、CodeDOMのような技術でマッピングするコードを自動生成してしまうアプローチを模索する。この方法は現在Msgpack-cli 0.5で試作されてるらしく、現状は一応コードの生成も出来る(でもcontext作る時にjitを使っているので現状使えない)

結論から言えば、何とかなりそう。ただ自動生成のタイミングと自動生成の対象はもっと考えなければならない感じ。特に自動生成の対象を属性で設定したり、生成のタイミングをビルド毎ではなくエディタから設定するといったギミックを仕込んだ方が良さそうだ。

コードの自動生成は、iOSのようなJITで動かせない端末でリフレクション的にアクセスしたい場合や、(糞遅い)リフレクションを避けるといった箇所に使えるかもしれない。ただし作るのが非常に面倒なので(10行作るのに100行必要・デバッグが超面倒)、殆どのケースでは使わないと思う。

・・・実はコレが先日書いた[Unity]Unity標準クラスにメソッドを追加する方法の目的。


まずコードを生成するコードの位置だが、ランタイムにこの機能は不要なのでEditorに配置する。Editorフォルダに配置したプログラムはアプリに含まれない為、アプリの起動時間の短縮やメモリの削減に繋がるし、無駄な物を入れない事で動作もスッキリする。

で、今回のコード生成タイミングだが、静的コンストラクタ実行のタイミングで行った。このタイミングの場合、エディタがプログラムをビルドするタイミングで呼ばれるので、プログラムが変更される度に呼び出せ使う側が意識することなく使える…と思っていたが、ビルドに失敗するとコード生成が呼ばれないので思ってたより厄介な代物だった(生成結果に依存するコードが存在する場合、それらを全て無効にしないとコードが生成出来ない)。逆にエディタ拡張で呼ぶ場合はビルドに失敗していても呼べるが、エラー前に拡張結果が適応されてる必要があり、エディタの再起動等を行うとこれまた自動生成出来なくなる。いっその事シェルスクリプトみたいな物で作ったほうが良いかもしれない。

ちなみに静的コンストラクタをEditor上から呼ぶには、クラスの頭に[InitializeOnLoad]を付ける。InitializeOnLoadはUnityEditorで定義されている。


コードの生成はCodeDomで行った。ただ、今回の目的は「拡張メソッドを作る事」で2つ程障害があった。
1. CodeDomは静的クラスを作れない
2. this ***みたいな引数のパラメータが無い

この2つの問題は物凄く強引に解決した。まず1だが、生成したコードの"public class"を"public static class"に置換、そしてthisのパラメータ問題はCodeParameterDeclarationExpressionの第一引数の型を"this クラス名"とすることで解決した。

最後にAssetDatabase.Refreshでファイルをリフレッシュし、生成したファイルも含めビルドし直す。これがファイルが増えていくと地味に長くなるかもしれない。

CodeGenが動けばTestCodeが生成される。