diff --git a/Packages/com.singularitygroup.hotreload/Editor/Attribution/VSAttribution.cs b/Packages/com.singularitygroup.hotreload/Editor/Attribution/VSAttribution.cs
index cc06552..1cd7459 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/Attribution/VSAttribution.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/Attribution/VSAttribution.cs
@@ -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
+ ));
+ }
+ }
+
+ ///
+ /// Registers and attempts to send a Verified Solutions Attribution event.
+ ///
+ /// Name of the action, identifying a place this event was called from.
+ /// Identifiable Verified Solutions Partner's name.
+ /// Unique identifier of the customer using Partner's Verified Solution.
+ 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
diff --git a/Packages/com.singularitygroup.hotreload/Editor/CLI/HotReloadCli.cs b/Packages/com.singularitygroup.hotreload/Editor/CLI/HotReloadCli.cs
index 8cac040..2f24aff 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/CLI/HotReloadCli.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/CLI/HotReloadCli.cs
@@ -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 {
///
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}""";
}
diff --git a/Packages/com.singularitygroup.hotreload/Editor/Constants.cs b/Packages/com.singularitygroup.hotreload/Editor/Constants.cs
index 3f7c269..3b90ef4 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/Constants.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/Constants.cs
@@ -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";
diff --git a/Packages/com.singularitygroup.hotreload/Editor/EditorCodePatcher.cs b/Packages/com.singularitygroup.hotreload/Editor/EditorCodePatcher.cs
index 12c62ac..93734b6 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/EditorCodePatcher.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/EditorCodePatcher.cs
@@ -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 inlinedMethodsFound = new HashSet();
+ internal static void CheckInlinedMethods() {
+ if (CompilationPipeline.codeOptimization != CodeOptimization.Release) {
+ return;
+ }
+ HashSet 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();
+ }
+ 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(response.partiallySupportedChanges ?? Array.Empty());
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(response.failures ?? Array.Empty());
- _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();
} else {
- allMethods = allMethods.Concat(response.removedMethod.Select(m => GetExtendedMethodName(m))).Distinct(StringComparer.OrdinalIgnoreCase);
+ allMethods = allMethods.Concat(response.removedMethod?.Select(m => GetExtendedMethodName(m)) ?? Array.Empty()).Distinct(StringComparer.OrdinalIgnoreCase);
+ }
+
+ var allFields = (patchResult?.addedFields.Select(f => GetExtendedFieldName(f)) ?? Array.Empty())
+ .Concat(response.alteredFields?.Select(f => GetExtendedFieldName(f)).Distinct(StringComparer.OrdinalIgnoreCase) ?? Array.Empty())
+ .Concat(response.patches?.SelectMany(p => p?.propertyAttributesFieldUpdated ?? Array.Empty()).Select(f => GetExtendedFieldName(f)).Distinct(StringComparer.OrdinalIgnoreCase) ?? Array.Empty())
+ .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 DownloadAndRun(LoginData loginData = null) {
+ internal static async Task 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 childrenToRemove = new List();
+ 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 __result
+ ) {
+ if (field == null) {
+ // do nothing
+ return true;
+ }
+ List 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
+ }
+ }
}
diff --git a/Packages/com.singularitygroup.hotreload/Editor/Helpers/HotReloadSuggestionsHelper.cs b/Packages/com.singularitygroup.hotreload/Editor/Helpers/HotReloadSuggestionsHelper.cs
index c39b59c..56f739e 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/Helpers/HotReloadSuggestionsHelper.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/Helpers/HotReloadSuggestionsHelper.cs
@@ -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 suggestionMap = new Dictionary {
{ 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 don’t 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();
- 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) {
diff --git a/Packages/com.singularitygroup.hotreload/Editor/Helpers/HotReloadTimelineHelper.cs b/Packages/com.singularitygroup.hotreload/Editor/Helpers/HotReloadTimelineHelper.cs
index 8e9ef1c..84c13d8 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/Helpers/HotReloadTimelineHelper.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/Helpers/HotReloadTimelineHelper.cs
@@ -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 partiallySupportedChangeDescriptions = new Dictionary {
{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 Suggestions = new List();
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);
}
diff --git a/Packages/com.singularitygroup.hotreload/Editor/HotReloadPrefs.cs b/Packages/com.singularitygroup.hotreload/Editor/HotReloadPrefs.cs
index 0ffdc7c..4b18af3 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/HotReloadPrefs.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/HotReloadPrefs.cs
@@ -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); }
+ }
}
}
diff --git a/Packages/com.singularitygroup.hotreload/Editor/HotReloadState.cs b/Packages/com.singularitygroup.hotreload/Editor/HotReloadState.cs
index d81e09e..e72257f 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/HotReloadState.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/HotReloadState.cs
@@ -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); }
+ }
}
}
diff --git a/Packages/com.singularitygroup.hotreload/Editor/Icon_Player.png b/Packages/com.singularitygroup.hotreload/Editor/Icon_Player.png
index 09e5105..ef9a138 100644
Binary files a/Packages/com.singularitygroup.hotreload/Editor/Icon_Player.png and b/Packages/com.singularitygroup.hotreload/Editor/Icon_Player.png differ
diff --git a/Packages/com.singularitygroup.hotreload/Editor/Installation/InstallUtility.cs b/Packages/com.singularitygroup.hotreload/Editor/Installation/InstallUtility.cs
index 8e28259..dcabdd2 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/Installation/InstallUtility.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/Installation/InstallUtility.cs
@@ -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();
}
diff --git a/Packages/com.singularitygroup.hotreload/Editor/PlayerBuild/HotReloadBuildHelper.cs b/Packages/com.singularitygroup.hotreload/Editor/PlayerBuild/HotReloadBuildHelper.cs
index 8c553f2..c767fd8 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/PlayerBuild/HotReloadBuildHelper.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/PlayerBuild/HotReloadBuildHelper.cs
@@ -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
}
}
\ No newline at end of file
diff --git a/Packages/com.singularitygroup.hotreload/Editor/ProjectGeneration/ProjectGeneration.cs b/Packages/com.singularitygroup.hotreload/Editor/ProjectGeneration/ProjectGeneration.cs
index cb2d3bf..c968fe9 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/ProjectGeneration/ProjectGeneration.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/ProjectGeneration/ProjectGeneration.cs
@@ -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())
// #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(key, b.Substring(index + 1));
+ return new KeyValuePair(key.ToLowerInvariant(), b.Substring(index + 1));
}
const string warnaserror = "warnaserror";
diff --git a/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.Editor.asmdef b/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.Editor.asmdef
index fa46fc6..a47a0d4 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.Editor.asmdef
+++ b/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.Editor.asmdef
@@ -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": []
diff --git a/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.EditorDependencies.dll b/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.EditorDependencies.dll
index 0dbdd18..eb26b0c 100644
Binary files a/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.EditorDependencies.dll and b/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.EditorDependencies.dll differ
diff --git a/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.EditorDependencies.dll.meta b/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.EditorDependencies.dll.meta
index 4e2eed2..08ef494 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.EditorDependencies.dll.meta
+++ b/Packages/com.singularitygroup.hotreload/Editor/SingularityGroup.HotReload.EditorDependencies.dll.meta
@@ -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:
diff --git a/Packages/com.singularitygroup.hotreload/Editor/Versioning/PackageUpdateChecker.cs b/Packages/com.singularitygroup.hotreload/Editor/Versioning/PackageUpdateChecker.cs
index cc12811..f6163aa 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/Versioning/PackageUpdateChecker.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/Versioning/PackageUpdateChecker.cs
@@ -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;
diff --git a/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/Helpers/RedeemLicenseHelper.cs b/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/Helpers/RedeemLicenseHelper.cs
index a0c841f..773d55e 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/Helpers/RedeemLicenseHelper.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/Helpers/RedeemLicenseHelper.cs
@@ -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();
diff --git a/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadRunTab.cs b/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadRunTab.cs
index 42e6561..0a3556a 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadRunTab.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadRunTab.cs
@@ -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(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(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" : "";
diff --git a/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadSettingsTab.cs b/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadSettingsTab.cs
index 0675d91..8228df9 100644
--- a/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadSettingsTab.cs
+++ b/Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadSettingsTab.cs
@@ -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;
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Burst/JobHotReloadUtility.cs b/Packages/com.singularitygroup.hotreload/Runtime/Burst/JobHotReloadUtility.cs
index 344095d..a0debd1 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/Burst/JobHotReloadUtility.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/Burst/JobHotReloadUtility.cs
@@ -1,11 +1,12 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
+using System;
using System.Reflection;
using SingularityGroup.HotReload.DTO;
namespace SingularityGroup.HotReload.Burst {
public static class JobHotReloadUtility {
- public static void HotReloadBurstCompiledJobs(CodePatch patch, Module module) {
- JobPatchUtility.PatchBurstCompiledJobs(patch, module, unityMajorVersion:
+ public static void HotReloadBurstCompiledJobs(SUnityJob jobData, Type proxyJobType) {
+ JobPatchUtility.PatchBurstCompiledJobs(jobData, proxyJobType, unityMajorVersion:
#if UNITY_2022_2_OR_NEWER
2022
#elif UNITY_2021_3_OR_NEWER
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/CodePatcher.cs b/Packages/com.singularitygroup.hotreload/Runtime/CodePatcher.cs
index 9e8e71e..f6204d1 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/CodePatcher.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/CodePatcher.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -13,8 +14,13 @@ using JetBrains.Annotations;
using SingularityGroup.HotReload.Burst;
using SingularityGroup.HotReload.HarmonyLib;
using SingularityGroup.HotReload.JsonConverters;
+using SingularityGroup.HotReload.MonoMod.Utils;
using SingularityGroup.HotReload.Newtonsoft.Json;
using SingularityGroup.HotReload.RuntimeDependencies;
+#if UNITY_EDITOR
+using UnityEditor;
+using UnityEditorInternal;
+#endif
using UnityEngine;
using UnityEngine.SceneManagement;
@@ -24,8 +30,23 @@ namespace SingularityGroup.HotReload {
class RegisterPatchesResult {
// note: doesn't include removals and method definition changes (e.g. renames)
public readonly List patchedMethods = new List();
+ public List addedFields = new List();
public readonly List patchedSMethods = new List();
+ public bool inspectorModified;
public readonly List> patchFailures = new List>();
+ public readonly List patchExceptions = new List();
+ }
+
+ class FieldHandler {
+ public readonly Action storeField;
+ public readonly Action registerInspectorFieldAttributes;
+ public readonly Func hideField;
+
+ public FieldHandler(Action storeField, Func hideField, Action registerInspectorFieldAttributes) {
+ this.storeField = storeField;
+ this.hideField = hideField;
+ this.registerInspectorFieldAttributes = registerInspectorFieldAttributes;
+ }
}
class CodePatcher {
@@ -42,6 +63,8 @@ namespace SingularityGroup.HotReload {
string[] assemblySearchPaths;
SymbolResolver symbolResolver;
readonly string tmpDir;
+ public FieldHandler fieldHandler;
+ public bool debuggerCompatibilityEnabled;
CodePatcher() {
pendingPatches = new List();
@@ -108,6 +131,26 @@ namespace SingularityGroup.HotReload {
if (seenResponses.Contains(response.id)) {
continue;
}
+ foreach (var patch in response.patches) {
+ var asm = Assembly.Load(patch.patchAssembly, patch.patchPdb);
+ SymbolResolver.AddAssembly(asm);
+ }
+ HandleRemovedUnityMethods(response.removedMethod);
+#if UNITY_EDITOR
+ HandleAlteredFields(response.id, result, response.alteredFields);
+#endif
+ // needs to come before RegisterNewFieldInitializers
+ RegisterNewFieldDefinitions(response);
+ // Note: order is important here. Reshaped fields require new field initializers to be added
+ // because the old initializers must override new initilaizers for existing holders.
+ // so that the initializer is not invoked twice
+ RegisterNewFieldInitializers(response);
+ HandleReshapedFields(response);
+ RemoveOldFieldInitializers(response);
+#if UNITY_EDITOR
+ RegisterInspectorFieldAttributes(result, response);
+#endif
+
HandleMethodPatchResponse(response, result);
patchHistory.Add(response);
@@ -152,11 +195,8 @@ namespace SingularityGroup.HotReload {
foreach(var patch in response.patches) {
try {
- var asm = Assembly.Load(patch.patchAssembly, patch.patchPdb);
-
- var module = asm.GetLoadedModules()[0];
foreach(var sMethod in patch.newMethods) {
- var newMethod = module.ResolveMethod(sMethod.metadataToken);
+ var newMethod = SymbolResolver.Resolve(sMethod);
try {
UnityEventHelper.EnsureUnityEventMethod(newMethod);
} catch(Exception ex) {
@@ -171,43 +211,273 @@ namespace SingularityGroup.HotReload {
}
}
- symbolResolver.AddAssembly(asm);
for (int i = 0; i < patch.modifiedMethods.Length; i++) {
var sOriginalMethod = patch.modifiedMethods[i];
var sPatchMethod = patch.patchMethods[i];
- var err = PatchMethod(module: module, sOriginalMethod: sOriginalMethod, sPatchMethod: sPatchMethod, containsBurstJobs: patch.unityJobs.Length > 0, patchesResult: result);
+ var err = PatchMethod(response.id, sOriginalMethod: sOriginalMethod, sPatchMethod: sPatchMethod, containsBurstJobs: patch.unityJobs.Length > 0, patchesResult: result);
if (!string.IsNullOrEmpty(err)) {
result.patchFailures.Add(Tuple.Create(sOriginalMethod, err));
}
}
- JobHotReloadUtility.HotReloadBurstCompiledJobs(patch, module);
+ foreach (var job in patch.unityJobs) {
+ var type = SymbolResolver.Resolve(new SType(patch.assemblyName, job.jobKind.ToString(), job.metadataToken));
+ JobHotReloadUtility.HotReloadBurstCompiledJobs(job, type);
+ }
+#if UNITY_EDITOR
+ HandleNewFields(patch.patchId, result, patch.newFields);
+#endif
+ } catch (Exception ex) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.Exception), new EditorExtraData {
+ { StatKey.PatchId, patch.patchId },
+ { StatKey.Detailed_Exception, ex.ToString() },
+ }).Forget();
+ result.patchExceptions.Add($"Edit requires full recompile to apply: Encountered exception when applying a patch.\nCommon causes: editing code that failed to patch previously, an unsupported change, or a real bug in Hot Reload.\nIf you think this is a bug, please report the issue on Discord and include a code-snippet before/after.\nException: {ex}");
+ }
+ }
+ }
+
+ void HandleRemovedUnityMethods(SMethod[] removedMethods) {
+ if (removedMethods == null) {
+ return;
+ }
+ foreach(var sMethod in removedMethods) {
+ try {
+ var oldMethod = SymbolResolver.Resolve(sMethod);
+ UnityEventHelper.RemoveUnityEventMethod(oldMethod);
+ } catch (SymbolResolvingFailedException) {
+ // ignore, not a unity event method if can't resolve
} catch(Exception ex) {
- Log.Warning("Failed to apply patch with id: {0}\n{1}", patch.patchId, ex);
+ Log.Warning("Encountered exception in RemoveUnityEventMethod: {0} {1}", ex.GetType().Name, ex.Message);
+ }
+ }
+ }
+
+ // Important: must come before applying any patches
+ void RegisterNewFieldInitializers(MethodPatchResponse resp) {
+ for (var i = 0; i < resp.addedFieldInitializerFields.Length; i++) {
+ var sField = resp.addedFieldInitializerFields[i];
+ var sMethod = resp.addedFieldInitializerInitializers[i];
+ try {
+ var declaringType = SymbolResolver.Resolve(sField.declaringType);
+ var method = SymbolResolver.Resolve(sMethod);
+ if (!(method is MethodInfo initializer)) {
+ Log.Warning($"Failed registering initializer for field {sField.fieldName} in {sField.declaringType.typeName}. Field value might not be initialized correctly. Invalid method.");
+ continue;
+ }
+ // We infer if the field is static by the number of parameters the method has
+ // because sField is old field
+ var isStatic = initializer.GetParameters().Length == 0;
+ MethodUtils.DisableVisibilityChecks(initializer);
+ // Initializer return type is used in place of fieldType because latter might be point to old field if the type changed
+ FieldInitializerRegister.RegisterInitializer(declaringType, sField.fieldName, initializer.ReturnType, initializer, isStatic);
+
+ } catch (Exception e) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.RegisterFieldInitializer), new EditorExtraData {
+ { StatKey.PatchId, resp.id },
+ { StatKey.Detailed_Exception, e.ToString() },
+ }).Forget();
+ Log.Warning($"Failed registering initializer for field {sField.fieldName} in {sField.declaringType.typeName}. Field value might not be initialized correctly. Exception: {e.Message}");
+ }
+ }
+ }
+
+ void RegisterNewFieldDefinitions(MethodPatchResponse resp) {
+ foreach (var sField in resp.newFieldDefinitions) {
+ try {
+ var declaringType = SymbolResolver.Resolve(sField.declaringType);
+ var fieldType = SymbolResolver.Resolve(sField).FieldType;
+ FieldResolver.RegisterFieldType(declaringType, sField.fieldName, fieldType);
+ } catch (Exception e) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.RegisterFieldDefinition), new EditorExtraData {
+ { StatKey.PatchId, resp.id },
+ { StatKey.Detailed_Exception, e.ToString() },
+ }).Forget();
+ Log.Warning($"Failed registering new field definitions for field {sField.fieldName} in {sField.declaringType.typeName}. Exception: {e.Message}");
+ }
+ }
+ }
+
+ // Important: must come before applying any patches
+ // Note: server might decide not to report removed field initializer at all if it can handle it
+ void RemoveOldFieldInitializers(MethodPatchResponse resp) {
+ foreach (var sField in resp.removedFieldInitializers) {
+ try {
+ var declaringType = SymbolResolver.Resolve(sField.declaringType);
+ var fieldType = SymbolResolver.Resolve(sField.declaringType);
+ FieldInitializerRegister.UnregisterInitializer(declaringType, sField.fieldName, fieldType, sField.isStatic);
+ } catch (Exception e) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.UnregisterFieldInitializer), new EditorExtraData {
+ { StatKey.PatchId, resp.id },
+ { StatKey.Detailed_Exception, e.ToString() },
+ }).Forget();
+ Log.Warning($"Failed removing initializer for field {sField.fieldName} in {sField.declaringType.typeName}. Field value might not be initialized correctly. Exception: {e.Message}");
+ }
+ }
+ }
+
+ // Important: must come before applying any patches
+ // Should also come after RegisterNewFieldInitializers so that new initializers are not invoked for existing objects
+ internal void HandleReshapedFields(MethodPatchResponse resp) {
+ foreach(var patch in resp.patches) {
+ var removedReshapedFields = patch.deletedFields;
+ var renamedReshapedFieldsFrom = patch.renamedFieldsFrom;
+ var renamedReshapedFieldsTo = patch.renamedFieldsTo;
+
+ foreach (var f in removedReshapedFields) {
+ try {
+ var declaringType = SymbolResolver.Resolve(f.declaringType);
+ var fieldType = SymbolResolver.Resolve(f).FieldType;
+ FieldResolver.ClearHolders(declaringType, f.isStatic, f.fieldName, fieldType);
+ } catch (Exception e) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.ClearHolders), new EditorExtraData {
+ { StatKey.PatchId, resp.id },
+ { StatKey.Detailed_Exception, e.ToString() },
+ }).Forget();
+ Log.Warning($"Failed removing field value from {f.fieldName} in {f.declaringType.typeName}. Field value in code might not be up to date. Exception: {e.Message}");
+ }
+ }
+ for (var i = 0; i < renamedReshapedFieldsFrom.Length; i++) {
+ var fromField = renamedReshapedFieldsFrom[i];
+ var toField = renamedReshapedFieldsTo[i];
+ try {
+ var declaringType = SymbolResolver.Resolve(fromField.declaringType);
+ var fieldType = SymbolResolver.Resolve(fromField).FieldType;
+ var toFieldType = SymbolResolver.Resolve(toField).FieldType;
+ if (!AreSTypesCompatible(fromField.declaringType, toField.declaringType)
+ || fieldType != toFieldType
+ || fromField.isStatic != toField.isStatic
+ ) {
+ FieldResolver.ClearHolders(declaringType, fromField.isStatic, fromField.fieldName, fieldType);
+ continue;
+ }
+ FieldResolver.MoveHolders(declaringType, fromField.fieldName, toField.fieldName, fieldType, fromField.isStatic);
+ } catch (Exception e) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.MoveHolders), new EditorExtraData {
+ { StatKey.PatchId, resp.id },
+ { StatKey.Detailed_Exception, e.ToString() },
+ }).Forget();
+ Log.Warning($"Failed moving field value from {fromField} to {toField} in {toField.declaringType.typeName}. Field value in code might not be up to date. Exception: {e.Message}");
+ }
}
}
}
+ internal bool AreSTypesCompatible(SType one, SType two) {
+ if (one.isGenericParameter != two.isGenericParameter) {
+ return false;
+ }
+ if (one.metadataToken != two.metadataToken) {
+ return false;
+ }
+ if (one.assemblyName != two.assemblyName) {
+ return false;
+ }
+ if (one.genericParameterPosition != two.genericParameterPosition) {
+ return false;
+ }
+ if (one.typeName != two.typeName) {
+ return false;
+ }
+ return true;
+ }
+
+#if UNITY_EDITOR
+ internal void RegisterInspectorFieldAttributes(RegisterPatchesResult result, MethodPatchResponse resp) {
+ foreach (var patch in resp.patches) {
+ var propertyAttributesFieldOriginal = patch.propertyAttributesFieldOriginal ?? Array.Empty();
+ var propertyAttributesFieldUpdated = patch.propertyAttributesFieldUpdated ?? Array.Empty();
+ for (var i = 0; i < propertyAttributesFieldOriginal.Length; i++) {
+ var original = propertyAttributesFieldOriginal[i];
+ var updated = propertyAttributesFieldUpdated[i];
+ try {
+ var declaringType = SymbolResolver.Resolve(original.declaringType);
+ var originalField = SymbolResolver.Resolve(original);
+ var updatedField = SymbolResolver.Resolve(updated);
+ fieldHandler?.registerInspectorFieldAttributes?.Invoke(declaringType, originalField, updatedField);
+ result.inspectorModified = true;
+ } catch (Exception e) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.MoveHolders), new EditorExtraData {
+ { StatKey.PatchId, resp.id },
+ { StatKey.Detailed_Exception, e.ToString() },
+ }).Forget();
+ Log.Warning($"Failed updating field attributes of {original.fieldName} in {original.declaringType.typeName}. Updates might not reflect in the inspector. Exception: {e.Message}");
+ }
+ }
+ }
+ }
+
+ internal void HandleNewFields(string patchId, RegisterPatchesResult result, SField[] sFields) {
+ foreach (var sField in sFields) {
+ if (!sField.serializable) {
+ continue;
+ }
+ try {
+ var declaringType = SymbolResolver.Resolve(sField.declaringType);
+ var field = SymbolResolver.Resolve(sField);
+ fieldHandler?.storeField?.Invoke(declaringType, field);
+ result.inspectorModified = true;
+ } catch (Exception e) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.AddInspectorField), new EditorExtraData {
+ { StatKey.PatchId, patchId },
+ { StatKey.Detailed_Exception, e.ToString() },
+ }).Forget();
+ Log.Warning($"Failed adding field {sField.fieldName}:{sField.declaringType.typeName} to the inspector. Field will not be displayed. Exception: {e.Message}");
+ }
+ }
+ result.addedFields.AddRange(sFields);
+ }
+
+ // IMPORTANT: must come before HandleNewFields. Might contain new fields which we don't want to hide
+ internal void HandleAlteredFields(string patchId, RegisterPatchesResult result, SField[] alteredFields) {
+ if (alteredFields == null) {
+ return;
+ }
+ bool alteredFieldHidden = false;
+ foreach(var sField in alteredFields) {
+ try {
+ var declaringType = SymbolResolver.Resolve(sField.declaringType);
+ if (fieldHandler?.hideField?.Invoke(declaringType, sField.fieldName) == true) {
+ alteredFieldHidden = true;
+ }
+ } catch(Exception e) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.HideInspectorField), new EditorExtraData {
+ { StatKey.PatchId, patchId },
+ { StatKey.Detailed_Exception, e.ToString() },
+ }).Forget();
+ Log.Warning($"Failed hiding field {sField.fieldName}:{sField.declaringType.typeName} from the inspector. Exception: {e.Message}");
+ }
+ }
+ if (alteredFieldHidden) {
+ result.inspectorModified = true;
+ }
+ }
+#endif
+
Dictionary previousPatchMethods = new Dictionary();
+ public IEnumerable OriginalPatchMethods => previousPatchMethods.Keys;
List newMethods = new List();
- string PatchMethod(Module module, SMethod sOriginalMethod, SMethod sPatchMethod, bool containsBurstJobs, RegisterPatchesResult patchesResult) {
+ string PatchMethod(string patchId, SMethod sOriginalMethod, SMethod sPatchMethod, bool containsBurstJobs, RegisterPatchesResult patchesResult) {
try {
- var patchMethod = module.ResolveMethod(sPatchMethod.metadataToken);
+ var patchMethod = SymbolResolver.Resolve(sPatchMethod);
var start = DateTime.UtcNow;
var state = TryResolveMethod(sOriginalMethod, patchMethod);
+ if (Debugger.IsAttached && !debuggerCompatibilityEnabled) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.DebuggerAttached), new EditorExtraData {
+ { StatKey.PatchId, patchId },
+ }).Forget();
+ return "Patching methods is not allowed while the Debugger is attached. You can change this behavior in settings if Hot Reload is compatible with the debugger you're running.";
+ }
if (DateTime.UtcNow - start > TimeSpan.FromMilliseconds(500)) {
Log.Info("Hot Reload apply took {0}", (DateTime.UtcNow - start).TotalMilliseconds);
}
if(state.match == null) {
- var error =
- "Method mismatch: {0}, patch: {1}. This can have multiple reasons:\n"
- + "1. You are running the Editor multiple times for the same project using symlinks, and are making changes from the symlink project\n"
- + "2. A bug in Hot Reload. Please send us a reproduce (code before/after), and we'll get it fixed for you\n"
- ;
- Log.Warning(error, sOriginalMethod.simpleName, patchMethod.Name);
-
+ var error = "Edit requires full recompile to apply: Method mismatch: {0}, patch: {1}. \nCommon causes: editing code that failed to patch previously, an unsupported change, or a real bug in Hot Reload.\nIf you think this is a bug, please report the issue on Discord and include a code-snippet before/after.";
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.MethodMismatch), new EditorExtraData {
+ { StatKey.PatchId, patchId },
+ }).Forget();
return string.Format(error, sOriginalMethod.simpleName, patchMethod.Name);
}
@@ -239,10 +509,18 @@ namespace SingularityGroup.HotReload {
//ignore. The method is likely burst compiled and can't be patched
return null;
} else {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.Failure), new EditorExtraData {
+ { StatKey.PatchId, patchId },
+ { StatKey.Detailed_Exception, result.exception.ToString() },
+ }).Forget();
return HandleMethodPatchFailure(sOriginalMethod, result.exception);
}
}
} catch(Exception ex) {
+ RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.Exception), new EditorExtraData {
+ { StatKey.PatchId, patchId },
+ { StatKey.Detailed_Exception, ex.ToString() },
+ }).Forget();
return HandleMethodPatchFailure(sOriginalMethod, ex);
}
}
@@ -316,7 +594,11 @@ namespace SingularityGroup.HotReload {
MethodBase resolvedMethod = null;
try {
resolvedMethod = TryGetMethodBaseWithRelativeToken(methodToResolve, offset);
- if(!MethodCompatiblity.AreMethodsCompatible(resolvedMethod, patchMethod)) {
+ var err = MethodCompatiblity.CheckCompatibility(resolvedMethod, patchMethod);
+ if(err != null) {
+ // if (resolvedMethod.Name == patchMethod.Name) {
+ // Log.Info(err);
+ // }
resolvedMethod = null;
}
} catch (SymbolResolvingFailedException ex) when(ex.InnerException is ArgumentOutOfRangeException) {
@@ -331,15 +613,11 @@ namespace SingularityGroup.HotReload {
return symbolResolver.Resolve(new SMethod(sOriginalMethod.assemblyName,
sOriginalMethod.displayName,
sOriginalMethod.metadataToken + offset,
- sOriginalMethod.genericTypeArguments,
- sOriginalMethod.genericTypeArguments,
sOriginalMethod.simpleName));
}
string HandleMethodPatchFailure(SMethod method, Exception exception) {
- var err = $"Failed to apply patch for method {method.displayName} in assembly {method.assemblyName}\n{exception}";
- Log.Warning(err);
- return err;
+ return $"Edit requires full recompile to apply: Failed to apply patch for method {method.displayName} in assembly {method.assemblyName}.\nCommon causes: editing code that failed to patch previously, an unsupported change, or a real bug in Hot Reload.\nIf you think this is a bug, please report the issue on Discord and include a code-snippet before/after.\nException: {exception}";
}
void EnsureSymbolResolver() {
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Demo/Scenes/HotReloadBasicDemo.unity b/Packages/com.singularitygroup.hotreload/Runtime/Demo/Scenes/HotReloadBasicDemo.unity
index a967bee..ca6a3dd 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/Demo/Scenes/HotReloadBasicDemo.unity
+++ b/Packages/com.singularitygroup.hotreload/Runtime/Demo/Scenes/HotReloadBasicDemo.unity
@@ -38,12 +38,12 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
- m_IndirectSpecularColor: {r: 0.18028378, g: 0.22571412, b: 0.30692285, a: 1}
+ m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
- serializedVersion: 12
+ serializedVersion: 11
m_GIWorkflowMode: 0
m_GISettings:
serializedVersion: 2
@@ -98,13 +98,13 @@ LightmapSettings:
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 0}
- m_LightingSettings: {fileID: 4890085278179872738, guid: 463b4a464af955e4d8d6b0a2923d94d0, type: 2}
+ m_UseShadowmask: 1
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
- serializedVersion: 3
+ serializedVersion: 2
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
@@ -117,9 +117,7 @@ NavMeshSettings:
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
- buildHeightMesh: 0
- maxJobWorkers: 0
- preserveTilesOutsideBounds: 0
+ accuratePlacement: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
@@ -150,18 +148,10 @@ BoxCollider:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 19295889}
m_Material: {fileID: 0}
- m_IncludeLayers:
- serializedVersion: 2
- m_Bits: 0
- m_ExcludeLayers:
- serializedVersion: 2
- m_Bits: 0
- m_LayerOverridePriority: 0
m_IsTrigger: 0
- m_ProvidesContacts: 0
m_Enabled: 1
- serializedVersion: 3
- m_Size: {x: 1, y: 1, z: 1}
+ serializedVersion: 2
+ m_Size: {x: 2, y: 2, z: 2}
m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &19295891
MeshRenderer:
@@ -174,12 +164,10 @@ MeshRenderer:
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
- m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
- m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -204,7 +192,6 @@ MeshRenderer:
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
- m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &19295892
MeshFilter:
m_ObjectHideFlags: 0
@@ -223,7 +210,6 @@ Transform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 4
@@ -268,17 +254,9 @@ Camera:
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
- m_Iso: 200
- m_ShutterSpeed: 0.005
- m_Aperture: 16
- m_FocusDistance: 10
- m_FocalLength: 50
- m_BladeCount: 5
- m_Curvature: {x: 2, y: 11}
- m_BarrelClipping: 0.25
- m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
+ m_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
@@ -315,7 +293,6 @@ Transform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
@@ -348,7 +325,6 @@ RectTransform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 511172213}
m_RootOrder: 0
@@ -373,7 +349,6 @@ MonoBehaviour:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
- m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
@@ -429,7 +404,6 @@ RectTransform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 460271677}
m_Father: {fileID: 649153321}
@@ -454,7 +428,6 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
- m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
@@ -499,7 +472,6 @@ MonoBehaviour:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
- m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
@@ -580,7 +552,6 @@ MonoBehaviour:
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
- m_PresetInfoIsWorld: 0
--- !u!223 &649153320
Canvas:
m_ObjectHideFlags: 0
@@ -599,7 +570,6 @@ Canvas:
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_AdditionalShaderChannelsFlag: 0
- m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 0
m_TargetDisplay: 0
@@ -613,7 +583,6 @@ RectTransform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
- m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1537524790}
- {fileID: 1847025553}
@@ -656,7 +625,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
m_Name:
m_EditorClassIdentifier:
- m_SendPointerHoverToParent: 1
m_HorizontalAxis: Horizontal
m_VerticalAxis: Vertical
m_SubmitButton: Submit
@@ -689,7 +657,6 @@ Transform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 5
@@ -770,7 +737,6 @@ Light:
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
- m_UseViewFrustumForShadowCasterCull: 1
m_ShadowRadius: 0
m_ShadowAngle: 0
--- !u!4 &965437872
@@ -783,7 +749,6 @@ Transform:
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 1
@@ -816,7 +781,6 @@ RectTransform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1847025553}
m_RootOrder: 0
@@ -841,7 +805,6 @@ MonoBehaviour:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
- m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
@@ -896,7 +859,6 @@ RectTransform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 649153321}
m_RootOrder: 0
@@ -921,7 +883,6 @@ MonoBehaviour:
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 1}
m_RaycastTarget: 1
- m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
@@ -977,7 +938,6 @@ RectTransform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1101930859}
m_Father: {fileID: 649153321}
@@ -1002,7 +962,6 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
- m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
@@ -1047,7 +1006,6 @@ MonoBehaviour:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
- m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
@@ -1097,7 +1055,6 @@ Transform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 18.716805, y: 53.419094, z: 92.546875}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 2
@@ -1119,3 +1076,4 @@ MonoBehaviour:
openWindowButton: {fileID: 1847025554}
openScriptButton: {fileID: 511172214}
thisScript: {fileID: 11500000, guid: 5a2e4d3f095a9441688c70278068eee0, type: 3}
+ myEditedField: 54
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Demo/Scripts/HotReloadBasicDemo.cs b/Packages/com.singularitygroup.hotreload/Runtime/Demo/Scripts/HotReloadBasicDemo.cs
index 724abdd..8c2f936 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/Demo/Scripts/HotReloadBasicDemo.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/Demo/Scripts/HotReloadBasicDemo.cs
@@ -8,24 +8,27 @@ using UnityEngine.UI;
namespace SingularityGroup.HotReload.Demo {
class HotReloadBasicDemo : MonoBehaviour {
-
+
public GameObject cube;
public Text informationText;
public Button openWindowButton;
public Button openScriptButton;
public TextAsset thisScript;
-
+
+ // // 1. Adding fields (Added fields can show in the inspector)
+ // public int myNewField = 1;
+
void Start() {
- if(Application.isEditor) {
+ if (Application.isEditor) {
openWindowButton.onClick.AddListener(Demo.I.OpenHotReloadWindow);
- openScriptButton.onClick.AddListener(() => Demo.I.OpenScriptFile(thisScript, 31, 13));
+ openScriptButton.onClick.AddListener(() => Demo.I.OpenScriptFile(thisScript, myStaticField, 13));
} else {
openWindowButton.gameObject.SetActive(false);
openScriptButton.gameObject.SetActive(false);
informationText.gameObject.SetActive(false);
}
}
-
+
// Update is called once per frame
void Update() {
if (Demo.I.IsServerRunning()) {
@@ -33,25 +36,25 @@ namespace SingularityGroup.HotReload.Demo {
} else {
informationText.text = "Hot Reload is not running";
}
-
- // // 1. Editing functions in monobehaviours, normal classes or static classes
+
+ // // 2. Editing functions in monobehaviours, normal classes or static classes
// // Edit the vector to rotate the cube in the scene differently or change the speed
// var speed = 100;
// cube.transform.Rotate(new Vector3(0, 1, 0) * Time.deltaTime * speed);
-
- // // 1. Editing functions in monobehaviours, normal classes or static classes
+
+ // // 2. Editing functions in monobehaviours, normal classes or static classes
// // Uncomment this code to scale the cube
// cube.transform.localScale = Mathf.Sin(Time.time) * Vector3.one;
-
- // // 1. Editing functions in monobehaviours, normal classes or static classes
+
+ // // 2. Editing functions in monobehaviours, normal classes or static classes
// // Uncomment this code to make the cube move from left to right and back
// var newPos = cube.transform.position += (cube.transform.localScale.x < 0.5 ? Vector3.left : Vector3.right) * Time.deltaTime;
// if(Mathf.Abs(newPos.x) > 10) {
// cube.transform.position = Vector3.zero;
// }
}
-
- // 2. Editing lambda methods
+
+ // 3. Editing lambda methods
static Func addFunction = x => {
var result = x + 10;
Debug.Log("Add: " + result);
@@ -60,17 +63,17 @@ namespace SingularityGroup.HotReload.Demo {
// Debug.Log("Multiply: " + result);
return result;
};
-
- // 3. Editing async/await methods
+
+ // 4. Editing async/await methods
async Task AsyncMethod() {
// await Task.Delay(500);
// Debug.Log("AsyncMethod");
-
+
// // silicense warning
await Task.CompletedTask;
}
-
- // 4. Editing properties (get/set)
+
+ // 5. Editing properties (get/set)
public static string SomeString {
// edit the get method
get {
@@ -78,8 +81,8 @@ namespace SingularityGroup.HotReload.Demo {
return someStringHere;
}
}
-
- // 5. Editing indexers (square bracket access such as dictionaries)
+
+ // 6. Editing indexers (square bracket access such as dictionaries)
class CustomDictionary : Dictionary {
public new int this[string key] {
get {
@@ -102,8 +105,8 @@ namespace SingularityGroup.HotReload.Demo {
{ "d", 19 },
{ "D", 20 }
};
-
- // 6. Editing operators methods (explicit and implicit operators)
+
+ // 7. Editing operators methods (explicit and implicit operators)
public class Email {
public string Value { get; }
@@ -116,7 +119,7 @@ namespace SingularityGroup.HotReload.Demo {
// Uncomment to change the implicit operator
// => value.Value + " FOO";
=> value.Value;
-
+
// // Uncomment to change add an implicit operator
// public static implicit operator byte[](Email value)
// => Encoding.UTF8.GetBytes(value.Value);
@@ -126,51 +129,86 @@ namespace SingularityGroup.HotReload.Demo {
=> new Email(value);
}
+ // 8. Editing fields: modifiers/type/name/initializer
+ public int myEditedField = 4;
+
+ // 9. Editing static field initializers (variable value is updated)
+ static readonly int myStaticField = 31;
+
+ // // 10. Adding auto properties/events
+ // int MyProperty { get; set; } = 6;
+ // event Action MyEvent = () => Debug.Log("MyEvent");
+
+ class GenericClass {
+ // // 11. Adding methods in generic classes
+ // public void GenericMethod() {
+ // Debug.Log("GenericMethod");
+ // }
+ // // 12. Adding fields (any type) in generic classes
+ // public T myGenericField;
+ }
+
void LateUpdate() {
- // // 2. Editing lambda methods
+ // // 3. Editing lambda methods
// addFunction(10);
-
-
- // // 3. Editing async/await methods
+
+
+ // // 4. Editing async/await methods
// AsyncMethod().Forget();
-
-
- // // 4. Editing properties (get/set)
+
+
+ // // 5. Editing properties (get/set)
// Debug.Log(SomeString);
-
-
- // // 5. Editing indexers (square bracket access such as dictionaries)
+
+
+ // // 6. Editing indexers (square bracket access such as dictionaries)
// Debug.Log(randomDict["A"]);
-
-
- // // 6. Editing operators methods (explicit and implicit operators)
+
+
+ // // 7. Editing operators methods (explicit and implicit operators)
Email email = new Email("example@example.com");
// string stringEmail = email;
// Debug.Log(stringEmail);
-
+
// // Uncomment new operator in Email class + Uncomment this to add byte implicit operator
// byte[] byteEmail = email;
// var hexRepresentation = BitConverter.ToString(byteEmail);
// Debug.Log(hexRepresentation);
// Debug.Log(Encoding.UTF8.GetString(byteEmail));
+
+ // // 8. Editing fields: modifiers/type/name/initializer
+ // Debug.Log("myEditedField: " + myEditedField);
+
+ // // 9. Editing static field initializers (variable value is updated)
+ // Debug.Log("myStaticField: " + myStaticField);
+
+ // // 10. Adding auto properties/events
+ // Debug.Log("MyProperty: " + MyProperty);
+ // MyEvent.Invoke();
- // // 7. Editing lambda methods with closures
+ // var newClass = new GenericClass();
+ // // 11. Adding methods in generic classes
+ // newClass.GenericMethod();
+ // // 12. Adding fields in generic classes
+ // newClass.myGenericField = 3;
+ // Debug.Log("myGenericField: " + newClass.myGenericField);
+
+ // // 13. Editing lambda methods with closures
// // Uncomment to log sorted array
// // Switch a and b to reverse the sorting
// int[] numbers = { 5, 3, 8, 1, 9 };
// Array.Sort(numbers, (b, a) => a.CompareTo(b));
// Debug.Log(string.Join(", ", numbers));
-
}
-
+
// This function gets invoked every time it's patched
[InvokeOnHotReloadLocal]
static void OnHotReloadMe() {
- // change the string to see the method getting invoked
- Debug.Log("Hello there");
+ // // change the string to see the method getting invoked
+ // Debug.Log("Hello there");
}
- // // 8. Adding event functions
+ // // 14. Adding event functions
// void OnDisable() {
// Debug.Log("OnDisable");
// }
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies.dll b/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies.dll
index f5a7ca8..6f88328 100644
Binary files a/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies.dll and b/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies.dll differ
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2019.dll b/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2019.dll
index 4c93350..7069e00 100644
Binary files a/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2019.dll and b/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2019.dll differ
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2020.dll b/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2020.dll
index 7e9e033..dcf44db 100644
Binary files a/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2020.dll and b/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2020.dll differ
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2022.dll b/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2022.dll
index a0b7d9d..c0d33b3 100644
Binary files a/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2022.dll and b/Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2022.dll differ
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies.dll b/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies.dll
index f5a7ca8..6f88328 100644
Binary files a/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies.dll and b/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies.dll differ
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2019.dll b/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2019.dll
index 4c93350..7069e00 100644
Binary files a/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2019.dll and b/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2019.dll differ
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2020.dll b/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2020.dll
index 7e9e033..dcf44db 100644
Binary files a/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2020.dll and b/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2020.dll differ
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2022.dll b/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2022.dll
index a0b7d9d..c0d33b3 100644
Binary files a/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2022.dll and b/Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2022.dll differ
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/MethodCompatiblity.cs b/Packages/com.singularitygroup.hotreload/Runtime/MethodCompatiblity.cs
index 7642daf..fd7f844 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/MethodCompatiblity.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/MethodCompatiblity.cs
@@ -6,7 +6,7 @@ using SingularityGroup.HotReload.MonoMod.Utils;
namespace SingularityGroup.HotReload {
static class MethodCompatiblity {
- internal static bool AreMethodsCompatible(MethodBase previousMethod, MethodBase patchMethod) {
+ internal static string CheckCompatibility(MethodBase previousMethod, MethodBase patchMethod) {
var previousConstructor = previousMethod as ConstructorInfo;
var patchConstructor = patchMethod as ConstructorInfo;
if(previousConstructor != null && !ReferenceEquals(patchConstructor, null)) {
@@ -17,20 +17,29 @@ namespace SingularityGroup.HotReload {
if(!ReferenceEquals(previousMethodInfo, null) && !ReferenceEquals(patchMethodInfo, null)) {
return AreMethodInfosCompatible(previousMethodInfo, patchMethodInfo);
}
- return false;
+ return "unknown issue";
}
- static bool AreMethodBasesCompatible(MethodBase previousMethod, MethodBase patchMethod) {
+ static string AreMethodBasesCompatible(MethodBase previousMethod, MethodBase patchMethod) {
if(previousMethod.Name != patchMethod.Name) {
- return false;
+ return "Method name mismatch";
}
//Declaring type of patch method is different from the target method but their full name (namespace + name) is equal
- if(previousMethod.DeclaringType.FullName != patchMethod.DeclaringType.FullName) {
- return false;
+ bool isDeclaringTypeCompatible = false;
+ var declaringType = patchMethod.DeclaringType;
+ while (declaringType != null) {
+ if(previousMethod.DeclaringType?.FullName == declaringType.FullName) {
+ isDeclaringTypeCompatible = true;
+ break;
+ }
+ declaringType = declaringType.BaseType;
+ }
+ if (!isDeclaringTypeCompatible) {
+ return "Declaring type name mismatch";
}
//Check in case type parameter overloads to distinguish between: void M() { } <-> void M() { }
if(previousMethod.IsGenericMethodDefinition != patchMethod.IsGenericMethodDefinition) {
- return false;
+ return "IsGenericMethodDefinition mismatch";
}
var prevParams = previousMethod.GetParameters();
@@ -53,11 +62,11 @@ namespace SingularityGroup.HotReload {
//Special case: patch method for an instance method is static and has an explicit this parameter.
//If the patch method doesn't have any parameters it is not compatible.
if(patchParams.Length == 0) {
- return false;
+ return "missing this parameter";
}
//this parameter has to be the declaring type
if(!ParamTypeMatches(patchParams[0].ParameterType, previousMethod.DeclaringType)) {
- return false;
+ return "this parameter type mismatch";
}
//Ignore the this parameter and compare the remaining ones.
patchParamsSegment = new ArraySegment(patchParams, 1, patchParams.Length - 1);
@@ -75,9 +84,6 @@ namespace SingularityGroup.HotReload {
if (!ParamTypeMatches(patchT, previousMethod.DeclaringType)) {
return false;
}
- if (prevParams.Length >= 1 && prevParams[0].ParameterType == previousMethod.DeclaringType) {
- return false;
- }
return patchParams[0].Name == "this";
}
@@ -85,25 +91,25 @@ namespace SingularityGroup.HotReload {
return patchT == originalT || patchT.IsByRef && patchT.GetElementType() == originalT;
}
- static bool CompareParameters(ArraySegment x, ArraySegment y) {
+ static string CompareParameters(ArraySegment x, ArraySegment y) {
if(x.Count != y.Count) {
- return false;
+ return "parameter count mismatch";
}
for (var i = 0; i < x.Count; i++) {
if(x.Array[i + x.Offset].ParameterType != y.Array[i + y.Offset].ParameterType) {
- return false;
+ return "parameter type mismatch";
}
}
- return true;
+ return null;
}
- static bool AreConstructorsCompatible(ConstructorInfo x, ConstructorInfo y) {
+ static string AreConstructorsCompatible(ConstructorInfo x, ConstructorInfo y) {
return AreMethodBasesCompatible(x, y);
}
- static bool AreMethodInfosCompatible(MethodInfo x, MethodInfo y) {
- return AreMethodBasesCompatible(x, y) && x.ReturnType == y.ReturnType;
+ static string AreMethodInfosCompatible(MethodInfo x, MethodInfo y) {
+ return AreMethodBasesCompatible(x, y) ?? (x.ReturnType == y.ReturnType ? null : "Return type mismatch");
}
}
}
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/MethodPatchResponsesConverter.cs b/Packages/com.singularitygroup.hotreload/Runtime/MethodPatchResponsesConverter.cs
index 55d56b1..64e579b 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/MethodPatchResponsesConverter.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/MethodPatchResponsesConverter.cs
@@ -30,6 +30,11 @@ namespace SingularityGroup.HotReload.JsonConverters {
CodePatch[] patches = null;
string[] failures = null;
SMethod[] removedMethod = null;
+ SField[] alteredFields = null;
+ SField[] addedFieldInitializerFields = null;
+ SMethod[] addedFieldInitializerInitializers = null;
+ SField[] removedFieldInitializers = null;
+ SField[] newFieldDefinitions = null;
while (reader.Read()) {
if (reader.TokenType == JsonToken.EndObject) {
@@ -56,6 +61,26 @@ namespace SingularityGroup.HotReload.JsonConverters {
case nameof(MethodPatchResponse.removedMethod):
removedMethod = ReadSMethodArray(reader);
break;
+
+ case nameof(MethodPatchResponse.alteredFields):
+ alteredFields = ReadSFields(reader);
+ break;
+
+ case nameof(MethodPatchResponse.addedFieldInitializerFields):
+ addedFieldInitializerFields = ReadSFields(reader);
+ break;
+
+ case nameof(MethodPatchResponse.addedFieldInitializerInitializers):
+ addedFieldInitializerInitializers = ReadSMethodArray(reader);
+ break;
+
+ case nameof(MethodPatchResponse.removedFieldInitializers):
+ removedFieldInitializers = ReadSFields(reader);
+ break;
+
+ case nameof(MethodPatchResponse.newFieldDefinitions):
+ newFieldDefinitions = ReadSFields(reader);
+ break;
default:
reader.Skip(); // Skip unknown properties
@@ -68,8 +93,14 @@ namespace SingularityGroup.HotReload.JsonConverters {
patches ?? Array.Empty(),
failures ?? Array.Empty(),
removedMethod ?? Array.Empty(),
- // Note: doesn't have to be persisted here
- Array.Empty()
+ alteredFields ?? Array.Empty(),
+ // Note: suggestions don't have to be persisted here
+ Array.Empty(),
+ Array.Empty(),
+ addedFieldInitializerFields ?? Array.Empty(),
+ addedFieldInitializerInitializers ?? Array.Empty(),
+ removedFieldInitializers ?? Array.Empty(),
+ newFieldDefinitions ?? Array.Empty()
);
}
@@ -91,6 +122,12 @@ namespace SingularityGroup.HotReload.JsonConverters {
SMethod[] patchMethods = null;
SMethod[] newMethods = null;
SUnityJob[] unityJobs = null;
+ SField[] newFields = null;
+ SField[] deletedFields = null;
+ SField[] renamedFieldsFrom = null;
+ SField[] renamedFieldsTo = null;
+ SField[] propertyAttributesFieldOriginal = null;
+ SField[] propertyAttributesFieldUpdated = null;
while (reader.Read()) {
if (reader.TokenType == JsonToken.EndObject) {
@@ -133,6 +170,29 @@ namespace SingularityGroup.HotReload.JsonConverters {
case nameof(CodePatch.unityJobs):
unityJobs = ReadSUnityJobArray(reader);
break;
+
+ case nameof(CodePatch.newFields):
+ newFields = ReadSFields(reader);
+ break;
+ case nameof(CodePatch.deletedFields):
+ deletedFields = ReadSFields(reader);
+ break;
+
+ case nameof(CodePatch.renamedFieldsFrom):
+ renamedFieldsFrom = ReadSFields(reader);
+ break;
+
+ case nameof(CodePatch.renamedFieldsTo):
+ renamedFieldsTo = ReadSFields(reader);
+ break;
+
+ case nameof(CodePatch.propertyAttributesFieldOriginal):
+ propertyAttributesFieldOriginal = ReadSFields(reader);
+ break;
+
+ case nameof(CodePatch.propertyAttributesFieldUpdated):
+ propertyAttributesFieldUpdated = ReadSFields(reader);
+ break;
default:
reader.Skip(); // Skip unknown properties
@@ -148,7 +208,13 @@ namespace SingularityGroup.HotReload.JsonConverters {
modifiedMethods: modifiedMethods ?? Array.Empty(),
patchMethods: patchMethods ?? Array.Empty(),
newMethods: newMethods ?? Array.Empty(),
- unityJobs: unityJobs ?? Array.Empty()
+ unityJobs: unityJobs ?? Array.Empty(),
+ newFields: newFields ?? Array.Empty(),
+ deletedFields: deletedFields ?? Array.Empty(),
+ renamedFieldsFrom: renamedFieldsFrom ?? Array.Empty(),
+ renamedFieldsTo: renamedFieldsTo ?? Array.Empty(),
+ propertyAttributesFieldOriginal: propertyAttributesFieldOriginal ?? Array.Empty(),
+ propertyAttributesFieldUpdated: propertyAttributesFieldUpdated ?? Array.Empty()
));
}
@@ -210,13 +276,23 @@ namespace SingularityGroup.HotReload.JsonConverters {
return array.ToArray();
}
+
+ private SField[] ReadSFields(JsonReader reader) {
+ var array = new List();
+ while (reader.Read()) {
+ if (reader.TokenType == JsonToken.StartObject) {
+ array.Add(ReadSField(reader));
+ } else if (reader.TokenType == JsonToken.EndArray) {
+ break; // End of the SUnityJob array
+ }
+ }
+ return array.ToArray();
+ }
private SMethod ReadSMethod(JsonReader reader) {
string assemblyName = null;
string displayName = null;
int metadataToken = default(int);
- SType[] genericTypeArguments = null;
- SType[] genericArguments = null;
string simpleName = null;
while (reader.Read()) {
@@ -241,14 +317,6 @@ namespace SingularityGroup.HotReload.JsonConverters {
metadataToken = reader.ReadAsInt32() ?? default(int);
break;
- case nameof(SMethod.genericTypeArguments):
- genericTypeArguments = ReadSTypeArray(reader);
- break;
-
- case nameof(SMethod.genericArguments):
- genericArguments = ReadSTypeArray(reader);
- break;
-
case nameof(SMethod.simpleName):
simpleName = reader.ReadAsString();
break;
@@ -263,8 +331,6 @@ namespace SingularityGroup.HotReload.JsonConverters {
assemblyName ?? string.Empty,
displayName ?? string.Empty,
metadataToken,
- genericTypeArguments ?? Array.Empty(),
- genericArguments ?? Array.Empty(),
simpleName ?? string.Empty
);
}
@@ -272,9 +338,12 @@ namespace SingularityGroup.HotReload.JsonConverters {
private SType ReadSType(JsonReader reader) {
string assemblyName = null;
string typeName = null;
- SType[] genericArguments = null;
+ int? metadataToken = null;
while (reader.Read()) {
+ if (reader.TokenType == JsonToken.Null) {
+ return null;
+ }
if (reader.TokenType == JsonToken.EndObject) {
break;
}
@@ -292,8 +361,8 @@ namespace SingularityGroup.HotReload.JsonConverters {
typeName = reader.ReadAsString();
break;
- case nameof(SType.genericArguments):
- genericArguments = ReadSTypeArray(reader);
+ case nameof(SType.metadataToken):
+ metadataToken = reader.ReadAsInt32();
break;
default:
@@ -305,7 +374,7 @@ namespace SingularityGroup.HotReload.JsonConverters {
return new SType(
assemblyName ?? string.Empty,
typeName ?? string.Empty,
- genericArguments ?? Array.Empty()
+ metadataToken ?? 0
);
}
@@ -340,6 +409,57 @@ namespace SingularityGroup.HotReload.JsonConverters {
return new SUnityJob(metadataToken, jobKind);
}
+
+ private SField ReadSField(JsonReader reader) {
+ SType declaringType = null;
+ string fieldName = null;
+ string assemblyName = null;
+ int? metadataToken = null;
+ bool? serializable = null;
+ bool? isStatic = null;
+
+ while (reader.Read()) {
+ if (reader.TokenType == JsonToken.EndObject) {
+ break;
+ }
+ if (reader.TokenType != JsonToken.PropertyName) {
+ continue;
+ }
+ var propertyName = (string)reader.Value;
+
+ switch (propertyName) {
+ case nameof(SField.declaringType):
+ declaringType = ReadSType(reader);
+ break;
+
+ case nameof(SField.fieldName):
+ fieldName = reader.ReadAsString();
+ break;
+
+ case nameof(SField.assemblyName):
+ assemblyName = reader.ReadAsString();
+ break;
+
+ case nameof(SField.metadataToken):
+ metadataToken = reader.ReadAsInt32();
+ break;
+
+ case nameof(SField.serializable):
+ serializable = reader.ReadAsBoolean();
+ break;
+
+ case nameof(SField.isStatic):
+ isStatic = reader.ReadAsBoolean();
+ break;
+
+ default:
+ reader.Skip(); // Skip unknown properties
+ break;
+ }
+ }
+
+ return new SField(declaringType: declaringType, fieldName: fieldName, assemblyName: assemblyName, metadataToken ?? 0, isStatic ?? false, serializable ?? false);
+ }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
var responses = (List)value;
@@ -412,6 +532,60 @@ namespace SingularityGroup.HotReload.JsonConverters {
}
writer.WriteEndArray();
}
+
+ if (responsePatch.newFields != null) {
+ writer.WritePropertyName(nameof(responsePatch.newFields));
+ writer.WriteStartArray();
+ foreach (var newField in responsePatch.newFields) {
+ WriteSField(writer, newField);
+ }
+ writer.WriteEndArray();
+ }
+
+ if (responsePatch.deletedFields != null) {
+ writer.WritePropertyName(nameof(responsePatch.deletedFields));
+ writer.WriteStartArray();
+ foreach (var deletedField in responsePatch.deletedFields) {
+ WriteSField(writer, deletedField);
+ }
+ writer.WriteEndArray();
+ }
+
+ if (responsePatch.renamedFieldsFrom != null) {
+ writer.WritePropertyName(nameof(responsePatch.renamedFieldsFrom));
+ writer.WriteStartArray();
+ foreach (var removedFieldFrom in responsePatch.renamedFieldsFrom) {
+ WriteSField(writer, removedFieldFrom);
+ }
+ writer.WriteEndArray();
+ }
+
+ if (responsePatch.renamedFieldsTo != null) {
+ writer.WritePropertyName(nameof(responsePatch.renamedFieldsTo));
+ writer.WriteStartArray();
+ foreach (var removedFieldTo in responsePatch.renamedFieldsTo) {
+ WriteSField(writer, removedFieldTo);
+ }
+ writer.WriteEndArray();
+ }
+
+ if (responsePatch.propertyAttributesFieldOriginal != null) {
+ writer.WritePropertyName(nameof(responsePatch.propertyAttributesFieldOriginal));
+ writer.WriteStartArray();
+ foreach (var removedFieldFrom in responsePatch.propertyAttributesFieldOriginal) {
+ WriteSField(writer, removedFieldFrom);
+ }
+ writer.WriteEndArray();
+ }
+
+ if (responsePatch.propertyAttributesFieldUpdated != null) {
+ writer.WritePropertyName(nameof(responsePatch.propertyAttributesFieldUpdated));
+ writer.WriteStartArray();
+ foreach (var removedFieldTo in responsePatch.propertyAttributesFieldUpdated) {
+ WriteSField(writer, removedFieldTo);
+ }
+ writer.WriteEndArray();
+ }
writer.WriteEndObject();
}
@@ -436,6 +610,51 @@ namespace SingularityGroup.HotReload.JsonConverters {
writer.WriteEndArray();
}
+ if (response.alteredFields != null) {
+ writer.WritePropertyName(nameof(response.alteredFields));
+ writer.WriteStartArray();
+ foreach (var alteredField in response.alteredFields) {
+ WriteSField(writer, alteredField);
+ }
+ writer.WriteEndArray();
+ }
+
+ if (response.addedFieldInitializerFields != null) {
+ writer.WritePropertyName(nameof(response.addedFieldInitializerFields));
+ writer.WriteStartArray();
+ foreach (var addedFieldInitializerField in response.addedFieldInitializerFields) {
+ WriteSField(writer, addedFieldInitializerField);
+ }
+ writer.WriteEndArray();
+ }
+
+ if (response.addedFieldInitializerInitializers != null) {
+ writer.WritePropertyName(nameof(response.addedFieldInitializerInitializers));
+ writer.WriteStartArray();
+ foreach (var addedFieldInitializerInitializer in response.addedFieldInitializerInitializers) {
+ WriteSMethod(writer, addedFieldInitializerInitializer);
+ }
+ writer.WriteEndArray();
+ }
+
+ if (response.removedFieldInitializers != null) {
+ writer.WritePropertyName(nameof(response.removedFieldInitializers));
+ writer.WriteStartArray();
+ foreach (var removedFieldInitializer in response.removedFieldInitializers) {
+ WriteSField(writer, removedFieldInitializer);
+ }
+ writer.WriteEndArray();
+ }
+
+ if (response.newFieldDefinitions != null) {
+ writer.WritePropertyName(nameof(response.newFieldDefinitions));
+ writer.WriteStartArray();
+ foreach (var newFieldDefinition in response.newFieldDefinitions) {
+ WriteSField(writer, newFieldDefinition);
+ }
+ writer.WriteEndArray();
+ }
+
writer.WriteEndObject();
}
writer.WriteEndArray();
@@ -450,51 +669,48 @@ namespace SingularityGroup.HotReload.JsonConverters {
writer.WriteValue(method.displayName);
writer.WritePropertyName(nameof(method.metadataToken));
writer.WriteValue(method.metadataToken);
-
- if (method.genericTypeArguments != null) {
- writer.WritePropertyName(nameof(method.genericTypeArguments));
- writer.WriteStartArray();
- foreach (var genericTypeArgument in method.genericTypeArguments) {
- WriteSType(writer, genericTypeArgument);
- }
- writer.WriteEndArray();
- }
-
- if (method.genericArguments != null) {
- writer.WritePropertyName(nameof(method.genericArguments));
- writer.WriteStartArray();
- foreach (var genericArgument in method.genericArguments) {
- WriteSType(writer, genericArgument);
- }
- writer.WriteEndArray();
- }
-
writer.WritePropertyName(nameof(method.simpleName));
writer.WriteValue(method.simpleName);
writer.WriteEndObject();
}
+
+ void WriteSField(JsonWriter writer, SField field) {
+ writer.WriteStartObject();
+
+ writer.WritePropertyName(nameof(field.declaringType));
+ writer.WriteSType(field.declaringType);
+ writer.WritePropertyName(nameof(field.fieldName));
+ writer.WriteValue(field.fieldName);
+ writer.WritePropertyName(nameof(field.assemblyName));
+ writer.WriteValue(field.assemblyName);
+ writer.WritePropertyName(nameof(field.metadataToken));
+ writer.WriteValue(field.metadataToken);
+ writer.WritePropertyName(nameof(field.serializable));
+ writer.WriteValue(field.serializable);
+
+ writer.WriteEndObject();
+ }
- void WriteSType(JsonWriter writer, SType type) {
+ }
+ internal static class MethodPatchResponsesConverterExtensions {
+ public static void WriteSType(this JsonWriter writer, SType type) {
+ if (type == null) {
+ writer.WriteNull();
+ return;
+ }
writer.WriteStartObject();
writer.WritePropertyName(nameof(type.assemblyName));
writer.WriteValue(type.assemblyName);
writer.WritePropertyName(nameof(type.typeName));
writer.WriteValue(type.typeName);
-
- // always writing generic arguments will cause recursion issues
- if (type.genericArguments?.Length > 0) {
- writer.WritePropertyName(nameof(type.genericArguments));
- writer.WriteStartArray();
- foreach (var genericArgument in type.genericArguments) {
- WriteSType(writer, genericArgument);
- }
- writer.WriteEndArray();
- }
+ writer.WritePropertyName(nameof(type.metadataToken));
+ writer.WriteValue(type.metadataToken);
writer.WriteEndObject();
}
}
+
}
#endif
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/MethodUtils.cs b/Packages/com.singularitygroup.hotreload/Runtime/MethodUtils.cs
index 14e33d7..4989850 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/MethodUtils.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/MethodUtils.cs
@@ -14,8 +14,21 @@ namespace SingularityGroup.HotReload {
ptr->monoMethodFlags |= Interop.MonoMethodFlags.skip_visibility;
}
}
+
+ public static unsafe bool IsMethodInlined(MethodBase method) {
+ if(IntPtr.Size == sizeof(long)) {
+ var ptr = (Interop.MonoMethod64*)method.MethodHandle.Value.ToPointer();
+ return (ptr -> monoMethodFlags & Interop.MonoMethodFlags.inline_info) == Interop.MonoMethodFlags.inline_info;
+ } else {
+ var ptr = (Interop.MonoMethod32*)method.MethodHandle.Value.ToPointer();
+ return (ptr -> monoMethodFlags & Interop.MonoMethodFlags.inline_info) == Interop.MonoMethodFlags.inline_info;
+ }
+ }
#else
public static void DisableVisibilityChecks(MethodBase method) { }
+ public static bool IsMethodInlined(MethodBase method) {
+ return false;
+ }
#endif
}
}
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/Prompts.cs b/Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/Prompts.cs
index ea9a498..05c3572 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/Prompts.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/Prompts.cs
@@ -4,6 +4,9 @@ using System.Collections;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.EventSystems;
+#if ENABLE_INPUT_SYSTEM
+using UnityEngine.InputSystem;
+#endif
namespace SingularityGroup.HotReload {
internal class Prompts : MonoBehaviour {
@@ -105,10 +108,18 @@ namespace SingularityGroup.HotReload {
private void Update() {
if (!userTriedToInteract) {
// when user interacts with the screen, make sure overlay can handle taps
+#if ENABLE_INPUT_SYSTEM
+ if ((Touchscreen.current != null && Touchscreen.current.touches.Count > 0) ||
+ (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)) {
+ userTriedToInteract = true;
+ DoEnsureEventSystem();
+ }
+#else
if (Input.touchCount > 0 || Input.GetMouseButtonDown(0)) {
userTriedToInteract = true;
DoEnsureEventSystem();
}
+#endif
}
}
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/PackageConst.cs b/Packages/com.singularitygroup.hotreload/Runtime/PackageConst.cs
index 94ad9df..300261e 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/PackageConst.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/PackageConst.cs
@@ -8,10 +8,10 @@ namespace SingularityGroup.HotReload {
public static bool IsAssetStoreBuild => true;
- public const string Version = "1.12.14";
+ public const string Version = "1.13.7";
// Never higher than Version
// Used for the download
- public const string ServerVersion = "1.12.12";
+ public const string ServerVersion = "1.13.7";
public const string PackageName = "com.singularitygroup.hotreload";
public const string LibraryCachePath = "Library/" + PackageName;
public const string ConfigFileName = "hot-reload-config.json";
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/PlayerCodePatcher.cs b/Packages/com.singularitygroup.hotreload/Runtime/PlayerCodePatcher.cs
index d12396a..dcac22f 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/PlayerCodePatcher.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/PlayerCodePatcher.cs
@@ -100,7 +100,6 @@ namespace SingularityGroup.HotReload {
static void HandleResponseReceived(MethodPatchResponse response) {
Log.Debug("PollMethodPatches handling MethodPatchResponse id:{0} response.patches.Length:{1} response.failures.Length:{2}",
response.id, response.patches.Length, response.failures.Length);
- // TODO handle new response data (removed methods etc.)
if(response.patches.Length > 0) {
CodePatcher.I.RegisterPatches(response, persist: true);
}
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/PlayerEntrypoint.cs b/Packages/com.singularitygroup.hotreload/Runtime/PlayerEntrypoint.cs
index 80c7587..ce1c848 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/PlayerEntrypoint.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/PlayerEntrypoint.cs
@@ -66,6 +66,8 @@ namespace SingularityGroup.HotReload {
return;
}
+ CodePatcher.I.debuggerCompatibilityEnabled = true;
+
try {
var customIp = PlayerPrefs.GetString("HotReloadRuntime.CustomIP", "");
if (!string.IsNullOrEmpty(customIp)) {
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/RequestHelper.cs b/Packages/com.singularitygroup.hotreload/Runtime/RequestHelper.cs
index c513615..ab5cb0e 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/RequestHelper.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/RequestHelper.cs
@@ -345,14 +345,41 @@ namespace SingularityGroup.HotReload {
}
}
+ public static bool IsReleaseMode() {
+# if (UNITY_EDITOR && UNITY_2022_1_OR_NEWER)
+ return UnityEditor.Compilation.CompilationPipeline.codeOptimization == UnityEditor.Compilation.CodeOptimization.Release;
+# elif (UNITY_EDITOR)
+ return false;
+# elif (DEBUG)
+ return false;
+# else
+ return true;
+#endif
+ }
+
public static Task RequestClearPatches() {
- var body = SerializeRequestBody(new CompileRequest(serverInfo.rootPath));
+ var body = SerializeRequestBody(new CompileRequest(serverInfo.rootPath, IsReleaseMode()));
return PostJson(url + "/clearpatches", body, 10);
}
- public static Task RequestCompile() {
- var body = SerializeRequestBody(new CompileRequest(serverInfo.rootPath));
- return PostJson(url + "/compile", body, 10);
+ public static async Task RequestCompile(Action onResponseReceived) {
+ var body = SerializeRequestBody(new CompileRequest(serverInfo.rootPath, IsReleaseMode()));
+ var result = await PostJson(url + "/compile", body, 10);
+ if (result.statusCode == HttpStatusCode.OK && !string.IsNullOrEmpty(result.responseText)) {
+ var responses = JsonConvert.DeserializeObject>(result.responseText);
+ if (responses == null) {
+ return;
+ }
+ await ThreadUtility.SwitchToMainThread();
+ foreach (var response in responses) {
+ // Avoid importing assets twice
+ if (responses.Contains(response + ".meta")) {
+ Log.Debug($"Ignoring asset change inside Unity: {response}");
+ continue;
+ }
+ onResponseReceived(response);
+ }
+ }
}
internal static async Task> FetchChangelog(int timeoutSeconds = 20) {
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/SingularityGroup.HotReload.Runtime.asmdef b/Packages/com.singularitygroup.hotreload/Runtime/SingularityGroup.HotReload.Runtime.asmdef
index 895e5c3..e4f6197 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/SingularityGroup.HotReload.Runtime.asmdef
+++ b/Packages/com.singularitygroup.hotreload/Runtime/SingularityGroup.HotReload.Runtime.asmdef
@@ -6,7 +6,8 @@
"GUID:2bafac87e7f4b9b418d9448d219b01ab",
"GUID:46c537318f0d530469a1df1fafe86c9c",
"GUID:af67dfb2ec5c9c740be50ae4470ed85f",
- "SingularityGroup.HotReload.Runtime.Public"
+ "SingularityGroup.HotReload.Runtime.Public",
+ "Unity.InputSystem"
],
"includePlatforms": [],
"excludePlatforms": [],
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/SymbolResolver.cs b/Packages/com.singularitygroup.hotreload/Runtime/SymbolResolver.cs
index 885b826..054e848 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/SymbolResolver.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/SymbolResolver.cs
@@ -23,25 +23,50 @@ namespace SingularityGroup.HotReload {
assemblies.Add(asm);
}
- public Type Resolve(SType t) {
- List assemblies;
- if (assembliesByName.TryGetValue(t.assemblyName, out assemblies)) {
-
- Type type;
- foreach (var assembly in assemblies) {
- if ((type = assembly.GetType(t.typeName)) != null) {
- if(t.typeName == "System.Array" && t.genericArguments.Length > 0) {
- var elementType = Resolve(t.genericArguments[0]);
- return elementType.Assembly.GetType(t.genericArguments[0].typeName + "[]");
+ public Type Resolve(SType t) {
+ var assmeblies = Resolve(t.assemblyName);
+ Type result = null;
+ Exception lastException = null;
+ for (var i = 0; i < assmeblies.Count; i++) {
+ try {
+ result = assmeblies[i].GetLoadedModules()[0].ResolveType(t.metadataToken);
+ if (t.isGenericParameter) {
+ if (!result.IsGenericTypeDefinition) {
+ throw new SymbolResolvingFailedException(t, new ApplicationException("Generic parameter did not resolve to generic type definition"));
}
- if(t.genericArguments.Length > 0) {
- type = type.MakeGenericType(ResolveTypes(t.genericArguments));
+ var genericParameters = result.GetGenericArguments();
+ if (t.genericParameterPosition >= genericParameters.Length) {
+ throw new SymbolResolvingFailedException(t, new ApplicationException("Generic parameter did not exist on the generic type definition"));
}
- return type;
+ result = genericParameters[t.genericParameterPosition];
}
+ break;
+ } catch(Exception ex) {
+ lastException = ex;
}
}
- throw new SymbolResolvingFailedException(t);
+ if(result == null) {
+ throw new SymbolResolvingFailedException(t, lastException);
+ }
+ return result;
+ }
+
+ public FieldInfo Resolve(SField t) {
+ var assmeblies = Resolve(t.assemblyName);
+ FieldInfo result = null;
+ Exception lastException = null;
+ for (var i = 0; i < assmeblies.Count; i++) {
+ try {
+ result = assmeblies[i].GetLoadedModules()[0].ResolveField(t.metadataToken);
+ break;
+ } catch(Exception ex) {
+ lastException = ex;
+ }
+ }
+ if(result == null) {
+ throw new SymbolResolvingFailedException(t, lastException);
+ }
+ return result;
}
public IReadOnlyList Resolve(string assembly) {
@@ -54,13 +79,11 @@ namespace SingularityGroup.HotReload {
public MethodBase Resolve(SMethod m) {
var assmeblies = Resolve(m.assemblyName);
- var genericTypeArgs = ResolveTypes(m.genericTypeArguments);
- var genericMethodArgs = ResolveTypes(m.genericArguments);
MethodBase result = null;
Exception lastException = null;
for (var i = 0; i < assmeblies.Count; i++) {
try {
- result = assmeblies[i].GetLoadedModules()[0].ResolveMethod(m.metadataToken, genericTypeArgs, genericMethodArgs);
+ result = assmeblies[i].GetLoadedModules()[0].ResolveMethod(m.metadataToken);
break;
} catch(Exception ex) {
lastException = ex;
@@ -71,20 +94,6 @@ namespace SingularityGroup.HotReload {
}
return result;
}
-
- Type[] ResolveTypes(SType[] sTypes) {
- if(sTypes == null) {
- return null;
- }
- if(sTypes.Length == 0) {
- return Array.Empty();
- }
- var result = new Type[sTypes.Length];
- for (int i = 0; i < sTypes.Length; i++) {
- result[i] = Resolve(sTypes[i]);
- }
- return result;
- }
}
}
#endif
diff --git a/Packages/com.singularitygroup.hotreload/Runtime/SymbolResolvingFailedException.cs b/Packages/com.singularitygroup.hotreload/Runtime/SymbolResolvingFailedException.cs
index 3dabcb4..b6e873f 100644
--- a/Packages/com.singularitygroup.hotreload/Runtime/SymbolResolvingFailedException.cs
+++ b/Packages/com.singularitygroup.hotreload/Runtime/SymbolResolvingFailedException.cs
@@ -7,8 +7,11 @@ namespace SingularityGroup.HotReload {
public SymbolResolvingFailedException(SMethod m, Exception inner)
: base($"Unable to resolve method {m.displayName} in assembly {m.assemblyName}", inner) { }
- public SymbolResolvingFailedException(SType t)
- : base($"Unable to resolve type with name: {t.typeName} in assembly {t.assemblyName}") { }
+ public SymbolResolvingFailedException(SType t, Exception inner)
+ : base($"Unable to resolve type with name: {t.typeName} in assembly {t.assemblyName}", inner) { }
+
+ public SymbolResolvingFailedException(SField t, Exception inner)
+ : base($"Unable to resolve field with name: {t.fieldName} in assembly {t.assemblyName}", inner) { }
}
}
#endif
diff --git a/Packages/com.singularitygroup.hotreload/Server/linux-x64/hotreload-start-script.sh b/Packages/com.singularitygroup.hotreload/Server/linux-x64/hotreload-start-script.sh
index d218d47..46dc4d2 100644
--- a/Packages/com.singularitygroup.hotreload/Server/linux-x64/hotreload-start-script.sh
+++ b/Packages/com.singularitygroup.hotreload/Server/linux-x64/hotreload-start-script.sh
@@ -51,9 +51,6 @@ fi
CLIARGUMENTS=$(cat "$CLIARGUMENTS_FILE")
rm "$CLIARGUMENTS_FILE"
-# Needs be removed if you have multiple unities
-pgrep CodePatcherCLI | xargs -I {} kill {}
-
rm -rf "$METHODPATCHDIR"
SERVER="$EXECUTABLETARGETDIR/CodePatcherCLI"
diff --git a/Packages/com.singularitygroup.hotreload/package.json b/Packages/com.singularitygroup.hotreload/package.json
index 86496b2..473842c 100644
--- a/Packages/com.singularitygroup.hotreload/package.json
+++ b/Packages/com.singularitygroup.hotreload/package.json
@@ -1,7 +1,7 @@
{
"displayName": "Hot Reload",
"name": "com.singularitygroup.hotreload",
- "unity": "2018.4",
+ "unity": "2019.4",
"description": "Change code and get immediate updates in the Unity Editor or on-device.",
"category": "Engineering",
"keywords": [
@@ -12,7 +12,7 @@
"sg",
"singularity"
],
- "version": "1.12.14",
+ "version": "1.13.7",
"dependencies": {
"com.unity.ugui": "1.0.0",
"com.unity.modules.unitywebrequest": "1.0.0"
diff --git a/ProjectSettings/Packages/com.unity.probuilder/Settings.json b/ProjectSettings/Packages/com.unity.probuilder/Settings.json
index 75fdb09..a2a08b5 100644
--- a/ProjectSettings/Packages/com.unity.probuilder/Settings.json
+++ b/ProjectSettings/Packages/com.unity.probuilder/Settings.json
@@ -175,6 +175,11 @@
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"key": "WeldVertices.weldDistance",
"value": "{\"m_Value\":0.009999999776482582}"
+ },
+ {
+ "type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
+ "key": "uv.uvEditorGridSnapIncrement",
+ "value": "{\"m_Value\":0.125}"
}
]
}