updated: hot reload to 1.13.7

This commit is contained in:
Chris
2025-07-09 23:24:14 -04:00
parent 236f9ea5df
commit 7641b83b6a
45 changed files with 2018 additions and 462 deletions

View File

@@ -1,6 +1,86 @@
using System;
using UnityEngine.Analytics;
#if UNITY_6000_0_OR_NEWER
namespace UnityEditor.VSAttribution.HotReload
{
internal static class VSAttribution
{
const int k_VersionId = 4;
const int k_MaxEventsPerHour = 10;
const int k_MaxNumberOfElements = 1000;
const string k_VendorKey = "unity.vsp-attribution";
const string k_EventName = "vspAttribution";
[Serializable]
private class VSAttributionData : IAnalytic.IData {
public string actionName;
public string partnerName;
public string customerUid;
public string extra;
}
[AnalyticInfo(k_EventName, k_VendorKey, k_VersionId, k_MaxEventsPerHour, k_MaxNumberOfElements)]
class VSAttributionAnalytics : IAnalytic {
private VSAttributionData m_Data;
private VSAttributionAnalytics(
string actionName,
string partnerName,
string customerUid,
string extra
) {
this.m_Data = new VSAttributionData {
actionName = actionName,
partnerName = partnerName,
customerUid = customerUid,
extra = extra
};
}
public bool TryGatherData(out IAnalytic.IData data, out Exception error) {
data = this.m_Data;
error = null;
return this.m_Data != null;
}
public static AnalyticsResult SendEvent(
string actionName,
string partnerName,
string customerUid,
string extra
) {
return EditorAnalytics.SendAnalytic(new VSAttributionAnalytics(actionName,
partnerName,
customerUid,
extra
));
}
}
/// <summary>
/// Registers and attempts to send a Verified Solutions Attribution event.
/// </summary>
/// <param name="actionName">Name of the action, identifying a place this event was called from.</param>
/// <param name="partnerName">Identifiable Verified Solutions Partner's name.</param>
/// <param name="customerUid">Unique identifier of the customer using Partner's Verified Solution.</param>
public static AnalyticsResult SendAttributionEvent(string actionName, string partnerName, string customerUid)
{
try
{
return VSAttributionAnalytics.SendEvent(actionName, partnerName, customerUid, "{}");
}
catch
{
// Fail silently
return AnalyticsResult.AnalyticsDisabled;
}
}
}
}
#else
namespace UnityEditor.VSAttribution.HotReload
{
internal static class VSAttribution
@@ -66,3 +146,4 @@ namespace UnityEditor.VSAttribution.HotReload
}
}
}
#endif

View File

@@ -2,8 +2,11 @@ using System;
using System.Diagnostics;
using System.IO;
using System.Net;
#if UNITY_EDITOR_WIN
using System.Net.NetworkInformation;
#else
using System.Net.Sockets;
#endif
using System.Threading.Tasks;
using SingularityGroup.HotReload.Newtonsoft.Json;
using UnityEditor;
@@ -34,17 +37,19 @@ namespace SingularityGroup.HotReload.Editor.Cli {
/// </summary>
public static Task StartAsync() {
return StartAsync(
isReleaseMode: RequestHelper.IsReleaseMode(),
exposeServerToNetwork: HotReloadPrefs.ExposeServerToLocalNetwork,
allAssetChanges: HotReloadPrefs.AllAssetChanges,
createNoWindow: HotReloadPrefs.DisableConsoleWindow
createNoWindow: HotReloadPrefs.DisableConsoleWindow,
detailedErrorReporting: !HotReloadPrefs.DisableDetailedErrorReporting
);
}
internal static async Task StartAsync(bool exposeServerToNetwork, bool allAssetChanges, bool createNoWindow, LoginData loginData = null) {
internal static async Task StartAsync(bool exposeServerToNetwork, bool allAssetChanges, bool createNoWindow, bool isReleaseMode, bool detailedErrorReporting, LoginData loginData = null) {
var port = await Prepare().ConfigureAwait(false);
await ThreadUtility.SwitchToThreadPool();
StartArgs args;
if (TryGetStartArgs(UnityHelper.DataPath, exposeServerToNetwork, allAssetChanges, createNoWindow, loginData, port, out args)) {
if (TryGetStartArgs(UnityHelper.DataPath, exposeServerToNetwork, allAssetChanges, createNoWindow, isReleaseMode, detailedErrorReporting, loginData, port, out args)) {
await controller.Start(args);
}
}
@@ -65,7 +70,7 @@ namespace SingularityGroup.HotReload.Editor.Cli {
#pragma warning restore CS0649
}
static bool TryGetStartArgs(string dataPath, bool exposeServerToNetwork, bool allAssetChanges, bool createNoWindow, LoginData loginData, int port, out StartArgs args) {
static bool TryGetStartArgs(string dataPath, bool exposeServerToNetwork, bool allAssetChanges, bool createNoWindow, bool isReleaseMode, bool detailedErrorReporting, LoginData loginData, int port, out StartArgs args) {
string serverDir;
if(!CliUtils.TryFindServerDir(out serverDir)) {
Log.Warning($"Failed to start the Hot Reload Server. " +
@@ -114,7 +119,7 @@ namespace SingularityGroup.HotReload.Editor.Cli {
}
var searchAssemblies = string.Join(";", CodePatcher.I.GetAssemblySearchPaths());
var cliArguments = $@"-u ""{unityProjDir}"" -s ""{slnPath}"" -t ""{cliTempDir}"" -a ""{searchAssemblies}"" -ver ""{PackageConst.Version}"" -proc ""{Process.GetCurrentProcess().Id}"" -assets ""{allAssetChanges}"" -p ""{port}""";
var cliArguments = $@"-u ""{unityProjDir}"" -s ""{slnPath}"" -t ""{cliTempDir}"" -a ""{searchAssemblies}"" -ver ""{PackageConst.Version}"" -proc ""{Process.GetCurrentProcess().Id}"" -assets ""{allAssetChanges}"" -p ""{port}"" -r {isReleaseMode} -detailed-error-reporting {detailedErrorReporting}";
if (loginData != null) {
cliArguments += $@" -email ""{loginData.email}"" -pass ""{loginData.password}""";
}

View File

@@ -18,6 +18,7 @@ namespace SingularityGroup.HotReload.Editor {
public const string RecompileTroubleshootingURL = TroubleshootingURL + "#unity-recompiles-every-time-i-enterexit-playmode";
public const string FeaturesDocumentationURL = DocumentationURL + "/features";
public const string MultipleEditorsURL = DocumentationURL + "/multiple-editors";
public const string DebuggerURL = DocumentationURL + "/debugger";
public const string UndetectedChangesURL = DocumentationURL + "/getting-started#undetected-changes";
public const string VoteForAwardURL = "https://awards.unity.com/#best-development-tool";
public const string UnityStoreRateAppURL = "https://assetstore.unity.com/packages/slug/254358#reviews";

View File

@@ -18,7 +18,11 @@ using Task = System.Threading.Tasks.Task;
using System.Reflection;
using System.Runtime.CompilerServices;
using SingularityGroup.HotReload.Newtonsoft.Json;
using SingularityGroup.HotReload.ZXing;
using UnityEditor.Compilation;
using UnityEditor.UIElements;
using UnityEditorInternal;
using UnityEngine.UIElements;
[assembly: InternalsVisibleTo("SingularityGroup.HotReload.IntegrationTests")]
@@ -51,11 +55,43 @@ namespace SingularityGroup.HotReload.Editor {
internal static PatchStatus patchStatus = PatchStatus.None;
internal static event Action OnPatchHandled;
internal static event Action<(MethodPatchResponse, RegisterPatchesResult)> OnPatchHandled;
internal static Config config;
#if ODIN_INSPECTOR
internal static bool DrawPrefix(Sirenix.OdinInspector.Editor.InspectorProperty __instance) {
return !UnityFieldHelper.IsFieldHidden(__instance.ParentType, __instance.Name);
}
internal static MethodInfo OdinPropertyDrawPrefixInfo = typeof(EditorCodePatcher).GetMethod("DrawPrefix", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
#if UNITY_2021_1_OR_NEWER
internal static MethodInfo OdinPropertyDrawInfo = typeof(Sirenix.OdinInspector.Editor.InspectorProperty)?.GetMethod("Draw", 0, BindingFlags.Instance | BindingFlags.Public, null, new Type[]{}, null);
#else
internal static MethodInfo OdinPropertyDrawInfo = typeof(Sirenix.OdinInspector.Editor.InspectorProperty)?.GetMethod("Draw", BindingFlags.Instance | BindingFlags.Public, null, new Type[]{}, null);
#endif
internal static MethodInfo DrawOdinInspectorInfo = typeof(Sirenix.OdinInspector.Editor.OdinEditor)?.GetMethod("DrawOdinInspector", BindingFlags.NonPublic | BindingFlags.Instance);
#else
internal static MethodInfo OdinPropertyDrawPrefixInfo = null;
internal static MethodInfo OdinPropertyDrawInfo = null;
internal static MethodInfo DrawOdinInspectorInfo = null;
#endif
internal static MethodInfo GetDrawVInspectorInfo() {
// performance optimization
if (!Directory.Exists("Assets/vInspector")) {
return null;
}
try {
var t = Type.GetType("VInspector.AbstractEditor, VInspector");
return t?.GetMethod("OnInspectorGUI", BindingFlags.Public | BindingFlags.Instance);
} catch {
// ignore
}
return null;
}
internal static ICompileChecker compileChecker;
static bool quitting;
static EditorCodePatcher() {
@@ -77,6 +113,16 @@ namespace SingularityGroup.HotReload.Editor {
}
serverDownloader = new ServerDownloader();
serverDownloader.CheckIfDownloaded(HotReloadCli.controller);
SingularityGroup.HotReload.Demo.Demo.I = new EditorDemo();
if (HotReloadPrefs.DeactivateHotReload || new DirectoryInfo(Path.GetFullPath("..")).Name == "VP") {
ResetSettings();
return;
}
// ReSharper disable ExpressionIsAlwaysNull
UnityFieldHelper.Init(Log.Warning, HotReloadRunTab.Recompile, DrawOdinInspectorInfo, OdinPropertyDrawInfo, OdinPropertyDrawPrefixInfo, GetDrawVInspectorInfo(), typeof(UnityFieldDrawerPatchHelper));
timer = new Timer(OnIntervalThreaded, (Action) OnIntervalMainThread, 500, 500);
UpdateHost();
@@ -110,8 +156,7 @@ namespace SingularityGroup.HotReload.Editor {
};
DetectEditorStart();
DetectVersionUpdate();
SingularityGroup.HotReload.Demo.Demo.I = new EditorDemo();
RecordActiveDaysForRateApp();
CodePatcher.I.fieldHandler = new FieldHandler(FieldDrawerUtil.StoreField, UnityFieldHelper.HideField, UnityFieldHelper.RegisterInspectorFieldAttributes);
if (EditorApplication.isPlayingOrWillChangePlaymode) {
CodePatcher.I.InitPatchesBlocked(patchesFilePath);
HotReloadTimelineHelper.InitPersistedEvents();
@@ -146,10 +191,25 @@ namespace SingularityGroup.HotReload.Editor {
HotReloadState.RecompiledUnsupportedChangesInPlaymode = false;
EditorApplication.isPlaying = true;
}
#if UNITY_2020_1_OR_NEWER
if (CompilationPipeline.codeOptimization != CodeOptimization.Release) {
HotReloadSuggestionsHelper.SetSuggestionInactive(HotReloadSuggestionKind.SwitchToDebugModeForInlinedMethods);
}
#endif
if (!HotReloadState.EditorCodePatcherInit) {
ClearPersistence();
HotReloadState.EditorCodePatcherInit = true;
}
CodePatcher.I.debuggerCompatibilityEnabled = !HotReloadPrefs.AutoDisableHotReloadWithDebugger;
}
public static void ResetSettingsOnQuit() {
static void ResetSettingsOnQuit() {
quitting = true;
ResetSettings();
}
static void ResetSettings() {
AutoRefreshSettingChecker.Reset();
ScriptCompilationSettingChecker.Reset();
PlaymodeTintSettingChecker.Reset();
@@ -321,15 +381,21 @@ namespace SingularityGroup.HotReload.Editor {
await FlushErrors();
_lastErrorFlush = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
public static bool disableServerLogs;
public static string lastCompileErrorLog;
static async Task FlushErrors() {
var response = await RequestHelper.RequestFlushErrors();
if (response == null) {
if (response == null || disableServerLogs) {
return;
}
foreach (var responseWarning in response.warnings) {
if (responseWarning.Contains("Scripts have compile errors")) {
Log.Error(responseWarning);
if (compileError) {
Log.Error(responseWarning);
} else {
lastCompileErrorLog = responseWarning;
}
} else {
Log.Warning(responseWarning);
}
@@ -345,6 +411,7 @@ namespace SingularityGroup.HotReload.Editor {
}
internal static bool firstPatchAttempted;
internal static bool loggedDebuggerRecompile;
static void OnIntervalMainThread() {
HotReloadSuggestionsHelper.Check();
@@ -364,6 +431,27 @@ namespace SingularityGroup.HotReload.Editor {
// Reset startup progress
startupProgress = null;
}
if (HotReloadPrefs.AutoDisableHotReloadWithDebugger && Debugger.IsAttached) {
if (!HotReloadState.ShowedDebuggerCompatibility) {
HotReloadSuggestionsHelper.SetSuggestionActive(HotReloadSuggestionKind.HotReloadWhileDebuggerIsAttached);
HotReloadState.ShowedDebuggerCompatibility = true;
}
if (CodePatcher.I.OriginalPatchMethods.Count() > 0) {
if (!Application.isPlaying) {
if (!loggedDebuggerRecompile) {
Log.Info("Debugger was attached. Hot Reload may interfere with your debugger session. Recompiling in order to get full debugger experience.");
loggedDebuggerRecompile = true;
}
HotReloadRunTab.Recompile();
HotReloadSuggestionsHelper.SetSuggestionInactive(HotReloadSuggestionKind.HotReloadedMethodsWhenDebuggerIsAttached);
} else {
HotReloadSuggestionsHelper.SetSuggestionActive(HotReloadSuggestionKind.HotReloadedMethodsWhenDebuggerIsAttached);
}
}
} else if (HotReloadSuggestionsHelper.CheckSuggestionActive(HotReloadSuggestionKind.HotReloadedMethodsWhenDebuggerIsAttached)) {
HotReloadSuggestionsHelper.SetSuggestionInactive(HotReloadSuggestionKind.HotReloadedMethodsWhenDebuggerIsAttached);
}
if(ServerHealthCheck.I.IsServerHealthy) {
// NOTE: avoid calling this method when HR is not running to avoid allocations
@@ -389,6 +477,11 @@ namespace SingularityGroup.HotReload.Editor {
if (HotReloadPrefs.AllAssetChanges) {
RequestHelper.PollAssetChanges(HandleAssetChange);
}
#if UNITY_2020_1_OR_NEWER
if (!disableInlineChecks) {
CheckInlinedMethods();
}
#endif
}
if (!ServerHealthCheck.I.IsServerHealthy) {
stopping = false;
@@ -401,6 +494,49 @@ namespace SingularityGroup.HotReload.Editor {
}
CheckEditorSettings();
}
#if UNITY_2020_1_OR_NEWER
//only disabled for integration tests
internal static bool disableInlineChecks = false;
internal static HashSet<MethodBase> inlinedMethodsFound = new HashSet<MethodBase>();
internal static void CheckInlinedMethods() {
if (CompilationPipeline.codeOptimization != CodeOptimization.Release) {
return;
}
HashSet<MethodBase> newInlinedMethods = null;
try {
foreach (var method in CodePatcher.I.OriginalPatchMethods) {
if (inlinedMethodsFound.Contains(method)) {
continue;
}
var isMethodSynthesized = method.Name.Contains("<") || method.DeclaringType?.Name.Contains("<") == true && method.Name == ".ctor";
if (!(method is ConstructorInfo) && !isMethodSynthesized && MethodUtils.IsMethodInlined(method)) {
if (newInlinedMethods == null) {
newInlinedMethods = new HashSet<MethodBase>();
}
newInlinedMethods.Add(method);
}
}
if (newInlinedMethods?.Count > 0) {
if (!HotReloadPrefs.LoggedInlinedMethodsDialogue) {
Log.Warning("Unity Editor inlines simple methods when it's in \"Release\" mode, which Hot Reload cannot patch.\n\nSwitch to Debug mode to avoid this problem, or let Hot Reload fully recompile Unity when this issue occurs.");
HotReloadPrefs.LoggedInlinedMethodsDialogue = true;
}
HotReloadTimelineHelper.CreateInlinedMethodsEntry(entryType: EntryType.Foldout, patchedMethodsDisplayNames: newInlinedMethods.Select(mb => $"{mb.DeclaringType?.Name}::{mb.Name}").ToArray());
if (HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately || UnityEditorInternal.InternalEditorUtility.isApplicationActive) {
TryRecompileUnsupportedChanges();
}
HotReloadSuggestionsHelper.SetSuggestionActive(HotReloadSuggestionKind.SwitchToDebugModeForInlinedMethods);
foreach (var newInlinedMethod in newInlinedMethods) {
inlinedMethodsFound.Add(newInlinedMethod);
}
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.Patching, StatEventType.Inlined)).Forget();
}
} catch (Exception e) {
Log.Warning($"Inline method checker ran into an exception. Please contact support with the exception message to investigate the problem. Exception: {e.Message}");
}
}
#endif
static void CheckEditorSettings() {
if (quitting) {
@@ -432,13 +568,14 @@ namespace SingularityGroup.HotReload.Editor {
static string[] assetExtensionBlacklist = new[] {
".cs",
// TODO add setting to allow scenes to get hot reloaded for users who collaborate (their scenes change externally)
// we can add setting to allow scenes to get hot reloaded for users who collaborate (their scenes change externally)
".unity",
// safer to ignore meta files completely until there's a use-case
".meta",
// debug files
".mdb",
".pdb",
".compute",
// ".shader", //use assetBlacklist instead
};
@@ -468,6 +605,10 @@ namespace SingularityGroup.HotReload.Editor {
if (Directory.Exists(assetPath)) {
return;
}
// ignore temp compile files
if (assetPath.Contains("UnityDirMonSyncFile") || assetPath.EndsWith("~", StringComparison.Ordinal)) {
return;
}
foreach (var compileFile in compileFiles) {
if (assetPath.EndsWith(compileFile, StringComparison.Ordinal)) {
HotReloadTimelineHelper.CreateErrorEventEntry($"errors: AssemblyFileEdit: Editing assembly files requires recompiling in Unity. in {assetPath}", entryType: EntryType.Foldout);
@@ -508,25 +649,47 @@ namespace SingularityGroup.HotReload.Editor {
}
}
}
var relativePath = GetRelativePath(assetPath, Path.GetFullPath("Assets"));
var relativePathPackages = GetRelativePath(assetPath, Path.GetFullPath("Packages"));
// ignore files outside assets and packages folders
if (relativePath.StartsWith("..", StringComparison.Ordinal)
&& relativePathPackages.StartsWith("..", StringComparison.Ordinal)
) {
var path = ToPath(assetPath);
if (path == null) {
return;
}
try {
if (!File.Exists(assetPath)) {
AssetDatabase.DeleteAsset(relativePath);
AssetDatabase.DeleteAsset(path);
} else {
AssetDatabase.ImportAsset(relativePath, ImportAssetOptions.ForceUpdate);
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
}
} catch (Exception e){
Log.Warning($"Refreshing asset at path: {assetPath} failed due to exception: {e}");
}
}
static string ToPath(string assetPath) {
var relativePath = GetRelativePath(assetPath, Path.GetFullPath("Assets"));
var relativePathPackages = GetRelativePath(assetPath, Path.GetFullPath("Packages"));
// ignore files outside assets and packages folders
if (relativePath.StartsWith("..", StringComparison.Ordinal)) {
relativePath = null;
}
if (relativePathPackages.StartsWith("..", StringComparison.Ordinal)) {
relativePathPackages = null;
#if UNITY_2021_1_OR_NEWER
// Might be inside a package "file:"
try {
foreach (var package in UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages()) {
if (assetPath.StartsWith(package.resolvedPath.Replace("\\", "/"), StringComparison.Ordinal)) {
relativePathPackages = $"Packages/{package.name}/{assetPath.Substring(package.resolvedPath.Length)}";
break;
}
}
} catch {
// ignore
}
#endif
}
return relativePath ?? relativePathPackages;
}
public static string GetRelativePath(string filespec, string folder) {
Uri pathUri = new Uri(filespec);
Uri folderUri = new Uri(folder);
@@ -551,30 +714,64 @@ namespace SingularityGroup.HotReload.Editor {
}
static void HandleResponseReceived(MethodPatchResponse response) {
HandleRemovedUnityMethods(response.removedMethod);
RegisterPatchesResult patchResult = null;
if (response.patches?.Length > 0) {
if (response.patches?.Length > 0
|| response.alteredFields.Length > 0
|| response.removedFieldInitializers.Length > 0
|| response.addedFieldInitializerInitializers.Length > 0
|| response.addedFieldInitializerFields.Length > 0
) {
LogBurstHint(response);
patchResult = CodePatcher.I.RegisterPatches(response, persist: true);
CodePatcher.I.SaveAppliedPatches(patchesFilePath).Forget();
}
if (patchResult?.inspectorModified == true) {
// repaint all views calls all gui callbacks but doesn't rebuild the visual tree
// which is needed to hide removed fields
UnityFieldDrawerPatchHelper.repaintVisualTree = true;
InternalEditorUtility.RepaintAllViews();
}
var partiallySupportedChangesFiltered = new List<PartiallySupportedChange>(response.partiallySupportedChanges ?? Array.Empty<PartiallySupportedChange>());
partiallySupportedChangesFiltered.RemoveAll(x => !HotReloadTimelineHelper.GetPartiallySupportedChangePref(x));
if (!HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported && partiallySupportedChangesFiltered.Remove(PartiallySupportedChange.AddMonobehaviourMethod)) {
if (HotReloadSuggestionsHelper.CanShowServerSuggestion(HotReloadSuggestionKind.AddMonobehaviourMethod)) {
HotReloadSuggestionsHelper.SetServerSuggestionShown(HotReloadSuggestionKind.AddMonobehaviourMethod);
}
}
var failuresDeduplicated = new HashSet<string>(response.failures ?? Array.Empty<string>());
_compileError = response.failures?.Any(failure => failure.Contains("error CS")) ?? false;
_applyingFailed = response.failures?.Length > 0 || patchResult?.patchFailures.Count > 0;
_appliedPartially = !_applyingFailed && partiallySupportedChangesFiltered.Count > 0;
_appliedUndetected = (patchResult == null || patchResult.patchedMethods.Count == 0) && response.removedMethod.Length == 0;
foreach (var hotReloadSuggestionKind in response.suggestions) {
if (HotReloadSuggestionsHelper.CanShowServerSuggestion(hotReloadSuggestionKind)) {
HotReloadSuggestionsHelper.SetServerSuggestionShown(hotReloadSuggestionKind);
}
}
var allMethods = patchResult?.patchedSMethods.Select(m => GetExtendedMethodName(m));
if (allMethods == null) {
allMethods = response.removedMethod.Select(m => GetExtendedMethodName(m)).Distinct(StringComparer.OrdinalIgnoreCase);
allMethods = response.removedMethod?.Select(m => GetExtendedMethodName(m)).Distinct(StringComparer.OrdinalIgnoreCase) ?? Array.Empty<string>();
} else {
allMethods = allMethods.Concat(response.removedMethod.Select(m => GetExtendedMethodName(m))).Distinct(StringComparer.OrdinalIgnoreCase);
allMethods = allMethods.Concat(response.removedMethod?.Select(m => GetExtendedMethodName(m)) ?? Array.Empty<string>()).Distinct(StringComparer.OrdinalIgnoreCase);
}
var allFields = (patchResult?.addedFields.Select(f => GetExtendedFieldName(f)) ?? Array.Empty<string>())
.Concat(response.alteredFields?.Select(f => GetExtendedFieldName(f)).Distinct(StringComparer.OrdinalIgnoreCase) ?? Array.Empty<string>())
.Concat(response.patches?.SelectMany(p => p?.propertyAttributesFieldUpdated ?? Array.Empty<SField>()).Select(f => GetExtendedFieldName(f)).Distinct(StringComparer.OrdinalIgnoreCase) ?? Array.Empty<string>())
.Distinct(StringComparer.OrdinalIgnoreCase);
var patchedMembersDisplayNames = allMethods.Concat(allFields).ToArray();
_compileError = response.failures?.Any(failure => failure.Contains("error CS")) ?? false;
_applyingFailed = response.failures?.Length > 0 || patchResult?.patchFailures.Count > 0 || patchResult?.patchExceptions.Count > 0;
_appliedPartially = !_applyingFailed && partiallySupportedChangesFiltered.Count > 0;
_appliedUndetected = patchedMembersDisplayNames.Length == 0;
if (!_compileError) {
lastCompileErrorLog = null;
}
var autoRecompiled = false;
if (_compileError) {
HotReloadTimelineHelper.EventsTimeline.RemoveAll(e => e.alertType == AlertType.CompileError);
foreach (var failure in failuresDeduplicated) {
@@ -582,6 +779,13 @@ namespace SingularityGroup.HotReload.Editor {
HotReloadTimelineHelper.CreateErrorEventEntry(failure);
}
}
if (lastCompileErrorLog != null) {
Log.Error(lastCompileErrorLog);
lastCompileErrorLog = null;
}
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.Reload, StatEventType.CompileError), new EditorExtraData {
{ StatKey.PatchId, response.id },
}).Forget();
} else if (_applyingFailed) {
if (partiallySupportedChangesFiltered.Count > 0) {
foreach (var responsePartiallySupportedChange in partiallySupportedChangesFiltered) {
@@ -598,24 +802,41 @@ namespace SingularityGroup.HotReload.Editor {
HotReloadTimelineHelper.CreatePatchFailureEventEntry(error, methodName: GetMethodName(method), methodSimpleName: method.simpleName, entryType: EntryType.Child);
}
}
HotReloadTimelineHelper.CreateReloadFinishedWithWarningsEventEntry(patchedMethodsDisplayNames: allMethods.ToArray());
if (patchResult?.patchExceptions.Count > 0) {
foreach (var error in patchResult.patchExceptions) {
HotReloadTimelineHelper.CreateErrorEventEntry(error, entryType: EntryType.Child);
}
}
HotReloadTimelineHelper.CreateReloadFinishedWithWarningsEventEntry(patchedMembersDisplayNames: patchedMembersDisplayNames);
HotReloadSuggestionsHelper.SetSuggestionsShown(HotReloadSuggestionKind.UnsupportedChanges);
if (HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately || UnityEditorInternal.InternalEditorUtility.isApplicationActive) {
TryRecompileUnsupportedChanges();
autoRecompiled = TryRecompileUnsupportedChanges();
}
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.Reload, StatEventType.Failure), new EditorExtraData {
{ StatKey.PatchId, response.id },
}).Forget();
} else if (_appliedPartially) {
foreach (var responsePartiallySupportedChange in partiallySupportedChangesFiltered) {
HotReloadTimelineHelper.CreatePartiallyAppliedEventEntry(responsePartiallySupportedChange, entryType: EntryType.Child, detailed: false);
}
HotReloadTimelineHelper.CreateReloadPartiallyAppliedEventEntry(patchedMethodsDisplayNames: allMethods.ToArray());
HotReloadTimelineHelper.CreateReloadPartiallyAppliedEventEntry(patchedMethodsDisplayNames: patchedMembersDisplayNames);
if (HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately || UnityEditorInternal.InternalEditorUtility.isApplicationActive) {
TryRecompileUnsupportedChanges();
autoRecompiled = TryRecompileUnsupportedChanges();
}
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.Reload, StatEventType.Partial), new EditorExtraData {
{ StatKey.PatchId, response.id },
}).Forget();
} else if (_appliedUndetected) {
HotReloadTimelineHelper.CreateReloadUndetectedChangeEventEntry();
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.Reload, StatEventType.Undetected), new EditorExtraData {
{ StatKey.PatchId, response.id },
}).Forget();
} else {
HotReloadTimelineHelper.CreateReloadFinishedEventEntry(patchedMethodsDisplayNames: allMethods.ToArray());
HotReloadTimelineHelper.CreateReloadFinishedEventEntry(patchedMethodsDisplayNames: patchedMembersDisplayNames);
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.Reload, StatEventType.Finished), new EditorExtraData {
{ StatKey.PatchId, response.id },
}).Forget();
}
// When patching different assembly, compile error will get removed, even though it's still there
@@ -623,6 +844,34 @@ namespace SingularityGroup.HotReload.Editor {
if (!_compileError) {
HotReloadTimelineHelper.EventsTimeline.RemoveAll(x => x.alertType == AlertType.CompileError);
}
foreach (string responseFailure in response.failures) {
if (responseFailure.Contains("error CS")) {
Log.Error(responseFailure);
} else if (autoRecompiled) {
Log.Info(responseFailure);
} else {
Log.Warning(responseFailure);
}
}
if (patchResult?.patchFailures.Count > 0) {
foreach (var patchResultPatchFailure in patchResult.patchFailures) {
if (autoRecompiled) {
Log.Info(patchResultPatchFailure.Item2);
} else {
Log.Warning(patchResultPatchFailure.Item2);
}
}
}
if (patchResult?.patchExceptions.Count > 0) {
foreach (var patchResultPatchException in patchResult.patchExceptions) {
if (autoRecompiled) {
Log.Info(patchResultPatchException);
} else {
Log.Warning(patchResultPatchException);
}
}
}
// attempt to recompile if previous Unity compilation had compilation errors
// because new changes might've fixed those errors
@@ -634,7 +883,7 @@ namespace SingularityGroup.HotReload.Editor {
HotReloadWindow.Current.Repaint();
}
HotReloadState.LastPatchId = response.id;
OnPatchHandled?.Invoke();
OnPatchHandled?.Invoke((response, patchResult));
}
static string GetExtendedMethodName(SMethod method) {
@@ -649,6 +898,15 @@ namespace SingularityGroup.HotReload.Editor {
}
return method.simpleName;
}
static string GetExtendedFieldName(SField field) {
string typeName = field.declaringType.typeName;
var simpleTypeIndex = typeName.LastIndexOf(".", StringComparison.Ordinal);
if (simpleTypeIndex > 0) {
typeName = typeName.Substring(simpleTypeIndex + 1);
}
return $"{typeName}::{field.fieldName}";
}
static string GetMethodName(SMethod method) {
var spaceIndex = method.displayName.IndexOf(" ", StringComparison.Ordinal);
@@ -658,23 +916,6 @@ namespace SingularityGroup.HotReload.Editor {
return method.displayName;
}
static void HandleRemovedUnityMethods(SMethod[] removedMethods) {
if (removedMethods == null) {
return;
}
foreach(var sMethod in removedMethods) {
try {
var candidates = CodePatcher.I.SymbolResolver.Resolve(sMethod.assemblyName.Replace(".dll", ""));
var asm = candidates[0];
var module = asm.GetLoadedModules()[0];
var oldMethod = module.ResolveMethod(sMethod.metadataToken);
UnityEventHelper.RemoveUnityEventMethod(oldMethod);
} catch(Exception ex) {
Log.Warning("Encountered exception in RemoveUnityEventMethod: {0} {1}", ex.GetType().Name, ex.Message);
}
}
}
[Conditional("UNITY_2022_2_OR_NEWER")]
static void LogBurstHint(MethodPatchResponse response) {
@@ -700,6 +941,10 @@ namespace SingularityGroup.HotReload.Editor {
firstPatchAttempted = false;
RequestCompile().Forget();
}
ClearPersistence();
}
static void ClearPersistence() {
Task.Run(() => File.Delete(patchesFilePath));
HotReloadTimelineHelper.ClearPersistance();
}
@@ -710,7 +955,12 @@ namespace SingularityGroup.HotReload.Editor {
try {
await RequestHelper.RequestClearPatches();
await ProjectGeneration.ProjectGeneration.GenerateSlnAndCsprojFiles(Application.dataPath);
await RequestHelper.RequestCompile();
await RequestHelper.RequestCompile(scenePath => {
var path = ToPath(scenePath);
if (File.Exists(scenePath) && path != null) {
AssetDatabase.ImportAsset(path, ImportAssetOptions.Default);
}
});
} finally {
requestingCompile = false;
}
@@ -757,12 +1007,15 @@ namespace SingularityGroup.HotReload.Editor {
var exposeToNetwork = HotReloadPrefs.ExposeServerToLocalNetwork;
var allAssetChanges = HotReloadPrefs.AllAssetChanges;
var disableConsoleWindow = HotReloadPrefs.DisableConsoleWindow;
var isReleaseMode = RequestHelper.IsReleaseMode();
var detailedErrorReporting = !HotReloadPrefs.DisableDetailedErrorReporting;
CodePatcher.I.ClearPatchedMethods();
RecordActiveDaysForRateApp();
try {
requestingStart = true;
startupProgress = Tuple.Create(0f, "Starting Hot Reload");
serverStartedAt = DateTime.UtcNow;
await HotReloadCli.StartAsync(exposeToNetwork, allAssetChanges, disableConsoleWindow, loginData).ConfigureAwait(false);
await HotReloadCli.StartAsync(exposeToNetwork, allAssetChanges, disableConsoleWindow, isReleaseMode, detailedErrorReporting, loginData).ConfigureAwait(false);
}
catch (Exception ex) {
ThreadUtility.LogException(ex);
@@ -773,10 +1026,14 @@ namespace SingularityGroup.HotReload.Editor {
}
private static bool requestingStop;
internal static async Task StopCodePatcher() {
internal static async Task StopCodePatcher(bool recompileOnDone = false) {
stopping = true;
starting = false;
if (requestingStop) {
if (recompileOnDone) {
await ThreadUtility.SwitchToMainThread();
HotReloadRunTab.Recompile();
}
return;
}
CodePatcher.I.ClearPatchedMethods();
@@ -786,6 +1043,9 @@ namespace SingularityGroup.HotReload.Editor {
await HotReloadCli.StopAsync().ConfigureAwait(false);
serverStoppedAt = DateTime.UtcNow;
await ThreadUtility.SwitchToMainThread();
if (recompileOnDone) {
HotReloadRunTab.Recompile();
}
startupProgress = null;
}
catch (Exception ex) {
@@ -818,7 +1078,7 @@ namespace SingularityGroup.HotReload.Editor {
internal static bool DownloadRequired => DownloadProgress < 1f;
internal static bool DownloadStarted => serverDownloader.Started;
internal static bool RequestingDownloadAndRun => requestingDownloadAndRun;
internal static async Task<bool> DownloadAndRun(LoginData loginData = null) {
internal static async Task<bool> DownloadAndRun(LoginData loginData = null, bool recompileOnDone = false) {
if (requestingDownloadAndRun) {
return false;
}
@@ -832,6 +1092,11 @@ namespace SingularityGroup.HotReload.Editor {
}
}
await StartCodePatcher(loginData);
await ThreadUtility.SwitchToMainThread();
if (HotReloadPrefs.DeactivateHotReload) {
HotReloadPrefs.DeactivateHotReload = false;
HotReloadRunTab.Recompile();
}
return true;
} finally {
requestingDownloadAndRun = false;
@@ -850,6 +1115,9 @@ namespace SingularityGroup.HotReload.Editor {
[CanBeNull] internal static LoginStatusResponse Status { get; private set; }
internal static void HandleStatus(LoginStatusResponse resp) {
if (resp == null) {
return;
}
Attribution.RegisterLogin(resp);
bool consumptionsChanged = Status?.freeSessionRunning != resp.freeSessionRunning || Status?.freeSessionEndTime != resp.freeSessionEndTime;
@@ -949,4 +1217,149 @@ namespace SingularityGroup.HotReload.Editor {
lastServerPoll = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
}
// IMPORTANT: don't change the names of the methods
internal static class UnityFieldDrawerPatchHelper {
internal static void PatchCustom(Rect contentRect, UnityEditor.Editor __instance) {
if (__instance.target) {
FieldDrawerUtil.DrawFromObject(__instance.target);
}
}
internal static void PatchDefault(UnityEditor.Editor __instance) {
if (__instance.target) {
FieldDrawerUtil.DrawFromObject(__instance.target);
}
}
internal static bool repaintVisualTree;
internal static void PatchFillDefaultInspector(VisualElement container, SerializedObject serializedObject, UnityEditor.Editor editor) {
HideChildren(container, serializedObject);
if (editor.target) {
var child = new IMGUIContainer((() =>
{
FieldDrawerUtil.DrawFromObject(editor.target);
if (repaintVisualTree) {
HideChildren(container, serializedObject);
ResetInvalidatedInspectorFields(container, serializedObject);
// Mark dirty to repaint the visual tree
container.MarkDirtyRepaint();
repaintVisualTree = false;
}
}));
child.name = "SingularityGroup.HotReload.FieldDrawer";
container.Add(child);
}
}
static List<VisualElement> childrenToRemove = new List<VisualElement>();
static void HideChildren(VisualElement container, SerializedObject serializedObject) {
if (container == null) {
return;
}
childrenToRemove.Clear();
foreach (var child in container.Children()) {
if (!(child is PropertyField propertyField)) {
continue;
}
try {
if (serializedObject != null && serializedObject.targetObject && UnityFieldHelper.IsFieldHidden(serializedObject.targetObject.GetType(), serializedObject.FindProperty(propertyField.bindingPath)?.name ?? "")) {
childrenToRemove.Add(child);
}
} catch (NullReferenceException) {
// serializedObject.targetObject throws nullref in cases where e.g. exising playmode
}
}
foreach (var child in childrenToRemove) {
container.Remove(child);
}
childrenToRemove.Clear();
}
static void ResetInvalidatedInspectorFields(VisualElement container, SerializedObject serializedObject) {
if (container == null || serializedObject == null) {
return;
}
foreach (var child in container.Children()) {
if (!(child is PropertyField propertyField)) {
continue;
}
try {
var prop = serializedObject.FindProperty(propertyField.bindingPath);
if (prop != null && serializedObject.targetObject && UnityFieldHelper.HasFieldInspectorCacheInvalidation(serializedObject.targetObject.GetType(), prop.name ?? "")) {
child.GetType().GetMethod("Reset", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(SerializedProperty) }, null)?.Invoke(child, new object[] { prop });
}
} catch (NullReferenceException) {
// serializedObject.targetObject throws nullref in cases where e.g. exising playmode
}
}
}
internal static bool GetHandlerPrefix(
SerializedProperty property,
ref object __result
) {
if (property == null || property.serializedObject == null || !property.serializedObject.targetObject) {
// do nothing
return true;
}
if (UnityFieldHelper.TryInvalidateFieldInspectorCache(property.serializedObject.targetObject.GetType(), property.name)) {
__result = null;
return false;
}
return true;
}
internal static bool GetFieldAttributesPrefix(
FieldInfo field,
ref List<PropertyAttribute> __result
) {
if (field == null) {
// do nothing
return true;
}
List<PropertyAttribute> result;
if (UnityFieldHelper.TryGetInspectorFieldAttributes(field, out result)) {
__result = result;
return false;
}
return true;
}
internal static bool PropertyFieldPrefix(
Rect position,
UnityEditor.SerializedProperty property,
GUIContent label,
bool includeChildren,
Rect visibleArea,
ref bool __result
) {
if (property == null || property.serializedObject == null || !property.serializedObject.targetObject) {
// do nothing
return true;
}
if (UnityFieldHelper.IsFieldHidden(property.serializedObject.targetObject.GetType(), property.name)) {
// make sure field doesn't take any space
__result = false;
return false; // Skip original method
}
return true; // Continue with original method
}
internal static bool GetHightPrefix(
UnityEditor.SerializedProperty property, GUIContent label, bool includeChildren,
ref float __result
) {
if (property == null || property.serializedObject == null || !property.serializedObject.targetObject) {
// do nothing
return true;
}
if (UnityFieldHelper.IsFieldHidden(property.serializedObject.targetObject.GetType(), property.name)) {
// make sure field doesn't take any space
__result = 0.0f;
return false; // Skip original method
}
return true; // Continue with original method
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using SingularityGroup.HotReload.DTO;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEditor.PackageManager;
@@ -11,19 +12,6 @@ using UnityEngine;
namespace SingularityGroup.HotReload.Editor {
public enum HotReloadSuggestionKind {
UnsupportedChanges,
UnsupportedPackages,
[Obsolete] SymbolicLinks,
AutoRecompiledWhenPlaymodeStateChanges,
UnityBestDevelopmentToolAward2023,
#if UNITY_2022_1_OR_NEWER
AutoRecompiledWhenPlaymodeStateChanges2022,
#endif
MultidimensionalArrays,
EditorsWithoutHRRunning,
}
internal static class HotReloadSuggestionsHelper {
internal static void SetSuggestionsShown(HotReloadSuggestionKind hotReloadSuggestionKind) {
if (EditorPrefs.GetBool($"HotReloadWindow.SuggestionsShown.{hotReloadSuggestionKind}")) {
@@ -42,6 +30,44 @@ namespace SingularityGroup.HotReload.Editor {
return EditorPrefs.GetBool($"HotReloadWindow.SuggestionsActive.{hotReloadSuggestionKind}");
}
internal static bool CheckSuggestionShown(HotReloadSuggestionKind hotReloadSuggestionKind) {
return EditorPrefs.GetBool($"HotReloadWindow.SuggestionsShown.{hotReloadSuggestionKind}");
}
internal static bool CanShowServerSuggestion(HotReloadSuggestionKind hotReloadSuggestionKind) {
if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerWithSideEffects) {
return !HotReloadState.ShowedFieldInitializerWithSideEffects;
} else if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited) {
return !HotReloadState.ShowedFieldInitializerExistingInstancesEdited;
} else if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerExistingInstancesUnedited) {
return !HotReloadState.ShowedFieldInitializerExistingInstancesUnedited;
} else if (hotReloadSuggestionKind == HotReloadSuggestionKind.AddMonobehaviourMethod) {
return !HotReloadState.ShowedAddMonobehaviourMethods;
} else if (hotReloadSuggestionKind == HotReloadSuggestionKind.DetailedErrorReportingIsEnabled) {
return !CheckSuggestionShown(HotReloadSuggestionKind.DetailedErrorReportingIsEnabled);
}
return false;
}
internal static void SetServerSuggestionShown(HotReloadSuggestionKind hotReloadSuggestionKind) {
if (hotReloadSuggestionKind == HotReloadSuggestionKind.DetailedErrorReportingIsEnabled) {
HotReloadSuggestionsHelper.SetSuggestionsShown(hotReloadSuggestionKind);
return;
}
if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerWithSideEffects) {
HotReloadState.ShowedFieldInitializerWithSideEffects = true;
} else if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited) {
HotReloadState.ShowedFieldInitializerExistingInstancesEdited = true;
} else if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerExistingInstancesUnedited) {
HotReloadState.ShowedFieldInitializerExistingInstancesUnedited = true;
} else if (hotReloadSuggestionKind == HotReloadSuggestionKind.AddMonobehaviourMethod) {
HotReloadState.ShowedAddMonobehaviourMethods = true;
} else {
return;
}
HotReloadSuggestionsHelper.SetSuggestionActive(hotReloadSuggestionKind);
}
// used for cases where suggestion might need to be shown more than once
internal static void SetSuggestionActive(HotReloadSuggestionKind hotReloadSuggestionKind) {
if (EditorPrefs.GetBool($"HotReloadWindow.SuggestionsShown.{hotReloadSuggestionKind}")) {
@@ -88,6 +114,7 @@ namespace SingularityGroup.HotReload.Editor {
internal static readonly OpenURLButton recompileTroubleshootingButton = new OpenURLButton("Docs", Constants.RecompileTroubleshootingURL);
internal static readonly OpenURLButton featuresDocumentationButton = new OpenURLButton("Docs", Constants.FeaturesDocumentationURL);
internal static readonly OpenURLButton multipleEditorsDocumentationButton = new OpenURLButton("Docs", Constants.MultipleEditorsURL);
internal static readonly OpenURLButton debuggerDocumentationButton = new OpenURLButton("More Info", Constants.DebuggerURL);
public static Dictionary<HotReloadSuggestionKind, AlertEntry> suggestionMap = new Dictionary<HotReloadSuggestionKind, AlertEntry> {
{ HotReloadSuggestionKind.UnityBestDevelopmentToolAward2023, new AlertEntry(
AlertType.Suggestion,
@@ -109,7 +136,7 @@ namespace SingularityGroup.HotReload.Editor {
{ HotReloadSuggestionKind.UnsupportedChanges, new AlertEntry(
AlertType.Suggestion,
"Which changes does Hot Reload support?",
"Hot Reload supports most code changes, but there are some limitations. Generally, changes to the method definition and body are allowed. Non-method changes (like adding/editing classes and fields) are not supported. See the documentation for the list of current features and our current roadmap",
"Hot Reload supports most code changes, but there are some limitations. Generally, changes to methods and fields are supported. Things like adding new types is not (yet) supported. See the documentation for the list of current features and our current roadmap",
actionData: () => {
GUILayout.Space(10f);
using (new EditorGUILayout.HorizontalScope()) {
@@ -123,7 +150,7 @@ namespace SingularityGroup.HotReload.Editor {
{ HotReloadSuggestionKind.UnsupportedPackages, new AlertEntry(
AlertType.Suggestion,
"Unsupported package detected",
"The following packages are only partially supported: ECS, Mirror, Fishnet, and Photon. Hot Reload will work in the project, but changes specific to those packages might not work. Contact us if these packages are a big part of your project",
"The following packages are only partially supported: ECS, Mirror, Fishnet, and Photon. Hot Reload will work in the project, but changes specific to those packages might not hot-reload",
iconType: AlertType.UnsupportedChange,
actionData: () => {
GUILayout.Space(10f);
@@ -138,7 +165,7 @@ namespace SingularityGroup.HotReload.Editor {
{ HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges, new AlertEntry(
AlertType.Suggestion,
"Unity recompiles on enter/exit play mode?",
"If you have an issue with the Unity Editor recompiling when the Play Mode state changes, please consult the documentation, and dont hesitate to reach out to us if you need assistance",
"If you have an issue with the Unity Editor recompiling when the Play Mode state changes, more info is available in the docs. Feel free to reach out if you require assistance. We'll be glad to help.",
actionData: () => {
GUILayout.Space(10f);
using (new EditorGUILayout.HorizontalScope()) {
@@ -162,8 +189,12 @@ namespace SingularityGroup.HotReload.Editor {
actionData: () => {
GUILayout.Space(10f);
using (new EditorGUILayout.HorizontalScope()) {
if (GUILayout.Button(" Use \"Sprite Atlas V2\" ")) {
EditorSettings.spritePackerMode = SpritePackerMode.SpriteAtlasV2;
if (GUILayout.Button(" Use \"Build Time Only Atlas\" ")) {
if (EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2) {
EditorSettings.spritePackerMode = SpritePackerMode.SpriteAtlasV2Build;
} else {
EditorSettings.spritePackerMode = SpritePackerMode.BuildTimeOnlyAtlas;
}
}
if (GUILayout.Button(" Open Settings ")) {
SettingsService.OpenProjectSettings("Project/Editor");
@@ -183,7 +214,7 @@ namespace SingularityGroup.HotReload.Editor {
{ HotReloadSuggestionKind.MultidimensionalArrays, new AlertEntry(
AlertType.Suggestion,
"Use jagged instead of multidimensional arrays",
"Hot Reload doesn't support multidimensional ([,]) arrays. Jagged arrays ([][]) are a better alternative, and Microsoft recommends using them instead",
"Hot Reload doesn't support methods with multidimensional arrays ([,]). You can work around this by using jagged arrays ([][])",
iconType: AlertType.UnsupportedChange,
actionData: () => {
GUILayout.Space(10f);
@@ -224,6 +255,190 @@ namespace SingularityGroup.HotReload.Editor {
entryType: EntryType.Foldout,
iconType: AlertType.UnsupportedChange
)},
// Not in use (never reported from the server)
{ HotReloadSuggestionKind.FieldInitializerWithSideEffects, new AlertEntry(
AlertType.Suggestion,
"Field initializer with side-effects detected",
"A field initializer update might have side effects, e.g. calling a method or creating an object.\n\nWhile Hot Reload does support this, it can sometimes be confusing when the initializer logic runs at 'unexpected times'.",
actionData: () => {
GUILayout.Space(10f);
using (new EditorGUILayout.HorizontalScope()) {
if (GUILayout.Button(" OK ")) {
SetSuggestionInactive(HotReloadSuggestionKind.FieldInitializerWithSideEffects);
}
GUILayout.FlexibleSpace();
if (GUILayout.Button(" Don't show again ")) {
SetSuggestionsShown(HotReloadSuggestionKind.FieldInitializerWithSideEffects);
SetSuggestionInactive(HotReloadSuggestionKind.FieldInitializerWithSideEffects);
}
}
},
timestamp: DateTime.Now,
entryType: EntryType.Foldout,
iconType: AlertType.Suggestion
)},
{ HotReloadSuggestionKind.DetailedErrorReportingIsEnabled, new AlertEntry(
AlertType.Suggestion,
"Detailed error reporting is enabled",
"When an error happens in Hot Reload, the exception stacktrace is sent as telemetry to help diagnose and fix the issue.\nThe exception stack trace is only included if it originated from the Hot Reload package or binary. Stacktraces from your own code are not sent.\nYou can disable detailed error reporting to prevent telemetry from including any information about your project.",
actionData: () => {
GUILayout.Space(10f);
using (new EditorGUILayout.HorizontalScope()) {
GUILayout.Space(4f);
if (GUILayout.Button(" OK ")) {
SetSuggestionInactive(HotReloadSuggestionKind.DetailedErrorReportingIsEnabled);
}
GUILayout.FlexibleSpace();
if (GUILayout.Button(" Disable ")) {
HotReloadSettingsTab.DisableDetailedErrorReportingInner(true);
SetSuggestionInactive(HotReloadSuggestionKind.DetailedErrorReportingIsEnabled);
}
GUILayout.Space(10f);
}
},
timestamp: DateTime.Now,
entryType: EntryType.Foldout,
iconType: AlertType.Suggestion
)},
// Not in use (never reported from the server)
{ HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited, new AlertEntry(
AlertType.Suggestion,
"Field initializer edit updated the value of existing class instances",
"By default, Hot Reload updates field values of existing object instances when new field initializer has constant value.\n\nIf you want to change this behavior, disable the \"Apply field initializer edits to existing class instances\" option in Settings or click the button below.",
actionData: () => {
GUILayout.Space(10f);
using (new EditorGUILayout.HorizontalScope()) {
if (GUILayout.Button(" Turn off ")) {
#pragma warning disable CS0618
HotReloadSettingsTab.ApplyApplyFieldInitializerEditsToExistingClassInstances(false);
#pragma warning restore CS0618
SetSuggestionInactive(HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited);
}
if (GUILayout.Button(" Open Settings ")) {
HotReloadWindow.Current.SelectTab(typeof(HotReloadSettingsTab));
}
GUILayout.FlexibleSpace();
if (GUILayout.Button(" Don't show again ")) {
SetSuggestionsShown(HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited);
SetSuggestionInactive(HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited);
}
}
},
timestamp: DateTime.Now,
entryType: EntryType.Foldout,
iconType: AlertType.Suggestion
)},
{ HotReloadSuggestionKind.FieldInitializerExistingInstancesUnedited, new AlertEntry(
AlertType.Suggestion,
"Field initializer edits don't apply to existing objects",
"By default, Hot Reload applies field initializer edits of existing fields only to new objects (newly instantiated classes), just like normal C#.\n\nFor rapid prototyping, you can use static fields which will update across all instances.",
actionData: () => {
GUILayout.Space(8f);
using (new EditorGUILayout.HorizontalScope()) {
if (GUILayout.Button(" OK ")) {
SetSuggestionsShown(HotReloadSuggestionKind.FieldInitializerExistingInstancesUnedited);
SetSuggestionInactive(HotReloadSuggestionKind.FieldInitializerExistingInstancesUnedited);
}
GUILayout.FlexibleSpace();
}
},
timestamp: DateTime.Now,
entryType: EntryType.Foldout,
iconType: AlertType.Suggestion
)},
{ HotReloadSuggestionKind.AddMonobehaviourMethod, new AlertEntry(
AlertType.Suggestion,
"New MonoBehaviour methods are not shown in the inspector",
"New methods in MonoBehaviours are not shown in the inspector until the script is recompiled. This is a limitation of Hot Reload handling of Unity's serialization system.\n\nYou can use the button below to auto recompile partially supported changes such as this one.",
actionData: () => {
GUILayout.Space(8f);
using (new EditorGUILayout.HorizontalScope()) {
if (GUILayout.Button(" OK ")) {
SetSuggestionInactive(HotReloadSuggestionKind.AddMonobehaviourMethod);
}
if (GUILayout.Button(" Auto Recompile ")) {
SetSuggestionInactive(HotReloadSuggestionKind.AddMonobehaviourMethod);
HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges = true;
HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported = true;
HotReloadRunTab.RecompileWithChecks();
}
GUILayout.FlexibleSpace();
if (GUILayout.Button(" Don't show again ")) {
SetSuggestionsShown(HotReloadSuggestionKind.AddMonobehaviourMethod);
SetSuggestionInactive(HotReloadSuggestionKind.AddMonobehaviourMethod);
}
}
},
timestamp: DateTime.Now,
entryType: EntryType.Foldout,
iconType: AlertType.Suggestion
)},
#if UNITY_2020_1_OR_NEWER
{ HotReloadSuggestionKind.SwitchToDebugModeForInlinedMethods, new AlertEntry(
AlertType.Suggestion,
"Switch code optimization to Debug Mode",
"In Release Mode some methods are inlined, which prevents Hot Reload from applying changes. A clear warning is always shown when this happens, but you can use Debug Mode to avoid the issue altogether",
actionData: () => {
GUILayout.Space(10f);
using (new EditorGUILayout.HorizontalScope()) {
if (GUILayout.Button(" Switch to Debug mode ") && HotReloadRunTab.ConfirmExitPlaymode("Switching code optimization will stop Play Mode.\n\nDo you wish to proceed?")) {
HotReloadRunTab.SwitchToDebugMode();
}
GUILayout.FlexibleSpace();
}
},
timestamp: DateTime.Now,
entryType: EntryType.Foldout,
iconType: AlertType.UnsupportedChange
)},
#endif
{ HotReloadSuggestionKind.HotReloadWhileDebuggerIsAttached, new AlertEntry(
AlertType.Suggestion,
"Hot Reload is disabled while a debugger is attached",
"Hot Reload automatically disables itself while a debugger is attached, as it can otherwise interfere with certain debugger features.\nWhile disabled, every code change will trigger a full Unity recompilation.\n\nYou can choose to keep Hot Reload enabled while a debugger is attached, though some features like debugger variable inspection might not always work as expected.",
actionData: () => {
GUILayout.Space(8f);
using (new EditorGUILayout.HorizontalScope()) {
if (GUILayout.Button(" Keep enabled during debugging ")) {
SetSuggestionInactive(HotReloadSuggestionKind.HotReloadWhileDebuggerIsAttached);
HotReloadPrefs.AutoDisableHotReloadWithDebugger = false;
}
GUILayout.FlexibleSpace();
debuggerDocumentationButton.OnGUI();
if (GUILayout.Button(" Don't show again ")) {
SetSuggestionsShown(HotReloadSuggestionKind.HotReloadWhileDebuggerIsAttached);
SetSuggestionInactive(HotReloadSuggestionKind.HotReloadWhileDebuggerIsAttached);
}
}
},
timestamp: DateTime.Now,
entryType: EntryType.Foldout,
iconType: AlertType.Suggestion
)},
{ HotReloadSuggestionKind.HotReloadedMethodsWhenDebuggerIsAttached, new AlertEntry(
AlertType.Suggestion,
"Hot Reload may interfere with your debugger session",
"Some debugger features, like variable inspection, might not work as expected for methods patched during the Hot Reload session. A full Unity recompile is required to get the full debugger experience.",
actionData: () => {
GUILayout.Space(8f);
using (new EditorGUILayout.HorizontalScope()) {
if (GUILayout.Button(" Recompile ")) {
SetSuggestionInactive(HotReloadSuggestionKind.HotReloadedMethodsWhenDebuggerIsAttached);
if (HotReloadRunTab.ConfirmExitPlaymode("Using the Recompile button will stop Play Mode.\n\nDo you wish to proceed?")) {
HotReloadRunTab.Recompile();
}
}
GUILayout.FlexibleSpace();
debuggerDocumentationButton.OnGUI();
GUILayout.Space(8f);
}
},
timestamp: DateTime.Now,
entryType: EntryType.Foldout,
iconType: AlertType.UnsupportedChange,
hasExitButton: false
)},
};
static ListRequest listRequest;
@@ -260,10 +475,11 @@ namespace SingularityGroup.HotReload.Editor {
unsupportedPackagesList == null)
{
unsupportedPackagesList = new List<string>();
var packages = listRequest.Result;
foreach (var packageInfo in packages) {
if (unsupportedPackages.Contains(packageInfo.name)) {
unsupportedPackagesList.Add(packageInfo.name);
if (listRequest.Result != null) {
foreach (var packageInfo in listRequest.Result) {
if (unsupportedPackages.Contains(packageInfo.name)) {
unsupportedPackagesList.Add(packageInfo.name);
}
}
}
if (unsupportedPackagesList.Count > 0) {
@@ -274,7 +490,7 @@ namespace SingularityGroup.HotReload.Editor {
CheckEditorsWithoutHR();
#if UNITY_2022_1_OR_NEWER
if (EditorSettings.spritePackerMode == SpritePackerMode.AlwaysOnAtlas) {
if (EditorSettings.spritePackerMode == SpritePackerMode.AlwaysOnAtlas || EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2) {
SetSuggestionsShown(HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges2022);
} else if (CheckSuggestionActive(HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges2022)) {
SetSuggestionInactive(HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges2022);
@@ -302,7 +518,7 @@ namespace SingularityGroup.HotReload.Editor {
checkingEditorsWihtoutHR = true;
var showSuggestion = await Task.Run(() => {
try {
var runningUnities = Process.GetProcessesByName("Unity").Length;
var runningUnities = Process.GetProcessesByName("Unity Editor").Length;
var runningPatchers = Process.GetProcessesByName("CodePatcherCLI").Length;
return runningPatchers > 0 && runningUnities > runningPatchers;
} catch (ArgumentException) {

View File

@@ -28,6 +28,7 @@ namespace SingularityGroup.HotReload.Editor {
internal enum AlertEntryType {
Error,
Failure,
InlinedMethod,
PatchApplied,
PartiallySupportedChange,
UndetectedChange,
@@ -57,9 +58,9 @@ namespace SingularityGroup.HotReload.Editor {
public readonly EntryType entryType;
public readonly bool detiled;
public readonly DateTime createdAt;
public readonly string[] patchedMethodsDisplayNames;
public readonly string[] patchedMembersDisplayNames;
public AlertData(AlertEntryType alertEntryType, DateTime createdAt, bool detiled = false, EntryType entryType = EntryType.Standalone, string errorString = null, string methodName = null, string methodSimpleName = null, PartiallySupportedChange partiallySupportedChange = default(PartiallySupportedChange), string[] patchedMethodsDisplayNames = null) {
public AlertData(AlertEntryType alertEntryType, DateTime createdAt, bool detiled = false, EntryType entryType = EntryType.Standalone, string errorString = null, string methodName = null, string methodSimpleName = null, PartiallySupportedChange partiallySupportedChange = default(PartiallySupportedChange), string[] patchedMembersDisplayNames = null) {
this.alertEntryType = alertEntryType;
this.createdAt = createdAt;
this.detiled = detiled;
@@ -68,7 +69,7 @@ namespace SingularityGroup.HotReload.Editor {
this.methodName = methodName;
this.methodSimpleName = methodSimpleName;
this.partiallySupportedChange = partiallySupportedChange;
this.patchedMethodsDisplayNames = patchedMethodsDisplayNames;
this.patchedMembersDisplayNames = patchedMembersDisplayNames;
}
}
@@ -120,9 +121,14 @@ namespace SingularityGroup.HotReload.Editor {
case AlertEntryType.Error:
CreateErrorEventEntry(errorString: alertData.errorString, entryType: alertData.entryType, createdAt: alertData.createdAt);
break;
#if UNITY_2020_1_OR_NEWER
case AlertEntryType.InlinedMethod:
CreateInlinedMethodsEntry(alertData.patchedMembersDisplayNames, alertData.entryType, alertData.createdAt);
break;
#endif
case AlertEntryType.Failure:
if (alertData.entryType == EntryType.Parent) {
CreateReloadFinishedWithWarningsEventEntry(createdAt: alertData.createdAt, patchedMethodsDisplayNames: alertData.patchedMethodsDisplayNames);
CreateReloadFinishedWithWarningsEventEntry(createdAt: alertData.createdAt, patchedMembersDisplayNames: alertData.patchedMembersDisplayNames);
} else {
CreatePatchFailureEventEntry(errorString: alertData.errorString, methodName: alertData.methodName, methodSimpleName: alertData.methodSimpleName, entryType: alertData.entryType, createdAt: alertData.createdAt);
}
@@ -130,12 +136,12 @@ namespace SingularityGroup.HotReload.Editor {
case AlertEntryType.PatchApplied:
CreateReloadFinishedEventEntry(
createdAt: alertData.createdAt,
patchedMethodsDisplayNames: alertData.patchedMethodsDisplayNames
patchedMethodsDisplayNames: alertData.patchedMembersDisplayNames
);
break;
case AlertEntryType.PartiallySupportedChange:
if (alertData.entryType == EntryType.Parent) {
CreateReloadPartiallyAppliedEventEntry(createdAt: alertData.createdAt, patchedMethodsDisplayNames: alertData.patchedMethodsDisplayNames);
CreateReloadPartiallyAppliedEventEntry(createdAt: alertData.createdAt, patchedMethodsDisplayNames: alertData.patchedMembersDisplayNames);
} else {
CreatePartiallyAppliedEventEntry(alertData.partiallySupportedChange, entryType: alertData.entryType, detailed: alertData.detiled, createdAt: alertData.createdAt);
}
@@ -184,17 +190,23 @@ namespace SingularityGroup.HotReload.Editor {
{ AlertType.UndetectedChange, "undetected" },
};
#pragma warning disable CS0612 // obsolete
public static Dictionary<PartiallySupportedChange, string> partiallySupportedChangeDescriptions = new Dictionary<PartiallySupportedChange, string> {
{PartiallySupportedChange.LambdaClosure, "A lambda closure was edited (captured variable was added or removed). Changes to it will only be visible to the next created lambda(s)."},
{PartiallySupportedChange.EditAsyncMethod, "An async method was edited. Changes to it will only be visible the next time this method is called."},
{PartiallySupportedChange.AddMonobehaviourMethod, "A new method was added. It will not show up in the Inspector until the next full recompilation."},
{PartiallySupportedChange.EditMonobehaviourField, "A field in a MonoBehaviour was removed or reordered. The inspector will not notice this change until the next full recompilation."},
{PartiallySupportedChange.EditCoroutine, "An IEnumerator/IEnumerable was edited. When used as a coroutine, changes to it will only be visible the next time the coroutine is created."},
{PartiallySupportedChange.EditGenericFieldInitializer, "A field initializer inside generic class was edited. Field initializer will not have any effect until the next full recompilation."},
{PartiallySupportedChange.AddEnumMember, "An enum member was added. ToString and other reflection methods work only after the next full recompilation. Additionally, changes to the enum order may not apply until you patch usages in other places of the code."},
{PartiallySupportedChange.EditFieldInitializer, "A field initializer was edited. Changes will only apply to new instances of that type, since the initializer for an object only runs when it is created."},
{PartiallySupportedChange.AddMethodWithAttributes, "A method with attributes was added. Method attributes will not have any effect until the next full recompilation."},
{PartiallySupportedChange.AddFieldWithAttributes, "A field with attributes was added. Field attributes will not have any effect until the next full recompilation."},
{PartiallySupportedChange.GenericMethodInGenericClass, "A generic method was edited. Usages in non-generic classes applied, but usages in the generic classes are not supported."},
{PartiallySupportedChange.NewCustomSerializableField, "A new custom serializable field was added. The inspector will not notice this change until the next full recompilation."},
{PartiallySupportedChange.MultipleFieldsEditedInTheSameType, "Multiple fields modified in the same type during a single patch. Their values have been reset."},
};
#pragma warning restore CS0612
internal static List<AlertEntry> Suggestions = new List<AlertEntry>();
internal static int UnsupportedChangesCount => EventsTimeline.Count(alert => alert.alertType == AlertType.UnsupportedChange && alert.entryType != EntryType.Child);
@@ -339,6 +351,40 @@ namespace SingularityGroup.HotReload.Editor {
));
}
#if UNITY_2020_1_OR_NEWER
internal static void CreateInlinedMethodsEntry(string[] patchedMethodsDisplayNames, EntryType entryType = EntryType.Standalone, DateTime? createdAt = null) {
var truncated = false;
if (patchedMethodsDisplayNames?.Length > 25) {
patchedMethodsDisplayNames = TruncateList(patchedMethodsDisplayNames, 25);
truncated = true;
}
var patchesList = patchedMethodsDisplayNames?.Length > 0 ? string.Join("\n• ", patchedMethodsDisplayNames) : "";
var timestamp = createdAt ?? DateTime.Now;
var entry = new AlertEntry(
timestamp: timestamp,
alertType : AlertType.UnsupportedChange,
title: "Failed applying patch to method",
description: $"Some methods got inlined by the Unity compiler and cannot be patched by Hot Reload. Switch to Debug mode to avoid this problem.\n\n• {(truncated ? patchesList + "\n..." : patchesList)}",
entryType: EntryType.Parent,
actionData: () => {
GUILayout.Space(10f);
using (new EditorGUILayout.HorizontalScope()) {
RenderCompileButton();
var suggestion = HotReloadSuggestionsHelper.suggestionMap[HotReloadSuggestionKind.SwitchToDebugModeForInlinedMethods];
if (suggestion?.actionData != null) {
suggestion.actionData();
}
}
},
alertData: new AlertData(AlertEntryType.InlinedMethod, createdAt: timestamp, patchedMembersDisplayNames: patchedMethodsDisplayNames, entryType: EntryType.Parent)
);
InsertEntry(entry);
if (patchedMethodsDisplayNames?.Length > 0) {
expandedEntries.Add(entry);
}
}
#endif
internal static void CreatePatchFailureEventEntry(string errorString, string methodName, string methodSimpleName = null, EntryType entryType = EntryType.Standalone, DateTime? createdAt = null) {
var timestamp = createdAt ?? DateTime.Now;
ErrorData errorData = ErrorData.GetErrorData(errorString);
@@ -390,7 +436,7 @@ namespace SingularityGroup.HotReload.Editor {
AlertEntryType.PatchApplied,
createdAt: timestamp,
entryType: EntryType.Standalone,
patchedMethodsDisplayNames: patchedMethodsDisplayNames)
patchedMembersDisplayNames: patchedMethodsDisplayNames)
);
InsertEntry(entry);
@@ -399,24 +445,24 @@ namespace SingularityGroup.HotReload.Editor {
}
}
internal static void CreateReloadFinishedWithWarningsEventEntry(DateTime? createdAt = null, string[] patchedMethodsDisplayNames = null) {
internal static void CreateReloadFinishedWithWarningsEventEntry(DateTime? createdAt = null, string[] patchedMembersDisplayNames = null) {
var truncated = false;
if (patchedMethodsDisplayNames?.Length > 25) {
patchedMethodsDisplayNames = TruncateList(patchedMethodsDisplayNames, 25);
if (patchedMembersDisplayNames?.Length > 25) {
patchedMembersDisplayNames = TruncateList(patchedMembersDisplayNames, 25);
truncated = true;
}
var patchesList = patchedMethodsDisplayNames?.Length > 0 ? string.Join("\n• ", patchedMethodsDisplayNames) : "";
var patchesList = patchedMembersDisplayNames?.Length > 0 ? string.Join("\n• ", patchedMembersDisplayNames) : "";
var timestamp = createdAt ?? DateTime.Now;
var entry = new AlertEntry(
timestamp: timestamp,
alertType: AlertType.UnsupportedChange,
title: EditorIndicationState.IndicationText[EditorIndicationState.IndicationStatus.Unsupported],
description: patchedMethodsDisplayNames?.Length > 0 ? $"• {(truncated ? patchesList + "\n...\n\nSee unsupported changes below" : patchesList + "\n\nSee unsupported changes below")}" : "See detailed entries below",
description: patchedMembersDisplayNames?.Length > 0 ? $"• {(truncated ? patchesList + "\n...\n\nSee unsupported changes below" : patchesList + "\n\nSee unsupported changes below")}" : "See detailed entries below",
entryType: EntryType.Parent,
alertData: new AlertData(AlertEntryType.Failure, createdAt: timestamp, entryType: EntryType.Parent, patchedMethodsDisplayNames: patchedMethodsDisplayNames)
alertData: new AlertData(AlertEntryType.Failure, createdAt: timestamp, entryType: EntryType.Parent, patchedMembersDisplayNames: patchedMembersDisplayNames)
);
InsertEntry(entry);
if (patchedMethodsDisplayNames?.Length > 0) {
if (patchedMembersDisplayNames?.Length > 0) {
expandedEntries.Add(entry);
}
}
@@ -435,7 +481,7 @@ namespace SingularityGroup.HotReload.Editor {
title: EditorIndicationState.IndicationText[EditorIndicationState.IndicationStatus.PartiallySupported],
description: patchedMethodsDisplayNames?.Length > 0 ? $"• {(truncated ? patchesList + "\n...\n\nSee partially applied changes below" : patchesList + "\n\nSee partially applied changes below")}" : "See detailed entries below",
entryType: EntryType.Parent,
alertData: new AlertData(AlertEntryType.PartiallySupportedChange, createdAt: timestamp, entryType: EntryType.Parent, patchedMethodsDisplayNames: patchedMethodsDisplayNames)
alertData: new AlertData(AlertEntryType.PartiallySupportedChange, createdAt: timestamp, entryType: EntryType.Parent, patchedMembersDisplayNames: patchedMethodsDisplayNames)
);
InsertEntry(entry);
if (patchedMethodsDisplayNames?.Length > 0) {
@@ -523,6 +569,7 @@ namespace SingularityGroup.HotReload.Editor {
// performance optimization (Enum.ToString uses reflection)
internal static string ToString(this PartiallySupportedChange change) {
#pragma warning disable CS0612 // obsolete
switch (change) {
case PartiallySupportedChange.LambdaClosure:
return nameof(PartiallySupportedChange.LambdaClosure);
@@ -531,9 +578,11 @@ namespace SingularityGroup.HotReload.Editor {
case PartiallySupportedChange.AddMonobehaviourMethod:
return nameof(PartiallySupportedChange.AddMonobehaviourMethod);
case PartiallySupportedChange.EditMonobehaviourField:
return nameof(PartiallySupportedChange.EditMonobehaviourField);
return nameof(PartiallySupportedChange.EditMonobehaviourField);
case PartiallySupportedChange.EditCoroutine:
return nameof(PartiallySupportedChange.EditCoroutine);
case PartiallySupportedChange.EditGenericFieldInitializer:
return nameof(PartiallySupportedChange.EditGenericFieldInitializer);
case PartiallySupportedChange.AddEnumMember:
return nameof(PartiallySupportedChange.AddEnumMember);
case PartiallySupportedChange.EditFieldInitializer:
@@ -542,6 +591,13 @@ namespace SingularityGroup.HotReload.Editor {
return nameof(PartiallySupportedChange.AddMethodWithAttributes);
case PartiallySupportedChange.GenericMethodInGenericClass:
return nameof(PartiallySupportedChange.GenericMethodInGenericClass);
case PartiallySupportedChange.AddFieldWithAttributes:
return nameof(PartiallySupportedChange.AddFieldWithAttributes);
case PartiallySupportedChange.NewCustomSerializableField:
return nameof(PartiallySupportedChange.NewCustomSerializableField);
case PartiallySupportedChange.MultipleFieldsEditedInTheSameType:
return nameof(PartiallySupportedChange.MultipleFieldsEditedInTheSameType);
#pragma warning restore CS0612
default:
throw new ArgumentOutOfRangeException(nameof(change), change, null);
}

View File

@@ -21,6 +21,7 @@ namespace SingularityGroup.HotReload.Editor {
private const string RefreshManuallyTipCachedKey = "HotReloadWindow.RefreshManuallyTipCachedKey";
private const string ShowLoginCachedKey = "HotReloadWindow.ShowLoginCachedKey";
private const string ConfigurationKey = "HotReloadWindow.Configuration";
private const string AvdancedKey = "HotReloadWindow.Avdanced";
private const string ShowPromoCodesCachedKey = "HotReloadWindow.ShowPromoCodesCached";
private const string ShowOnDeviceKey = "HotReloadWindow.ShowOnDevice";
private const string ShowChangelogKey = "HotReloadWindow.ShowChangelog";
@@ -35,6 +36,7 @@ namespace SingularityGroup.HotReload.Editor {
private const string LaunchOnEditorStartKey = "HotReloadWindow.LaunchOnEditorStart";
private const string AutoRecompileUnsupportedChangesKey = "HotReloadWindow.AutoRecompileUnsupportedChanges";
private const string AutoRecompilePartiallyUnsupportedChangesKey = "HotReloadWindow.AutoRecompilePartiallyUnsupportedChanges";
private const string DisplayNewMonobehaviourMethodsAsPartiallySupportedKey = "HotReloadWindow.DisplayNewMonobehaviourMethodsAsPartiallySupported";
private const string ShowNotificationsKey = "HotReloadWindow.ShowNotifications";
private const string ShowPatchingNotificationsKey = "HotReloadWindow.ShowPatchingNotifications";
private const string ShowCompilingUnsupportedNotificationsKey = "HotReloadWindow.ShowCompilingUnsupportedNotifications";
@@ -52,6 +54,8 @@ namespace SingularityGroup.HotReload.Editor {
private const string AllAssetChangesKey = "HotReloadWindow.AllAssetChanges";
private const string IncludeShaderChangesKey = "HotReloadWindow.IncludeShaderChanges";
private const string DisableConsoleWindowKey = "HotReloadWindow.DisableConsoleWindow";
private const string DisableDetailedErrorReportingKey = "HotReloadWindow.DisableDetailedErrorReporting";
private const string DebuggerCompatibilityEnabledKey = "HotReloadWindow.DebuggerCompatibilityEnabled";
private const string RedeemLicenseEmailKey = "HotReloadWindow.RedeemLicenseEmail";
private const string RedeemLicenseInvoiceKey = "HotReloadWindow.RedeemLicenseInvoice";
private const string RunTabEventsSuggestionsFoldoutKey = "HotReloadWindow.RunTabEventsSuggestionsFoldout";
@@ -62,7 +66,10 @@ namespace SingularityGroup.HotReload.Editor {
private const string RunTabUndetectedPatchesFilterKey = "HotReloadWindow.RunTabUndetectedPatchesFilter";
private const string RunTabAppliedPatchesFilterKey = "HotReloadWindow.RunTabAppliedPatchesFilter";
private const string RecompileDialogueShownKey = "HotReloadWindow.RecompileDialogueShown";
private const string ApplyFieldInitiailzerEditsToExistingClassInstancesKey = "HotReloadWindow.ApplyFieldInitiailzerEditsToExistingClassInstances";
private const string LoggedInlinedMethodsDialogueKey = "HotReloadWindow.LoggedInlinedMethodsDialogue";
private const string OpenedWindowAtLeastOnceKey = "HotReloadWindow.OpenedWindowAtLeastOnce";
private const string DeactivateHotReloadKey = "HotReloadWindow.DeactivateHotReload";
public const string DontShowPromptForDownloadKey = "ServerDownloader.DontShowPromptForDownload";
@@ -160,6 +167,11 @@ namespace SingularityGroup.HotReload.Editor {
get { return EditorPrefs.GetBool(ConfigurationKey, true); }
set { EditorPrefs.SetBool(ConfigurationKey, value); }
}
public static bool ShowAdvanced {
get { return EditorPrefs.GetBool(AvdancedKey, false); }
set { EditorPrefs.SetBool(AvdancedKey, value); }
}
public static bool ShowPromoCodes {
get { return EditorPrefs.GetBool(ShowPromoCodesCachedKey, true); }
@@ -271,6 +283,11 @@ namespace SingularityGroup.HotReload.Editor {
get { return EditorPrefs.GetBool(AutoRecompilePartiallyUnsupportedChangesKey, false); }
set { EditorPrefs.SetBool(AutoRecompilePartiallyUnsupportedChangesKey, value); }
}
public static bool DisplayNewMonobehaviourMethodsAsPartiallySupported {
get { return EditorPrefs.GetBool(DisplayNewMonobehaviourMethodsAsPartiallySupportedKey, false); }
set { EditorPrefs.SetBool(DisplayNewMonobehaviourMethodsAsPartiallySupportedKey, value); }
}
public static bool ShowNotifications {
get { return EditorPrefs.GetBool(ShowNotificationsKey, true); }
@@ -431,5 +448,31 @@ namespace SingularityGroup.HotReload.Editor {
string[] rgbaParts = ser.Split(rgbaDelimiter.ToCharArray());
return new Color(float.Parse(rgbaParts[0]), float.Parse(rgbaParts[1]),float.Parse(rgbaParts[2]),float.Parse(rgbaParts[3]));
}
[Obsolete("was not implemented")]
public static bool ApplyFieldInitiailzerEditsToExistingClassInstances {
get { return EditorPrefs.GetBool(ApplyFieldInitiailzerEditsToExistingClassInstancesKey); }
set { EditorPrefs.SetBool(ApplyFieldInitiailzerEditsToExistingClassInstancesKey, value); }
}
public static bool LoggedInlinedMethodsDialogue {
get { return EditorPrefs.GetBool(LoggedInlinedMethodsDialogueKey); }
set { EditorPrefs.SetBool(LoggedInlinedMethodsDialogueKey, value); }
}
public static bool DeactivateHotReload {
get { return EditorPrefs.GetBool(DeactivateHotReloadKey); }
set { EditorPrefs.SetBool(DeactivateHotReloadKey, value); }
}
public static bool DisableDetailedErrorReporting {
get { return EditorPrefs.GetBool(DisableDetailedErrorReportingKey, false); }
set { EditorPrefs.SetBool(DisableDetailedErrorReportingKey, value); }
}
public static bool AutoDisableHotReloadWithDebugger {
get { return EditorPrefs.GetBool(DebuggerCompatibilityEnabledKey, true); }
set { EditorPrefs.SetBool(DebuggerCompatibilityEnabledKey, value); }
}
}
}

View File

@@ -6,8 +6,15 @@ namespace SingularityGroup.HotReload.Editor {
private const string LastPatchIdKey = "HotReloadWindow.LastPatchId";
private const string ShowingRedDotKey = "HotReloadWindow.ShowingRedDot";
private const string ShowedEditorsWithoutHRKey = "HotReloadWindow.ShowedEditorWithoutHR";
private const string ShowedFieldInitializerWithSideEffectsKey = "HotReloadWindow.ShowedFieldInitializerWithSideEffects";
private const string ShowedAddMonobehaviourMethodsKey = "HotReloadWindow.ShowedAddMonobehaviourMethods";
private const string ShowedFieldInitializerExistingInstancesEditedKey = "HotReloadWindow.ShowedFieldInitializerExistingInstancesEdited";
private const string ShowedFieldInitializerExistingInstancesUneditedKey = "HotReloadWindow.ShowedFieldInitializerExistingInstancesUnedited";
private const string RecompiledUnsupportedChangesOnExitPlaymodeKey = "HotReloadWindow.RecompiledUnsupportedChangesOnExitPlaymode";
private const string RecompiledUnsupportedChangesInPlaymodeKey = "HotReloadWindow.RecompiledUnsupportedChangesInPlaymode";
private const string EditorCodePatcherInitKey = "HotReloadWindow.EditorCodePatcherInit";
private const string ShowedDebuggerCompatibilityKey = "HotReloadWindow.ShowedDebuggerCompatibility";
public static int ServerPort {
get { return SessionState.GetInt(ServerPortKey, RequestHelper.defaultPort); }
@@ -29,6 +36,26 @@ namespace SingularityGroup.HotReload.Editor {
set { SessionState.SetBool(ShowedEditorsWithoutHRKey, value); }
}
public static bool ShowedFieldInitializerWithSideEffects {
get { return SessionState.GetBool(ShowedFieldInitializerWithSideEffectsKey, false); }
set { SessionState.SetBool(ShowedFieldInitializerWithSideEffectsKey, value); }
}
public static bool ShowedAddMonobehaviourMethods {
get { return SessionState.GetBool(ShowedAddMonobehaviourMethodsKey, false); }
set { SessionState.SetBool(ShowedAddMonobehaviourMethodsKey, value); }
}
public static bool ShowedFieldInitializerExistingInstancesEdited {
get { return SessionState.GetBool(ShowedFieldInitializerExistingInstancesEditedKey, false); }
set { SessionState.SetBool(ShowedFieldInitializerExistingInstancesEditedKey, value); }
}
public static bool ShowedFieldInitializerExistingInstancesUnedited {
get { return SessionState.GetBool(ShowedFieldInitializerExistingInstancesUneditedKey, false); }
set { SessionState.SetBool(ShowedFieldInitializerExistingInstancesUneditedKey, value); }
}
public static bool RecompiledUnsupportedChangesOnExitPlaymode {
get { return SessionState.GetBool(RecompiledUnsupportedChangesOnExitPlaymodeKey, false); }
set { SessionState.SetBool(RecompiledUnsupportedChangesOnExitPlaymodeKey, value); }
@@ -38,6 +65,16 @@ namespace SingularityGroup.HotReload.Editor {
get { return SessionState.GetBool(RecompiledUnsupportedChangesInPlaymodeKey, false); }
set { SessionState.SetBool(RecompiledUnsupportedChangesInPlaymodeKey, value); }
}
public static bool EditorCodePatcherInit {
get { return SessionState.GetBool(EditorCodePatcherInitKey, false); }
set { SessionState.SetBool(EditorCodePatcherInitKey, value); }
}
public static bool ShowedDebuggerCompatibility {
get { return SessionState.GetBool(ShowedDebuggerCompatibilityKey, false); }
set { SessionState.SetBool(ShowedDebuggerCompatibilityKey, value); }
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -24,11 +24,11 @@ namespace SingularityGroup.HotReload.Editor {
if (showOnStartup == ShowOnStartupEnum.Always || (showOnStartup == ShowOnStartupEnum.OnNewVersion && !String.IsNullOrEmpty(updatedFromVersion))) {
// Don't open Hot Reload window inside Virtual Player folder
// This is a heuristic since user might have the main player inside VP user-created folder, but that will be rare
if (new DirectoryInfo(Path.GetFullPath("..")).Name != "VP") {
if (new DirectoryInfo(Path.GetFullPath("..")).Name != "VP" && !HotReloadPrefs.DeactivateHotReload) {
HotReloadWindow.Open();
}
}
if (HotReloadPrefs.LaunchOnEditorStart) {
if (HotReloadPrefs.LaunchOnEditorStart && !HotReloadPrefs.DeactivateHotReload) {
EditorCodePatcher.DownloadAndRun().Forget();
}

View File

@@ -1,6 +1,8 @@
using System;
using System.IO;
using UnityEditor;
using UnityEditor.Build;
using UnityEngine;
namespace SingularityGroup.HotReload.Editor {
internal static class HotReloadBuildHelper {
@@ -90,17 +92,20 @@ namespace SingularityGroup.HotReload.Editor {
BuildTargetGroup.WebGL, // has never had mono
};
#pragma warning disable CS0618
public static bool IsMonoSupported(BuildTargetGroup buildTarget) {
// "When a platform can support both backends, Mono is the default. For more information, see Scripting restrictions."
// Unity docs https://docs.unity3d.com/Manual/Mono.html (2019.4/2020.3/2021.3)
#pragma warning disable CS0618 // obsolete since 2023
var defaultScripting = PlayerSettings.GetDefaultScriptingBackend(buildTarget);
#pragma warning restore CS0618
if (defaultScripting == ScriptingImplementation.Mono2x) {
return Array.IndexOf(unsupportedBuildTargets, buildTarget) == -1;
var backend = PlayerSettings.GetScriptingBackend(buildTarget);
try {
// GetDefaultScriptingBackend returns IL2CPP for Unity 6 which goes against Unity documentation.
// Have to use a workaround approach instead
PlayerSettings.SetScriptingBackend(buildTarget, ScriptingImplementation.Mono2x);
return PlayerSettings.GetScriptingBackend(buildTarget) == ScriptingImplementation.Mono2x;
} catch {
return false;
} finally {
PlayerSettings.SetScriptingBackend(buildTarget, backend);
}
// default scripting was not Mono, so the platform doesn't support Mono at all.
return false;
}
#pragma warning restore CS0618
}
}

View File

@@ -438,7 +438,7 @@ namespace SingularityGroup.HotReload.Editor.ProjectGeneration {
// #if !ROSLYN_ANALYZER_FIX
// .Concat(GetRoslynAnalyzerPaths())
// #else
.Concat(assembly.CompilerOptions.RoslynAnalyzerDllPaths)
.Concat(assembly.CompilerOptions.RoslynAnalyzerDllPaths ?? Array.Empty<string>())
// #endif
.Select(MakeAbsolutePath)
.Distinct()
@@ -655,7 +655,7 @@ namespace SingularityGroup.HotReload.Editor.ProjectGeneration {
var index = b.IndexOf(":", StringComparison.Ordinal);
if (index > 0 && b.Length > index) {
var key = b.Substring(1, index - 1);
return new KeyValuePair<string, string>(key, b.Substring(index + 1));
return new KeyValuePair<string, string>(key.ToLowerInvariant(), b.Substring(index + 1));
}
const string warnaserror = "warnaserror";

View File

@@ -7,7 +7,8 @@
"UnityEditor.UI",
"UnityEngine.UI",
"UnityEngine.TestRunner",
"UnityEditor.TestRunner"
"UnityEditor.TestRunner",
"VInspector"
],
"optionalUnityReferences": [],
"includePlatforms": [
@@ -21,7 +22,10 @@
"SingularityGroup.HotReload.RuntimeDependencies2019.dll",
"SingularityGroup.HotReload.RuntimeDependencies2020.dll",
"SingularityGroup.HotReload.RuntimeDependencies2022.dll",
"SingularityGroup.HotReload.EditorDependencies.dll"
"SingularityGroup.HotReload.EditorDependencies.dll",
"Sirenix.OdinInspector.Editor.dll",
"Sirenix.Utilities.dll",
"Sirenix.OdinInspector.Attributes.dll"
],
"autoReferenced": false,
"defineConstraints": []

View File

@@ -7,28 +7,10 @@ PluginImporter:
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 1
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
@@ -39,48 +21,13 @@ PluginImporter:
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: x86_64
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: x86
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: x86_64
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -17,6 +17,7 @@ namespace SingularityGroup.HotReload.Editor {
readonly JsonSerializer jsonSerializer = JsonSerializer.CreateDefault();
SemVersion newVersionDetected;
bool started;
bool warnedVersionCheckFailed;
private static TimeSpan RetryInterval => TimeSpan.FromSeconds(30);
private static TimeSpan CheckInterval => TimeSpan.FromHours(1);
@@ -70,8 +71,9 @@ namespace SingularityGroup.HotReload.Editor {
if(response.err != null) {
if(response.statusCode == 0 || response.statusCode == 404) {
// probably no internet, fail silently and retry
} else {
} else if (!warnedVersionCheckFailed) {
Log.Warning("version check failed: {0}", response.err);
warnedVersionCheckFailed = true;
}
} else {
var newVersion = response.data;

View File

@@ -90,7 +90,7 @@ namespace SingularityGroup.HotReload.Editor {
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Comany size (number of employees)");
EditorGUILayout.LabelField("Company size (number of employees)");
GUI.SetNextControlName("company_size");
_pendingCompanySize = EditorGUILayout.TextField(_pendingCompanySize)?.Trim();
EditorGUILayout.Space();

View File

@@ -79,22 +79,30 @@ namespace SingularityGroup.HotReload.Editor {
} catch {
// ignore
}
// Get relative path
TextAsset file = null;
foreach (var path in supportedPaths) {
int lastprojectIndex = 0;
int attempt = 0;
while (attempt++ < 100 && !file) {
lastprojectIndex = errorString.IndexOf(path, lastprojectIndex + 1, StringComparison.Ordinal);
if (lastprojectIndex == -1) {
break;
try {
foreach (var path in supportedPaths) {
int lastprojectIndex = 0;
int attempt = 0;
while (attempt++ < 100 && !file) {
lastprojectIndex = errorString.IndexOf(path, lastprojectIndex + 1, StringComparison.Ordinal);
if (lastprojectIndex == -1) {
break;
}
var fullCsIndex = errorString.IndexOf(".cs", lastprojectIndex, StringComparison.Ordinal);
var l = fullCsIndex - lastprojectIndex + ".cs".Length;
if (l <= 0) {
continue;
}
var candidateAbsolutePath = errorString.Substring(lastprojectIndex, fullCsIndex - lastprojectIndex + ".cs".Length);
var candidateRelativePath = EditorCodePatcher.GetRelativePath(filespec: candidateAbsolutePath, folder: path);
file = AssetDatabase.LoadAssetAtPath<TextAsset>(candidateRelativePath);
}
var fullCsIndex = errorString.IndexOf(".cs", lastprojectIndex, StringComparison.Ordinal);
var candidateAbsolutePath = errorString.Substring(lastprojectIndex, fullCsIndex - lastprojectIndex + ".cs".Length);
var candidateRelativePath = EditorCodePatcher.GetRelativePath(filespec: candidateAbsolutePath, folder: path);
file = AssetDatabase.LoadAssetAtPath<TextAsset>(candidateRelativePath);
}
} catch {
// ignore
}
// Get the line number
@@ -397,17 +405,14 @@ namespace SingularityGroup.HotReload.Editor {
GUI.Label(startRect, new GUIContent(title, icon), style);
}
bool clickableDescription = alertEntry.title == "Unsupported change" || alertEntry.title == "Compile error" || alertEntry.title == "Failed applying patch to method";
bool clickableDescription = (alertEntry.title == "Unsupported change" || alertEntry.title == "Compile error" || alertEntry.title == "Failed applying patch to method") && alertEntry.alertData.alertEntryType != AlertEntryType.InlinedMethod;
if (HotReloadTimelineHelper.expandedEntries.Contains(alertEntry) || alertEntry.alertType == AlertType.CompileError) {
using (new EditorGUILayout.VerticalScope()) {
using (new EditorGUILayout.HorizontalScope()) {
using (new EditorGUILayout.VerticalScope(entryType == EntryType.Child ? HotReloadWindowStyles.ChildEntryBoxStyle : HotReloadWindowStyles.EntryBoxStyle)) {
if (alertEntry.alertType == AlertType.Suggestion) {
if (alertEntry.alertType == AlertType.Suggestion || !clickableDescription) {
GUILayout.Label(alertEntry.description, HotReloadWindowStyles.LabelStyle);
} else if (!clickableDescription) {
string text = alertEntry.description;
GUILayout.Label(text, HotReloadWindowStyles.LabelStyle);
}
if (alertEntry.actionData != null) {
alertEntry.actionData.Invoke();
@@ -659,18 +664,29 @@ namespace SingularityGroup.HotReload.Editor {
if (!firstDialoguePass) {
return;
}
var secondDialoguePass = !Application.isPlaying
|| EditorUtility.DisplayDialog(
title: "Stop Play Mode and Recompile?",
message: "Using the Recompile button will stop Play Mode.\n\nDo you wish to proceed?",
ok: "Stop and Recompile",
cancel: "Cancel");
if (!secondDialoguePass) {
if (!ConfirmExitPlaymode("Using the Recompile button will stop Play Mode.\n\nDo you wish to proceed?")) {
return;
}
Recompile();
}
#if UNITY_2020_1_OR_NEWER
public static void SwitchToDebugMode() {
CompilationPipeline.codeOptimization = CodeOptimization.Debug;
HotReloadRunTab.Recompile();
HotReloadSuggestionsHelper.SetSuggestionInactive(HotReloadSuggestionKind.SwitchToDebugModeForInlinedMethods);
}
#endif
public static bool ConfirmExitPlaymode(string message) {
return !Application.isPlaying
|| EditorUtility.DisplayDialog(
title: "Stop Play Mode and Recompile?",
message: message,
ok: "Stop and Recompile",
cancel: "Cancel");
}
public static bool recompiling;
public static void Recompile() {
recompiling = true;
@@ -697,7 +713,7 @@ namespace SingularityGroup.HotReload.Editor {
EditorCodePatcher.DownloadAndRun().Forget();
}
} else if (currentState.running && !currentState.starting) {
if (HotReloadWindowStyles.windowScreenWidth > 150 && HotReloadTimelineHelper.CompileErrorsCount == 0) {
if (HotReloadWindowStyles.windowScreenWidth > 150) {
RenderRecompileButton();
}
string stopText = HotReloadWindowStyles.windowScreenWidth > Constants.StartButtonTextHideWidth ? " Stop" : "";

View File

@@ -98,18 +98,12 @@ namespace SingularityGroup.HotReload.Editor {
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
using (new EditorGUILayout.VerticalScope()) {
HotReloadPrefs.ShowConfiguration = EditorGUILayout.Foldout(HotReloadPrefs.ShowConfiguration, "Configuration", true, HotReloadWindowStyles.FoldoutStyle);
HotReloadPrefs.ShowConfiguration = EditorGUILayout.Foldout(HotReloadPrefs.ShowConfiguration, "Settings", true, HotReloadWindowStyles.FoldoutStyle);
if (HotReloadPrefs.ShowConfiguration) {
EditorGUILayout.Space();
RenderUnityAutoRefresh();
RenderAssetRefresh();
if (HotReloadPrefs.AllAssetChanges) {
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
RenderIncludeShaderChanges();
}
EditorGUILayout.Space();
}
// main section
RenderUnityAutoRefresh();
using (new EditorGUI.DisabledScope(!EditorCodePatcher.autoRecompileUnsupportedChangesSupported)) {
RenderAutoRecompileUnsupportedChanges();
if (HotReloadPrefs.AutoRecompileUnsupportedChanges && EditorCodePatcher.autoRecompileUnsupportedChangesSupported) {
@@ -118,22 +112,49 @@ namespace SingularityGroup.HotReload.Editor {
RenderAutoRecompileUnsupportedChangesOnExitPlayMode();
RenderAutoRecompileUnsupportedChangesInPlayMode();
RenderAutoRecompilePartiallyUnsupportedChanges();
RenderDisplayNewMonobehaviourMethodsAsPartiallySupported();
}
}
EditorGUILayout.Space();
}
RenderConsoleWindow();
RenderAutostart();
RenderAssetRefresh();
if (HotReloadPrefs.AllAssetChanges) {
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
RenderIncludeShaderChanges();
}
EditorGUILayout.Space();
}
RenderDebuggerCompatibility();
// // fields
// RenderShowFeatures();
// using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
// RenderShowApplyfieldInitializerEditsToExistingClassInstances();
//
// EditorGUILayout.Space();
// }
// visual feedback
if (EditorWindowHelper.supportsNotifications) {
RenderShowNotifications();
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
RenderShowPatchingNotifications();
RenderShowCompilingUnsupportedNotifications();
}
EditorGUILayout.Space();
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
RenderShowPatchingNotifications();
RenderShowCompilingUnsupportedNotifications();
}
EditorGUILayout.Space();
}
// misc
RenderMiscHeader();
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
RenderAutostart();
RenderConsoleWindow();
EditorGUILayout.Space();
}
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope()) {
GUILayout.FlexibleSpace();
@@ -161,24 +182,38 @@ namespace SingularityGroup.HotReload.Editor {
}
}
}
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
using (new EditorGUILayout.VerticalScope()) {
HotReloadPrefs.ShowAdvanced = EditorGUILayout.Foldout(HotReloadPrefs.ShowAdvanced, "Advanced", true, HotReloadWindowStyles.FoldoutStyle);
if (HotReloadPrefs.ShowAdvanced) {
EditorGUILayout.Space();
DeactivateHotReload();
DisableDetailedErrorReporting();
}
}
}
}
}
}
}
void RenderUnityAutoRefresh() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Allow to manage Unity's Auto Compile settings (recommended)"), HotReloadPrefs.AllowDisableUnityAutoRefresh);
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Manage Unity auto-refresh (recommended)"), HotReloadPrefs.AllowDisableUnityAutoRefresh);
if (newSettings != HotReloadPrefs.AllowDisableUnityAutoRefresh) {
HotReloadPrefs.AllowDisableUnityAutoRefresh = newSettings;
}
string toggleDescription;
if (HotReloadPrefs.AllowDisableUnityAutoRefresh) {
toggleDescription = "Hot Reload will manage Unity's Auto Refresh and Script Compilation settings when it's running. Previous settings will be restored when Hot Reload is stopped.";
toggleDescription = "To avoid unnecessary recompiling, Hot Reload will automatically change Unity's Auto Refresh and Script Compilation settings. Previous settings will be restored when Hot Reload is stopped";
} else {
toggleDescription = "Enable to allow Hot Reload to manage Unity's Auto Refresh and Script Compilation settings when it's running. If enabled, previous settings will be restored when Hot Reload is stopped.";
toggleDescription = "Enabled this setting to auto-manage Unity's Auto Refresh and Script Compilation settings. This reduces unncessary recompiling";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(3f);
EditorGUILayout.Space(6f);
}
void RenderAssetRefresh() {
@@ -190,7 +225,7 @@ namespace SingularityGroup.HotReload.Editor {
var restartServer = EditorUtility.DisplayDialog("Hot Reload",
$"When changing 'Asset refresh', the Hot Reload server must be restarted for this to take effect." +
"\nDo you want to restart it now?",
"Restart server", "Don't restart");
"Restart Hot Reload", "Don't restart");
if (restartServer) {
EditorCodePatcher.RestartCodePatcher().Forget();
}
@@ -198,13 +233,30 @@ namespace SingularityGroup.HotReload.Editor {
}
string toggleDescription;
if (HotReloadPrefs.AllAssetChanges) {
toggleDescription = "Hot Reload will refresh changed assets in the project.";
toggleDescription = "Hot Reload will refresh changed assets such as sprites, prefabs, etc";
} else {
toggleDescription = "Enable to allow Hot Reload to refresh changed assets in the project. All asset types are supported including sprites, prefabs, shaders etc.";
toggleDescription = "Enable to allow Hot Reload to refresh changed assets in the project. All asset types are supported including sprites, prefabs, shaders etc";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(3f);
EditorGUILayout.Space(6f);
}
void RenderDebuggerCompatibility() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Auto-disable Hot Reload while a debugger is attached (recommended)"), HotReloadPrefs.AutoDisableHotReloadWithDebugger);
if (newSettings != HotReloadPrefs.AutoDisableHotReloadWithDebugger) {
HotReloadPrefs.AutoDisableHotReloadWithDebugger = newSettings;
CodePatcher.I.debuggerCompatibilityEnabled = !HotReloadPrefs.AutoDisableHotReloadWithDebugger;
}
string toggleDescription;
if (HotReloadPrefs.AutoDisableHotReloadWithDebugger) {
toggleDescription = "Hot Reload automatically disables itself while a debugger is attached, as it can otherwise interfere with certain debugger features. Please read the documentation if you consider disabling this setting.";
} else {
toggleDescription = "When a debugger is attached, Hot Reload will be active, but certain debugger features might not work as expected. Please read our documentation to learn about the limitations.";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
void RenderIncludeShaderChanges() {
@@ -245,9 +297,71 @@ namespace SingularityGroup.HotReload.Editor {
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(3f);
EditorGUILayout.Space(6f);
}
void DeactivateHotReload() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Deactivate Hot Reload"), HotReloadPrefs.DeactivateHotReload);
if (newSettings != HotReloadPrefs.DeactivateHotReload) {
DeactivateHotReloadInner(newSettings);
}
string toggleDescription;
if (HotReloadPrefs.DeactivateHotReload) {
toggleDescription = "Hot Reload is deactivated.";
} else {
toggleDescription = "Enable to deactivate Hot Reload.";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
void DisableDetailedErrorReporting() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Disable Detailed Error Reporting"), HotReloadPrefs.DisableDetailedErrorReporting);
DisableDetailedErrorReportingInner(newSettings);
string toggleDescription;
if (HotReloadPrefs.DisableDetailedErrorReporting) {
toggleDescription = "Detailed error reporting is disabled.";
} else {
toggleDescription = "Toggle on to disable detailed error reporting.";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
public static void DisableDetailedErrorReportingInner(bool newSetting) {
if (newSetting == HotReloadPrefs.DisableDetailedErrorReporting) {
return;
}
HotReloadPrefs.DisableDetailedErrorReporting = newSetting;
// restart when setting changes
if (ServerHealthCheck.I.IsServerHealthy) {
var restartServer = EditorUtility.DisplayDialog("Hot Reload",
$"When changing 'Disable Detailed Error Reporting', the Hot Reload server must be restarted for this to take effect." +
"\nDo you want to restart it now?",
"Restart server", "Don't restart");
if (restartServer) {
EditorCodePatcher.RestartCodePatcher().Forget();
}
}
}
static void DeactivateHotReloadInner(bool deactivate) {
var confirmed = !deactivate || EditorUtility.DisplayDialog("Hot Reload",
$"Hot Reload will be completely deactivated (unusable) until you activate it again." +
"\n\nDo you want to proceed?",
"Deactivate", "Cancel");
if (confirmed) {
HotReloadPrefs.DeactivateHotReload = deactivate;
if (deactivate) {
EditorCodePatcher.StopCodePatcher(recompileOnDone: true).Forget();
} else {
HotReloadRunTab.Recompile();
}
}
}
void RenderAutostart() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Autostart on Unity open"), HotReloadPrefs.LaunchOnEditorStart);
if (newSettings != HotReloadPrefs.LaunchOnEditorStart) {
@@ -265,15 +379,25 @@ namespace SingularityGroup.HotReload.Editor {
}
void RenderShowNotifications() {
GUILayout.Label("Indications", HotReloadWindowStyles.NotificationsTitleStyle);
EditorGUILayout.Space(10f);
GUILayout.Label("Visual Feedback", HotReloadWindowStyles.NotificationsTitleStyle);
EditorGUILayout.Space(10f);
string toggleDescription;
if (!EditorWindowHelper.supportsNotifications && !UnitySettingsHelper.I.playmodeTintSupported) {
toggleDescription = "Indications are not supported in the Unity version you use.";
} else {
toggleDescription = "Chosen indications are enabled:";
var toggleDescription = "Indications are not supported in the Unity version you use.";
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
}
// void RenderShowFields() {
// EditorGUILayout.Space(14f);
// GUILayout.Label("Fields", HotReloadWindowStyles.NotificationsTitleStyle);
// }
void RenderMiscHeader() {
EditorGUILayout.Space(10f);
GUILayout.Label("Misc", HotReloadWindowStyles.NotificationsTitleStyle);
EditorGUILayout.Space(10f);
}
void RenderShowPatchingNotifications() {
@@ -289,6 +413,36 @@ namespace SingularityGroup.HotReload.Editor {
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
// void RenderShowApplyfieldInitializerEditsToExistingClassInstances() {
// var newSetting = EditorGUILayout.BeginToggleGroup(new GUIContent("Apply field initializer edits to existing class instances"), HotReloadPrefs.ApplyFieldInitiailzerEditsToExistingClassInstances);
// ApplyApplyFieldInitializerEditsToExistingClassInstances(newSetting);
// string toggleDescription;
// if (HotReloadPrefs.ApplyFieldInitiailzerEditsToExistingClassInstances) {
// toggleDescription = "New field initializers with constant value will update field value of existing objects.";
// } else {
// toggleDescription = "New field initializers will not modify existing objects.";
// }
// EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
// EditorGUILayout.EndToggleGroup();
// }
[Obsolete("Not implemented")]
public static void ApplyApplyFieldInitializerEditsToExistingClassInstances(bool newSetting) {
if (newSetting != HotReloadPrefs.ApplyFieldInitiailzerEditsToExistingClassInstances) {
HotReloadPrefs.ApplyFieldInitiailzerEditsToExistingClassInstances = newSetting;
// restart when setting changes
if (ServerHealthCheck.I.IsServerHealthy) {
var restartServer = EditorUtility.DisplayDialog("Hot Reload",
$"When changing 'Apply field initializer edits to existing class instances' setting, the Hot Reload server must restart for it to take effect." +
"\nDo you want to restart it now?",
"Restart server", "Don't restart");
if (restartServer) {
EditorCodePatcher.RestartCodePatcher().Forget();
}
}
}
}
void RenderShowCompilingUnsupportedNotifications() {
HotReloadPrefs.ShowCompilingUnsupportedNotifications = EditorGUILayout.BeginToggleGroup(new GUIContent("Compiling Unsupported Changes Indication"), HotReloadPrefs.ShowCompilingUnsupportedNotifications);
@@ -310,9 +464,9 @@ namespace SingularityGroup.HotReload.Editor {
if (!EditorCodePatcher.autoRecompileUnsupportedChangesSupported) {
toggleDescription = "Auto recompiling unsupported changes is not supported in the Unity version you use.";
} else if (HotReloadPrefs.AutoRecompileUnsupportedChanges) {
toggleDescription = "Hot Reload will recompile when unsupported changes are detected.";
toggleDescription = "Hot Reload will recompile automatically after code changes that Hot Reload doesn't support.";
} else {
toggleDescription = "Enable to recompile when unsupported changes are detected.";
toggleDescription = "When enabled, recompile happens automatically after code changes that Hot Reload doesn't support.";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
@@ -330,6 +484,18 @@ namespace SingularityGroup.HotReload.Editor {
EditorGUILayout.EndToggleGroup();
}
void RenderDisplayNewMonobehaviourMethodsAsPartiallySupported() {
HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported = EditorGUILayout.BeginToggleGroup(new GUIContent("Display new Monobehaviour methods as partially supported"), HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported);
string toggleDescription;
if (HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported) {
toggleDescription = "Hot Reload will display new monobehaviour methods as partially unsupported.";
} else {
toggleDescription = "Enable to display new monobehaviour methods as partially unsupported.";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderAutoRecompileUnsupportedChangesImmediately() {
HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately = EditorGUILayout.BeginToggleGroup(new GUIContent("Recompile immediately"), HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately);
string toggleDescription;