LoginSignup
11
10

More than 5 years have passed since last update.

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

Posted at

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

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

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

DependsOnAttribute.cs
using System;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class DependsOnAttribute : Attribute {
    public Type dependee;
    public DependsOnAttribute(Type dependee) {
        this.dependee = dependee;
    }
}
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から図を拝借。

依存関係グラフ

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

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

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

スクリーンショット 2014-08-22 17.25.48.png

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

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10