この記事でのバージョン
Unity 2018.2.17f1
はじめに
Unityではコードを変更するたびに自動でコンパイルがされます。
(コンパイルしている時は右下にグルグルが出ている)
当然、コンパイル時間はコードが多いほど増していくので、
プロジェクトが大きくなっていくと無視できない時間になります。
そんな時はStandard Assetsという特殊なディレクトリやAssembly Definition Filesを使って、
アセンブリ(コンパイルする範囲)を分けて、コンパイル時間を短縮するのが常套手段です。
しかし、具体的なコンパイル時間が分かるわけではないので、
どのタイミングでコンパイル時間の短縮を図ればいいのかというのは難しい問題です。
ということで今回は、アセンブリごとのコンパイル時間を計測したり、
一定時間を越えたら警告を出したりという事をしようという記事です。
イメージとしては以下のような感じ
AssemblyCompileTimeWindow
早速ですが、アセンブリごとのコンパイル時間を計測するためのウィンドウ
AssemblyCompileTimeWindowのプログラムです。
これをEditorディレクトリに作れば、
Window -> AssemblyCompileTimeWindowからウィンドウが開けるようになります。
ウィンドウを開いていれば、それだけで直近のコンパイル時間を計測し、表示するようになります。
また、ログ表示のチェックボックスを有効にすれば、
コンパイルされる度にコンパイル時間をログで表示するようになります。
さらに警告表示のチェックボックスを有効にし、時間を設定すれば、
コンパイル時間がその時間を越えた時だけ警告を表示するようになります。
なお、設定や直近のコンパイル時間は保存されているので、
ウィンドウを閉じたり、Unityエディタを再起動したりしても消える事はありません。
実装方法
AssemblyCompileTimeWindowの実装について、要点だけを簡単に紹介しておきます。
まず、コンパイルの開始と終了の検知はCompilationPipelineを使っています。
//各アセンブリのコンパイル開始、終了時のイベントにメソッド登録
CompilationPipeline.assemblyCompilationStarted += OnAssemblyCompilationStarted;
CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished;
次に、時間計測にはStopwatchというUnityではなく.NET Frameworkのクラスを使っています。
//時間を計るためのクラス private static Stopwatch _stopwatch = new Stopwatch();
//計測時間を初期化し、計測開始
_stopwatch.Reset();
_stopwatch.Start();
//計測を終了時、コンパイル時間を取得 _stopwatch.Stop(); float compileTime = (float)_stopwatch.Elapsed.TotalSeconds;
さらに、設定データの保存は保存用のクラスを作成し、JsonにしてEditorUserSettingsで保存しています。
//設定情報と保存時のKey private AssemblyCompileTimeWindowSettingData _settingData = null; private const string SETTING_DATA_SAVE_KEY = "AssemblyCompileTimeWindowSettingDataKey";
//設定情報のロード _settingData = JsonUtility.FromJson<AssemblyCompileTimeWindowSettingData>(EditorUserSettings.GetConfigValue(SETTING_DATA_SAVE_KEY)); if (_settingData == null) { settingData = new AssemblyCompileTimeWindowSettingData(); }
//設定情報の保存
EditorUserSettings.SetConfigValue(SETTING_DATA_SAVE_KEY, JsonUtility.ToJson(_settingData));
また、直近のコンパイル時間をアセンブリ名と紐づけて管理&保存がしたかったのですが、
Dictionaryはそのままシリアライズ出来ない(保存出来ない)ので
保存用のクラスを作って、そのListを使って代用しています。
/// <summary> /// 名前と時間をペアで管理するクラス(シリアライズするために、Listで使ってDictの代わりに) /// </summary> [Serializable] public class NameAndTimePair { [SerializeField] private string _name = ""; public string Name { get { return _name; } } [SerializeField] private float _time = 0; public float Time { get { return _time; } set { _time = value; } } public NameAndTimePair(string name, float time) { _name = name; _time = time; } }
//各アセンブリの直近のコンパイル時間 [SerializeField] private List<NameAndTimePair> _currentCompileTimeList = new List<NameAndTimePair>();
なお、多次元Listも似たような感じでシリアライズが行えます。
おわりに
何でもそうですが、後から一気に直すのは大変です。
なので直すための仕組みづくりが大事になってくるので、今回紹介した方法を使って
コンパイル時間が長くなりすぎないように監視するのは我ながら便利だと思います。