依存関係に基づいてスクリプト実行順設定を自動化する

Unityでは基本的にスクリプトの処理順番は不定なので、スクリプト間に依存関係があるときはAwakeやStart、UpdateやLateUpdateの違いで乗り切る。最後の手段としてScript Execution Orderを使う。依存関係に敏感な実装にしない。

というのがよくあるスタイルと思う。しかし処理順が問題でUpdateからLateUpdateにコードを移すとき、誤魔化しを感じることがあった。A <-- B <-- C のように2段の依存を見つけたときは特に。

そこで依存関係は明確にして、処理順は適切にコントロールする。という発想に切り替えてそれを補助するユーティリティを作ってみた。

```csharp:DependsOnAttribute.cs
using System;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class DependsOnAttribute : Attribute {
	public Type dependee;
	public DependsOnAttribute(Type dependee) {
		this.dependee = dependee;
	}
}
```

```csharp:Editor/ExecutionOrderCoordinator.cs
using UnityEditor;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;

[InitializeOnLoad]
public class ExecutionOrderCoordinator {
	static ExecutionOrderCoordinator() {
		EditorApplication.update += DoRefreshExecutionOrders;
	}

	static void DoRefreshExecutionOrders() {
		EditorApplication.update -= DoRefreshExecutionOrders;
		if (!EditorApplication.isPlaying) {
			RefreshExecutionOrders();
		}
	}

	static void RefreshExecutionOrders() {
		var graph = GetDependencyGraph();
		List<Type> sorted = TSort(graph.dependencies);

		for (int i = 0; i < sorted.Count; ++i) {
			var type = sorted[i];
			var script = graph.monoScripts[type];
			var order = i - sorted.Count;

			if (MonoImporter.GetExecutionOrder(script) != order) {
				MonoImporter.SetExecutionOrder(script, order);
			}
		}
	}

	class DependencyGraph {
		public Dictionary<Type, MonoScript> monoScripts = new Dictionary<Type, MonoScript>();
		public Dictionary<Type, HashSet<Type>> dependencies = new Dictionary<Type, HashSet<Type>>();
	}

	static DependencyGraph GetDependencyGraph() {
		var graph = new DependencyGraph();
		foreach (var script in MonoImporter.GetAllRuntimeMonoScripts()) {
			var klass = script.GetClass();
			if (klass != null) {
				graph.monoScripts[klass] = script;

				var attrs = Attribute.GetCustomAttributes(klass, typeof(DependsOnAttribute));
				foreach (DependsOnAttribute attr in attrs) {
					HashSet<Type> set;
					if (!graph.dependencies.TryGetValue(klass, out set)) {
						set = graph.dependencies[klass] = new HashSet<Type>();
					}
					set.Add(attr.dependee);

					if (!graph.dependencies.ContainsKey(attr.dependee)) {
						graph.dependencies[attr.dependee] = new HashSet<Type>();
					}
				}
			}
		}
		return graph;
	}

	// The AWK Programming Language(1988) SECTION 7.3 TOPOLOGICAL SORTING
	// http://d.hatena.ne.jp/tociyuki/20120919
	// Color definition: white=0, gray=1, black=2
	static List<Type> Depends(Type target, Dictionary<Type, HashSet<Type>> dependencies, Dictionary<Type, int> marked, List<Type> sorted) {
		if (marked.ContainsKey(target)) {
			return null;
		}
		marked[target] = 1;
		foreach (var m in dependencies[target].OrderBy(x => x.FullName)) {
			if (!marked.ContainsKey(m)) {
				Depends(m, dependencies, marked, sorted);
			} else if (marked[m] == 1) {
				throw new InvalidOperationException(string.Format("nodes {0} and {1} are in a cycle", m, target));
			}
		}
		sorted.Add(target);
		marked[target] = 2;
		return sorted;
	}

	static List<Type> TSort(Dictionary<Type, HashSet<Type>> dependencies) {
		var marked = new Dictionary<Type, int>();
		var sorted = new List<Type>();
		foreach (var target in dependencies.Keys.OrderBy(x => x.FullName)) {
			Depends(target, dependencies, marked, sorted);
		}
		return sorted;
	}
}
```

[Wikipedia](http://en.wikipedia.org/wiki/Tsort)から図を拝借。

![依存関係グラフ](http://upload.wikimedia.org/wikipedia/commons/thumb/0/03/Directed_acyclic_graph_2.svg/305px-Directed_acyclic_graph_2.svg.png)

上図のような依存関係があるとして、

```csharp:C7.cs
[DependsOn(typeof(C8))]
[DependsOn(typeof(C11))]
public class C7 : MonoBehaviour { }
```

```csharp:C8.cs
[DependsOn(typeof(C9))]
public class C8 : MonoBehaviour { }
```

このように依存関係を属性で定義していく。

![スクリーンショット 2014-08-22 17.25.48.png](https://qiita-image-store.s3.amazonaws.com/0/21130/2a745ee4-e86b-0269-01c7-a267794458ff.png)

スクリプトがビルドされると上図のようにScript Execution Orderが-1から負方向に向かって自動設定される。
このユーティリティを使うときは、手動で設定された順序と競合しないように、値を調整しておく必要がある(UIStretchなど)。