Controlling ScriptExecutionOrder through Class Attributes - code provided.

Hi,

Figured I’d post this in case others might find it useful.

It always frustrated me that I had to set up the ScriptExecutionOrder by hand, especially when using my own personal library of useful scripts. For a while I used a script i’d found online but you had to add the classes to it by hand. So I finally got round to writing my own that leveraged c# attributes to automatically set the order for me.

The script has had basic testing, but as ever its a ‘use at your own risk’ deal and you should probably make a back up of the project before using it for the first time.

There are two scripts, ScriptExecutionAssignment is an editor script and as such MUST be placed within an Editor folder in your project. The other is ScriptExecutionAttribute which can be placed anywhere, but must NOT be in an editor folder.

Then simply add [ScriptExecutionAttribute( XXX )] to your scripts you want to control the script execution order for, replacing the XXX with an integer ( -9999 to 9999 ).

// ScriptExecutionAssignment
// By NoiseCrime 05.06.2016
//
//
// Editor class that will automatically set the scriptExecutionOrder based on using a ScriptExecutionAttribute.
// The class is called automatically everytime Unity recompiles the project - see [InitializeOnLoad]
//
// This script MUST be placed into an Editor Folder in your Unity Project.
// Requires Script names to match class name.
// Likely only works with a single class defined per file.
// Unfortunately the script will also execute everytime you run the project in the editor ( play )
//
// ENABLE_ASSIGNMENT:   If NOT defined this will prevent execution of the script.
// ENABLE_LOGGING:      If defined will log ( sorted by order ) to the console all found classes using the ScriptExecutionAttribute and their script order.


#define ENABLE_ASSIGNMENT
#define ENABLE_LOGGING

using UnityEngine;
using UnityEditor;

using System;
using System.Linq;
using System.Collections.Generic;

[InitializeOnLoad]
public class ScriptExecutionAssignment : Editor
{

   static ScriptExecutionAssignment()
   {
#if ENABLE_ASSIGNMENT
     if( EditorApplication.isCompiling || EditorApplication.isPaused || EditorApplication.isPlaying) return;

     #if ENABLE_LOGGING
     Dictionary<string, int> executionOrderLogging = new Dictionary<string, int>();
     #endif

     EditorApplication.LockReloadAssemblies();

     // Loop through monoscripts
     foreach (MonoScript monoScript in MonoImporter.GetAllRuntimeMonoScripts())
     {       
       Type monoClass = monoScript.GetClass();

       if( monoClass != null)
       {
         Attribute att = Attribute.GetCustomAttribute(monoClass, typeof(ScriptExecutionAttribute));

         if( att != null)
         {
           int executionIndex = ((ScriptExecutionAttribute)att).executionIndex;

           #if ENABLE_LOGGING
           executionOrderLogging.Add( monoScript.name, executionIndex);         
           #endif

           // It is important not to set the execution order if the value is already correct!
           if (MonoImporter.GetExecutionOrder(monoScript) != executionIndex)
           {
             MonoImporter.SetExecutionOrder(monoScript, executionIndex);
             #if ENABLE_LOGGING
             Debug.LogWarning("The script " + monoScript.name + " was not set to correct Script Execution Order - it has been fixed and set to " + executionIndex);
             #endif
           }     
         }
       }
     }

     EditorApplication.UnlockReloadAssemblies();

     #if ENABLE_LOGGING
     // Simple logging to console with the dictionary of found classes sorted by execution order.
     if(executionOrderLogging.Count > 0)
     {           
       string logging = "ScriptExecutionAttribute: Found Types\n";
       
       foreach (KeyValuePair<string,int> item in executionOrderLogging.OrderBy(key => key.Value))
         logging += "ScriptExecutionAttribute: " + item.Value + "  Type: " + item.Key + "\n";
       
       Debug.Log(logging);
     }
     #endif   

#else
     Debug.LogWarning("ScriptExecutionAssignment is disabled - check script define.");
#endif
   }
}
// ScriptExecutionAttribute
// By NoiseCrime 05.06.2016
//
// Support Attribute class that works with the editor script 'ScriptExecutionAssignment' to automatically populate the ScriptExecutionOrder.
// Simply add [ScriptExecutionAttribute( value )] prior to your class declaration, where value indicates the script order you want ( -9999 to +9999 )
// This script must NOT be placed in an Editor Folder.

using System;

[AttributeUsage( AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class ScriptExecutionAttribute : Attribute
{
    public readonly int executionIndex;
      
    public ScriptExecutionAttribute( int index )
    {
        this.executionIndex = index;
    }
}

an example

using UnityEngine;

[ScriptExecutionAttribute( -9702 )]
public class ApplicationStartUp : MonoBehaviour
{
    void Awake()
    {
        .....
    }
}
5 Likes

Hey,

So since people are still using this code I figured I’d post the latest version.

This updated version has

  • Removed the requirement for setting a define in the code to enable/disable the script. Instead that behavior is now achieved through the custom Unity menu toggle option. This means you can easily and quickly toggle the script on or off.
  • Other miscellaneous improvements and fixes.

If the ‘ScriptExecutionAssignment Automatic’ menu option is enabled then on every recompile the script will run and automatically sort the execution assignment of scripts with the attribute. This state is saved to the per project editor preferences.

Alternatively you can leave ‘ScriptExecutionAssignment Automatic’ disabled and instead select the ‘ScriptExecutionAssignment Refresh’ to immediately run and automatically sort the execution assignment of scripts with the attribute

The editor script - MUST BE PLACED WITHIN AN EDITOR FOLDER!

// Author:      NoiseCrime 05.06.2017
// Location:    In Editor Folder

#define ENABLE_LOGGING

using UnityEngine;
using UnityEditor;
using NoiseCrimeStudios.Core;
using System;
using System.Linq;
using System.Collections.Generic;

namespace NoiseCrimeStudios.Editor.Settings
{
    /// <summary  folder="Editor">
    /// Editor class that will automatically set the scriptExecutionOrder based on using a ScriptExecutionAttribute.
    /// The class is called automatically everytime Unity recompiles the project - see [InitializeOnLoad]
    /// </summary>
    /// <remarks>
    /// This script MUST be placed into an Editor Folder in your Unity Project.
    /// Requires Script names to match class name.
    /// Likely only works with a single class defined per file.
    ///
    /// ENABLE_LOGGING: Log to console all classes found using the ScriptExecutionAttributeand their script order.
    /// </remarks>
    [InitializeOnLoad]
    static class ScriptExecutionAssignment
    {
        public static bool m_EnabledState    = false;

        // Define where settings appear in the Unity Menus
        const string m_MenuItemAutomatic    = "Window/NoiseCrimeStudios/EditorSettings/ScriptExecutionAssignment Automatic";
        const string m_MenuItemImmediate    = "Window/NoiseCrimeStudios/EditorSettings/ScriptExecutionAssignment Refresh";

        // Cache Enabled state to Unity EditorPrefs
        const string m_EnabledPrefsKey        = "com.noiseCrimeStudios.ScriptExecutionAssignment.Enabled";

        static ScriptExecutionAssignment()
        {
            // Cache EnabledState to avoid constant reads of prefs file
            SetEnabledState( EditorPrefs.GetBool( m_EnabledPrefsKey, false ) );
           
            // Only allow ScriptExecutionAssignment if enabled and not conflicting with Unity
            if ( !m_EnabledState ) return;

            Debug.LogWarningFormat( "NoiseCrimeStudios EditorTools: ScriptExecutionAssignment Constructor: Active ( IsEnabled: {0} )", m_EnabledState );

            RefreshScriptExecutionAssignment();
        }


        static void RefreshScriptExecutionAssignment()
        {
            // Only allow ScriptExecutionAssignment if not conflicting with Unity
            if ( EditorApplication.isCompiling || EditorApplication.isPaused || EditorApplication.isPlaying || EditorApplication.isPlayingOrWillChangePlaymode ) return;
           
#if ENABLE_LOGGING
            int index = 0;
            Dictionary<string, int> executionOrderLogging = new Dictionary<string, int>();
#endif           

            try
            {               
                // LockAssemlies
                EditorApplication.LockReloadAssemblies();

                // Loop through monoscripts
                foreach ( MonoScript monoScript in MonoImporter.GetAllRuntimeMonoScripts() )
                {
                    Type monoClass = monoScript.GetClass();

                    if ( monoClass != null )
                    {
                        // Attribute att = Attribute.GetCustomAttribute(monoClass, typeof(ScriptExecutionAttribute));
                        ScriptExecutionAttribute attribute = Attribute.GetCustomAttribute(monoClass, typeof(ScriptExecutionAttribute)) as ScriptExecutionAttribute;

                        if ( attribute != null )
                        {
                            int        executionIndex = attribute.executionIndex;
                            string    monoScriptName = monoClass.FullName;
                           
#if ENABLE_LOGGING
                            if ( executionOrderLogging.ContainsKey( monoScriptName ) )
                                monoScriptName = string.Format( "{0} [{1}]", monoClass.FullName, index++ );

                            executionOrderLogging.Add( monoScriptName, executionIndex );
#endif

                            // It is important not to set the execution order if the value is already correct!
                            if ( MonoImporter.GetExecutionOrder( monoScript ) != executionIndex )
                            {
                                MonoImporter.SetExecutionOrder( monoScript, executionIndex );
#if ENABLE_LOGGING
                                Debug.LogWarning( "The script " + monoScriptName + " was not set to correct Script Execution Order - it has been fixed and set to " + executionIndex );
#endif
                            }
                        }
                    }
                }
            }
            catch
            {
                throw;
            }
            finally
            {
                // Unlock Assemblies
                EditorApplication.UnlockReloadAssemblies();
            }

#if ENABLE_LOGGING
            // Simple logging to console with the dictionary of found classes sorted by execution order.
            if ( executionOrderLogging.Count > 0 )
            {
                string logging = "ScriptExecutionAttribute: Found Types\n";

                foreach ( KeyValuePair<string, int> item in executionOrderLogging.OrderBy( key => key.Value ) )
                    logging += "ScriptExecutionAttribute: " + item.Value + "  Type: " + item.Key + "\n";

                Debug.Log( logging );
            }
#endif
        }

       
        #region Public Methods
        public static void SetEnabledState( bool val )
        {
            EditorPrefs.SetBool( m_EnabledPrefsKey, val );
            m_EnabledState = val;
        }
        #endregion

        #region MenumItems               
        [MenuItem( m_MenuItemAutomatic, false )]
        static void ToggleOption()
        {
            SetEnabledState(!m_EnabledState);
        }

        [MenuItem( m_MenuItemAutomatic, true )]
        public static bool ValidateToggleOption()
        {
            Menu.SetChecked( m_MenuItemAutomatic,     m_EnabledState );
            return true;
        }

        [MenuItem( m_MenuItemImmediate, false )]
        static void Refresh()
        {
            RefreshScriptExecutionAssignment();
        }

        [MenuItem( m_MenuItemImmediate, true )]
        public static bool ValidateRefresh()
        {           
            // Only allow ScriptExecutionAssignment if not conflicting with Unity
            if ( EditorApplication.isCompiling || EditorApplication.isPaused || EditorApplication.isPlaying || EditorApplication.isPlayingOrWillChangePlaymode ) return false;
            return true;
        }
        #endregion
    }
}

The Attribute Script - MUST BE PLACED ANYWHERE EXCEPT AN EDITOR FOLDER

// Author:      NoiseCrime 05.06.2017
// Location:    Not In Editor Folder

using System;

namespace NoiseCrimeStudios.Core
{
    /// <summary>
    /// Support Attribute class that works with the editor script 'ScriptExecutionAssignment' to automatically populate the ScriptExecutionOrder.
    /// Simply add [ScriptExecutionAttribute( value )] prior to your class declaration, where value indicates the script order you want ( -9999 to +9999 )
    /// This script must NOT be placed in an Editor Folder.
    /// </summary>
    [AttributeUsage( AttributeTargets.Class, Inherited = false, AllowMultiple = false )]
    public sealed class ScriptExecutionAttribute : Attribute
    {
        public readonly int executionIndex;

        public ScriptExecutionAttribute( int index )
        {
            this.executionIndex = index;
        }
    }
}