Skip to content

Instantly share code, notes, and snippets.

@MattOstgard
Last active August 31, 2023 10:19
Show Gist options
  • Save MattOstgard/6e385f61b9b04f987cfd934c9413899e to your computer and use it in GitHub Desktop.
Save MattOstgard/6e385f61b9b04f987cfd934c9413899e to your computer and use it in GitHub Desktop.
Helper for determining what order the Unity Editor makes callbacks.
// ==== UnityCallbackCheatSheetMonoBehaviour.cs ====
//
// Use this to determine what order the editor makes callbacks.
//
// Extra useful note: Use `UnityEditor.SessionState.SetString` and the other type variants to save a value for only the
// current editor session. The value will be erased after exiting.
//
// Below is the order I found during execution in the editor. Process was I would open the editor, enter play mode,
// then exit play mode.
//
// max=0: UnityEditor.InitializeOnLoadAttribute called class static constructor.
// max=1: Constructor called when creating an instance through a static constructor.
// max=2: UnityEditor.InitializeOnEnterPlayModeAttribute.
// max=3: UnityEditor.InitializeOnLoadMethodAttribute.
// max=4: UnityEditor.Callbacks.DidReloadScriptsAttribute.
// max=5: MonoBehaviour UnityEngine.RuntimeInitializeOnLoadMethodAttribute with parameter RuntimeInitializeLoadType.SubsystemRegistration.
// max=6: MonoBehaviour Awake.
// max=7: MonoBehaviour OnEnable.
// max=8: UnityEngine.RuntimeInitializeOnLoadMethodAttribute with parameter RuntimeInitializeLoadType.SubsystemRegistration.
// max=9: UnityEngine.RuntimeInitializeOnLoadMethodAttribute with parameter RuntimeInitializeLoadType.AfterAssembliesLoaded.
// max=10: UnityEngine.RuntimeInitializeOnLoadMethodAttribute with parameter RuntimeInitializeLoadType.BeforeSplashScreen.
// max=11: UnityEngine.RuntimeInitializeOnLoadMethodAttribute with parameter RuntimeInitializeLoadType.BeforeSceneLoad.
// max=12: UnityEngine.RuntimeInitializeOnLoadMethodAttribute with no parameters.
// max=13: UnityEngine.RuntimeInitializeOnLoadMethodAttribute with parameter RuntimeInitializeLoadType.AfterSceneLoad.
// max=14: MonoBehaviour Start.
// max=15: MonoBehaviour Update.
// max=16: UnityEditor.EditorApplication.playModeStateChanged changed to EnteredPlayMode (first call).
// max=18: UnityEditor.EditorApplication.playModeStateChanged changed to ExitingPlayMode (first call).
// max=19: Application.quitting.
// max=20: MonoBehaviour OnDisable.
// max=21: MonoBehaviour OnDestroy.
// max=21: UnityEditor.SceneView.beforeSceneGui (first call, ignoring future calls).
// max=22: UnityEditor.EditorApplication.playModeStateChanged changed to EnteredEditMode (first call).
// max=23: UnityEditor.EditorApplication.playModeStateChanged changed to ExitingEditMode (first call).
// max=24: Destructor.
#if UNITY_EDITOR
using UnityEngine;
public class UnityCallbackCheatSheetMonoBehaviour : MonoBehaviour {
private static CallbackCheatSheetMonoBehaviour instance;
private static bool hasInstance = false;
private static bool wasUpdateCalled = false;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnRuntimeInitializeOnLoadMethod_SubsystemRegistration() {
Debug.Log(
$"CallbackCheatSheet: (order={CallbackCheatSheet.callOrder++}, max=5) MonoBehaviour "
+ "`UnityEngine.RuntimeInitializeOnLoadMethodAttribute` with parameter "
+ "`RuntimeInitializeLoadType.SubsystemRegistration`."
);
if (instance != null) return;
var gObj = new GameObject(nameof(CallbackCheatSheetMonoBehaviour));
instance = gObj.AddComponent<CallbackCheatSheetMonoBehaviour>();
hasInstance = true;
}
protected void Awake() {
Debug.Log(
$"CallbackCheatSheet: (order={CallbackCheatSheet.callOrder++}, max=6) MonoBehaviour "
+ "`Awake`."
);
if (hasInstance) {
Destroy(gameObject);
}
}
protected void OnEnable() {
Debug.Log(
$"CallbackCheatSheet: (order={CallbackCheatSheet.callOrder++}, max=7) MonoBehaviour "
+ "`OnEnable`."
);
}
protected void Start() {
Debug.Log(
$"CallbackCheatSheet: (order={CallbackCheatSheet.callOrder++}, max=14) MonoBehaviour "
+ "`Start`."
);
}
protected void Update() {
if (wasUpdateCalled) return;
wasUpdateCalled = true;
// ReSharper disable once Unity.PerformanceCriticalCodeInvocation
Debug.Log(
$"CallbackCheatSheet: (order={CallbackCheatSheet.callOrder++}, max=15) MonoBehaviour "
+ "`Update`."
);
}
protected void OnDisable() {
Debug.Log(
$"CallbackCheatSheet: (order={CallbackCheatSheet.callOrder++}, max=20) MonoBehaviour "
+ "`OnDisable`."
);
}
protected void OnDestroy() {
Debug.Log(
$"CallbackCheatSheet: (order={CallbackCheatSheet.callOrder++}, max=21) MonoBehaviour "
+ "`OnDestroy`."
);
instance = null;
hasInstance = false;
}
}
[UnityEditor.InitializeOnLoad]
public class CallbackCheatSheet {
private static CallbackCheatSheet instance;
public static int callOrder = 0;
private static bool wasOnBeforeSceneGuiCalled = false;
private static bool wasPlayModeStateChanged_EnteredEditMode = false;
private static bool wasPlayModeStateChanged_EnteredPlayMode = false;
private static bool wasPlayModeStateChanged_ExitingEditMode = false;
private static bool wasPlayModeStateChanged_ExitingPlayMode = false;
/// <summary> Called at start of editor playmode runtime only if "Edit > Project Settings > Editor > Enter Play
/// Mode Settings > Reload Domain" is on. </summary>
static CallbackCheatSheet() {
callOrder = 0;
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=0) "
+ "`UnityEditor.InitializeOnLoadAttribute` called class static constructor."
);
instance = new CallbackCheatSheet();
UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += OnEditorSceneManagerSceneOpened;
UnityEditor.SceneView.beforeSceneGui += OnBeforeSceneGui;
UnityEditor.EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
Application.quitting += OnApplicationQuitting;
}
private static void OnPlayModeStateChanged(UnityEditor.PlayModeStateChange playModeStateChange) {
switch (playModeStateChange) {
case UnityEditor.PlayModeStateChange.EnteredEditMode:
if (!wasPlayModeStateChanged_EnteredEditMode) {
wasPlayModeStateChanged_EnteredEditMode = true;
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=22) "
+ "`UnityEditor.EditorApplication.playModeStateChanged` "
+ "changed to EnteredEditMode (first call, ignoring future calls)."
);
}
break;
case UnityEditor.PlayModeStateChange.EnteredPlayMode:
if (!wasPlayModeStateChanged_EnteredPlayMode) {
wasPlayModeStateChanged_EnteredPlayMode = true;
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=16) "
+ "`UnityEditor.EditorApplication.playModeStateChanged` "
+ "changed to EnteredPlayMode (first call, ignoring future calls)."
);
}
break;
case UnityEditor.PlayModeStateChange.ExitingEditMode:
if (!wasPlayModeStateChanged_ExitingEditMode) {
wasPlayModeStateChanged_ExitingEditMode = true;
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=23) "
+ "`UnityEditor.EditorApplication.playModeStateChanged` "
+ "changed to ExitingEditMode (first call, ignoring future calls)."
);
}
break;
case UnityEditor.PlayModeStateChange.ExitingPlayMode:
if (!wasPlayModeStateChanged_ExitingPlayMode) {
wasPlayModeStateChanged_ExitingPlayMode = true;
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=18) "
+ "`UnityEditor.EditorApplication.playModeStateChanged` "
+ "changed to ExitingPlayMode (first call, ignoring future calls)."
);
}
break;
default:
Debug.LogError($"Unexpected PlayModeStateChange: {playModeStateChange}");
break;
}
}
private static void OnApplicationQuitting() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=19) "
+ "`Application.quitting`."
);
}
CallbackCheatSheet() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=1) "
+ "Constructor called when creating instance through static constructor."
);
}
/// <summary> Called at edit-time or runtime regardless if "Edit > Project Settings > Editor > Enter Play Mode
/// Settings > Reload Domain" or "Reload Scene" is on.
/// https://docs.unity3d.com/ScriptReference/InitializeOnEnterPlayModeAttribute.html </summary>
[UnityEditor.InitializeOnEnterPlayMode]
private static void OnEditorInitializeOnEnterPlayMode() {
bool isDomainReloadOn = !(
UnityEditor.EditorSettings.enterPlayModeOptionsEnabled
&& UnityEditor.EditorSettings.enterPlayModeOptions.HasFlag(
UnityEditor.EnterPlayModeOptions.DisableDomainReload
)
);
bool isSceneReloadOn = !(
UnityEditor.EditorSettings.enterPlayModeOptionsEnabled
&& UnityEditor.EditorSettings.enterPlayModeOptions.HasFlag(
UnityEditor.EnterPlayModeOptions.DisableSceneReload
)
);
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=2) "
+ "`UnityEditor.InitializeOnEnterPlayModeAttribute`. "
+ $"(isDomainReloadOn={isDomainReloadOn}, isSceneReloadOn={isSceneReloadOn})"
);
}
[UnityEditor.InitializeOnLoadMethod]
private static void OnEditorInitializeOnLoadMethod() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=3) "
+ "`UnityEditor.InitializeOnLoadMethodAttribute`."
);
}
[UnityEditor.Callbacks.DidReloadScripts]
private static void OnEditorDidReloadScripts() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=4) "
+ "`UnityEditor.Callbacks.DidReloadScriptsAttribute`."
);
}
/// <summary> Called at runtime regardless if "Edit > Project Settings > Editor > Enter Play Mode Settings >
/// Reload Domain" or "Reload Scene" is on. </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnRuntimeInitializeOnLoadMethod_SubsystemRegistration() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=8) "
+ "`UnityEngine.RuntimeInitializeOnLoadMethodAttribute` with parameter "
+ "`RuntimeInitializeLoadType.SubsystemRegistration`."
);
}
/// <summary> Called at runtime regardless if "Edit > Project Settings > Editor > Enter Play Mode Settings >
/// Reload Domain" or "Reload Scene" is on. </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
private static void OnRuntimeInitializeOnLoadMethod_AfterAssembliesLoaded() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=9) "
+ "`UnityEngine.RuntimeInitializeOnLoadMethodAttribute` with parameter "
+ "`RuntimeInitializeLoadType.AfterAssembliesLoaded`."
);
}
/// <summary> Called at runtime regardless if "Edit > Project Settings > Editor > Enter Play Mode Settings >
/// Reload Domain" or "Reload Scene" is on. </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)]
private static void OnRuntimeInitializeOnLoadMethod_BeforeSplashScreen() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=10) "
+ "`UnityEngine.RuntimeInitializeOnLoadMethodAttribute` with parameter "
+ "`RuntimeInitializeLoadType.BeforeSplashScreen`."
);
}
/// <summary> Called at runtime regardless if "Edit > Project Settings > Editor > Enter Play Mode Settings >
/// Reload Domain" or "Reload Scene" is on. </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void OnRuntimeInitializeOnLoadMethod_BeforeSceneLoad() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=11) "
+ "`UnityEngine.RuntimeInitializeOnLoadMethodAttribute` with parameter "
+ "`RuntimeInitializeLoadType.BeforeSceneLoad`."
);
}
/// <summary> Called at runtime regardless if "Edit > Project Settings > Editor > Enter Play Mode Settings >
/// Reload Domain" or "Reload Scene" is on. </summary>
[RuntimeInitializeOnLoadMethod]
private static void OnRuntimeInitializeOnLoadMethod() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=12) "
+ "`UnityEngine.RuntimeInitializeOnLoadMethodAttribute` with no parameters."
);
}
/// <summary> Called at runtime regardless if "Edit > Project Settings > Editor > Enter Play Mode Settings >
/// Reload Domain" or "Reload Scene" is on. </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void OnRuntimeInitializeOnLoadMethod_AfterSceneLoad() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=13) "
+ "`UnityEngine.RuntimeInitializeOnLoadMethodAttribute` with parameter "
+ "`RuntimeInitializeLoadType.AfterSceneLoad`."
);
}
private static void OnBeforeSceneGui(UnityEditor.SceneView sceneView) {
if (wasOnBeforeSceneGuiCalled) return;
wasOnBeforeSceneGuiCalled = true;
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=21) "
+ "`UnityEditor.SceneView.beforeSceneGui` (first call, ignoring future calls)."
);
}
private static void OnEditorSceneManagerSceneOpened(
UnityEngine.SceneManagement.Scene scene, UnityEditor.SceneManagement.OpenSceneMode openSceneMode
) {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=?) "
+ "`UnityEditor.SceneManagement.EditorSceneManager.sceneOpened`."
);
}
~CallbackCheatSheet() {
Debug.Log(
$"CallbackCheatSheet: (order={callOrder++}, max=24) "
+ "Destructor."
);
if (instance == this) {
instance = null;
}
}
}
#endif // UNITY_EDITOR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment