123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- using System;
- using System.Collections.Generic;
- using System.Reflection;
- using SingularityGroup.HotReload.HarmonyLib;
- using UnityEditor;
- using UnityEditor.Compilation;
- using UnityEngine;
- namespace SingularityGroup.HotReload.Editor {
- using IndicationStatus = EditorIndicationState.IndicationStatus;
-
- // Before Unity 2021.3, value is 0 or 1. Only value of 1 is a problem.
- // From Unity 2021.3 onwards, the key is "kAutoRefreshMode".
- // kAutoRefreshMode options are:
- // 0: disabled
- // 1: enabled
- // 2: enabled outside playmode
- //
- // On newer Unity versions, Visual Studio is also checking the kAutoRefresh setting (but it should only check kAutoRefreshMode).
- // This is making hot reload unusable and so this setting needs to also get disabled.
- internal static class AutoRefreshSettingChecker {
- const string autoRefreshKey = "kAutoRefresh";
- #if UNITY_2021_3_OR_NEWER
- const string autoRefreshModeKey = "kAutoRefreshMode";
- #endif
-
- const int desiredValue = 0;
- public static void Apply() {
- if (HotReloadPrefs.AppliedAutoRefresh) {
- return;
- }
-
- var defaultPref = EditorPrefs.GetInt(autoRefreshKey);
- HotReloadPrefs.DefaultAutoRefresh = defaultPref;
- EditorPrefs.SetInt(autoRefreshKey, desiredValue);
-
- #if UNITY_2021_3_OR_NEWER
- var defaultModePref = EditorPrefs.GetInt(autoRefreshModeKey);
- HotReloadPrefs.DefaultAutoRefreshMode = defaultModePref;
- EditorPrefs.SetInt(autoRefreshModeKey, desiredValue);
- #endif
- HotReloadPrefs.AppliedAutoRefresh = true;
- }
- public static void Check() {
- if (!HotReloadPrefs.AppliedAutoRefresh) {
- return;
- }
-
- if (EditorPrefs.GetInt(autoRefreshKey) != desiredValue) {
- HotReloadPrefs.DefaultAutoRefresh = -1;
- }
-
- #if UNITY_2021_3_OR_NEWER
- if (EditorPrefs.GetInt(autoRefreshModeKey) != desiredValue) {
- HotReloadPrefs.DefaultAutoRefreshMode = -1;
- }
- #endif
- }
- public static void Reset() {
- if (!HotReloadPrefs.AppliedAutoRefresh) {
- return;
- }
-
- if (EditorPrefs.GetInt(autoRefreshKey) == desiredValue
- && HotReloadPrefs.DefaultAutoRefresh != -1
- ) {
- EditorPrefs.SetInt(autoRefreshKey, HotReloadPrefs.DefaultAutoRefresh);
- }
- HotReloadPrefs.DefaultAutoRefresh = -1;
-
- #if UNITY_2021_3_OR_NEWER
- if (EditorPrefs.GetInt(autoRefreshModeKey) == desiredValue
- && HotReloadPrefs.DefaultAutoRefreshMode != -1
- ) {
- EditorPrefs.SetInt(autoRefreshModeKey, HotReloadPrefs.DefaultAutoRefreshMode);
- }
- HotReloadPrefs.DefaultAutoRefreshMode = -1;
- #endif
- HotReloadPrefs.AppliedAutoRefresh = false;
- }
- }
-
- internal static class ScriptCompilationSettingChecker {
- const string scriptCompilationKey = "ScriptCompilationDuringPlay";
-
- const int recompileAndContinuePlaying = 0;
- static int? recompileAfterFinishedPlaying = (int?)typeof(EditorWindow).Assembly.GetType("UnityEditor.ScriptChangesDuringPlayOptions")?
- .GetField("RecompileAfterFinishedPlaying", BindingFlags.Static | BindingFlags.Public)?
- .GetValue(null);
- public static void Apply() {
- if (HotReloadPrefs.AppliedScriptCompilation) {
- return;
- }
-
- var defaultPref = EditorPrefs.GetInt(scriptCompilationKey);
- HotReloadPrefs.DefaultScriptCompilation = defaultPref;
- EditorPrefs.SetInt(scriptCompilationKey, GetRecommendedAutoScriptCompilationKey());
- HotReloadPrefs.AppliedScriptCompilation = true;
- }
-
- public static void Check() {
- if (!HotReloadPrefs.AppliedScriptCompilation) {
- return;
- }
- if (EditorPrefs.GetInt(scriptCompilationKey) != GetRecommendedAutoScriptCompilationKey()) {
- HotReloadPrefs.DefaultScriptCompilation = -1;
- }
- }
- public static void Reset() {
- if (!HotReloadPrefs.AppliedScriptCompilation) {
- return;
- }
- if (EditorPrefs.GetInt(scriptCompilationKey) == GetRecommendedAutoScriptCompilationKey()
- && HotReloadPrefs.DefaultScriptCompilation != -1
- ) {
- EditorPrefs.SetInt(scriptCompilationKey, HotReloadPrefs.DefaultScriptCompilation);
- }
- HotReloadPrefs.DefaultScriptCompilation = -1;
-
- HotReloadPrefs.AppliedScriptCompilation = false;
- }
-
- static int GetRecommendedAutoScriptCompilationKey() {
- // In some projects due to an unknown reason both "RecompileAndContinuePlaying" and "StopPlayingAndRecompile" cause issues
- // We were unable to identify the cause and therefore we always try to default to "RecompileAfterFinishedPlaying"
- // The exact issue users are experiencing is that domain reload happens shortly after entering play mode causing nullrefs
- return recompileAfterFinishedPlaying ?? recompileAndContinuePlaying;
- }
- }
-
- internal static class PlaymodeTintSettingChecker {
- private static readonly Color unsupportedPlaymodeColor = new Color(1f, 0.8f, 0f, 1f);
- private static readonly Color compilePlaymodeErrorColor = new Color(1f, 0.7f, 0.7f, 1f);
-
- public static void Apply() {
- if (HotReloadPrefs.AppliedEditorTint != null || !UnitySettingsHelper.I.playmodeTintSupported) {
- return;
- }
- var defaultPref = HotReloadPrefs.DefaultEditorTint ?? UnitySettingsHelper.I.GetCurrentPlaymodeColor();
- if (defaultPref == null) {
- return;
- }
- HotReloadPrefs.DefaultEditorTint = defaultPref.Value;
- var currentPlaymodeTint = GetModifiedPlaymodeTint() ?? defaultPref.Value;
- SetPlaymodeTint(currentPlaymodeTint);
- }
-
- public static void Check() {
- if (HotReloadPrefs.AppliedEditorTint == null || !UnitySettingsHelper.I.playmodeTintSupported) {
- return;
- }
- // if user modifies the settings manually, prevent the setting to be changed
- if (HotReloadPrefs.DefaultEditorTint == null || UnitySettingsHelper.I.GetCurrentPlaymodeColor() != HotReloadPrefs.AppliedEditorTint) {
- HotReloadPrefs.DefaultEditorTint = null;
- return;
- }
- var color = GetModifiedPlaymodeTint();
- if (color != null && color != HotReloadPrefs.AppliedEditorTint) {
- SetPlaymodeTint(color.Value);
- }
- }
-
- public static void Reset() {
- if (HotReloadPrefs.AppliedEditorTint == null || !UnitySettingsHelper.I.playmodeTintSupported) {
- return;
- }
- var color = HotReloadPrefs.DefaultEditorTint;
- if (color != null && UnitySettingsHelper.I.GetCurrentPlaymodeColor() == HotReloadPrefs.AppliedEditorTint) {
- SetPlaymodeTint(color.Value);
- }
-
- HotReloadPrefs.DefaultEditorTint = null;
- HotReloadPrefs.AppliedEditorTint = null;
- }
-
-
- private static void SetPlaymodeTint(Color color) {
- UnitySettingsHelper.I.SetPlaymodeTint(color);
- HotReloadPrefs.AppliedEditorTint = color;
- }
- private static Color? GetModifiedPlaymodeTint() {
- switch (EditorIndicationState.CurrentIndicationStatus) {
- case IndicationStatus.CompileErrors:
- return compilePlaymodeErrorColor;
- case IndicationStatus.Unsupported:
- return unsupportedPlaymodeColor;
- default:
- return HotReloadPrefs.DefaultEditorTint;
- }
- }
- }
-
- internal static class CompileMethodDetourer {
- static bool detouredMethod;
- static List<IDisposable> reverters = new List<IDisposable>();
- public static void Apply() {
- if (detouredMethod) {
- return;
- }
- detouredMethod = true;
- var originAssetRefresh = typeof(AssetDatabase).GetMethod(nameof(AssetDatabase.Refresh), Type.EmptyTypes);
- var targetAssetRefresh = typeof(CompileMethodDetourer).GetMethod(nameof(DetouredAssetRefresh));
- DetourMethod(originAssetRefresh, targetAssetRefresh);
-
- var originAssetRefreshWithParams = typeof(AssetDatabase).GetMethod(nameof(AssetDatabase.Refresh), new[] { typeof(ImportAssetOptions) });
- var targetAssetRefreshWithParams = typeof(CompileMethodDetourer).GetMethod(nameof(DetouredAssetRefresh));
- DetourMethod(originAssetRefreshWithParams, targetAssetRefreshWithParams);
-
- var originCompilation = typeof(CompilationPipeline).GetMethod(nameof(CompilationPipeline.RequestScriptCompilation), Type.EmptyTypes);
- var targetCompilation = typeof(CompileMethodDetourer).GetMethod(nameof(RequestScriptCompilation));
- DetourMethod(originCompilation, targetCompilation);
- }
- static void DetourMethod(MethodBase original, MethodBase replacement) {
- DetourResult result;
- DetourApi.DetourMethod(original, replacement, out result);
- if (!result.success) {
- Debug.LogWarning($"Detouring {original.Name} method failed. {result.exception?.GetType()} {result.exception}");
- } else {
- reverters.Add(result.patchRecord);
- }
- }
- public static void Reset() {
- if (!detouredMethod) {
- return;
- }
- detouredMethod = false;
- // don't revert for now
- // foreach (var reverter in reverters) {
- // try {
- // reverter.Dispose();
- // } catch (Exception exc) {
- // Debug.LogWarning($"Reverting method detour failed. {exc.GetType()} {exc}");
- // }
- // }
- reverters.Clear();
- // hack to undo changes to Editor assemblies.
- // Doing this when starting hotreload cancels the start
- // Exit playmode right away to prevent delayed compiling
- EditorApplication.isPlaying = false;
-
- EditorApplication.ExecuteMenuItem("Assets/Refresh");
- EditorUtility.RequestScriptReload(); //this will undo the modifications to the assemblies
- }
- public static void DetouredAssetRefresh(ImportAssetOptions options) { }
- public static void RequestScriptCompilation() { }
- }
- }
|