updated: hot reload to 1.13.7

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -98,18 +98,12 @@ namespace SingularityGroup.HotReload.Editor {
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
using (new EditorGUILayout.VerticalScope()) {
HotReloadPrefs.ShowConfiguration = EditorGUILayout.Foldout(HotReloadPrefs.ShowConfiguration, "Configuration", true, HotReloadWindowStyles.FoldoutStyle);
HotReloadPrefs.ShowConfiguration = EditorGUILayout.Foldout(HotReloadPrefs.ShowConfiguration, "Settings", true, HotReloadWindowStyles.FoldoutStyle);
if (HotReloadPrefs.ShowConfiguration) {
EditorGUILayout.Space();
RenderUnityAutoRefresh();
RenderAssetRefresh();
if (HotReloadPrefs.AllAssetChanges) {
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
RenderIncludeShaderChanges();
}
EditorGUILayout.Space();
}
// main section
RenderUnityAutoRefresh();
using (new EditorGUI.DisabledScope(!EditorCodePatcher.autoRecompileUnsupportedChangesSupported)) {
RenderAutoRecompileUnsupportedChanges();
if (HotReloadPrefs.AutoRecompileUnsupportedChanges && EditorCodePatcher.autoRecompileUnsupportedChangesSupported) {
@@ -118,22 +112,49 @@ namespace SingularityGroup.HotReload.Editor {
RenderAutoRecompileUnsupportedChangesOnExitPlayMode();
RenderAutoRecompileUnsupportedChangesInPlayMode();
RenderAutoRecompilePartiallyUnsupportedChanges();
RenderDisplayNewMonobehaviourMethodsAsPartiallySupported();
}
}
EditorGUILayout.Space();
}
RenderConsoleWindow();
RenderAutostart();
RenderAssetRefresh();
if (HotReloadPrefs.AllAssetChanges) {
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
RenderIncludeShaderChanges();
}
EditorGUILayout.Space();
}
RenderDebuggerCompatibility();
// // fields
// RenderShowFeatures();
// using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
// RenderShowApplyfieldInitializerEditsToExistingClassInstances();
//
// EditorGUILayout.Space();
// }
// visual feedback
if (EditorWindowHelper.supportsNotifications) {
RenderShowNotifications();
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
RenderShowPatchingNotifications();
RenderShowCompilingUnsupportedNotifications();
}
EditorGUILayout.Space();
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
RenderShowPatchingNotifications();
RenderShowCompilingUnsupportedNotifications();
}
EditorGUILayout.Space();
}
// misc
RenderMiscHeader();
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
RenderAutostart();
RenderConsoleWindow();
EditorGUILayout.Space();
}
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope()) {
GUILayout.FlexibleSpace();
@@ -161,24 +182,38 @@ namespace SingularityGroup.HotReload.Editor {
}
}
}
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
using (new EditorGUILayout.VerticalScope()) {
HotReloadPrefs.ShowAdvanced = EditorGUILayout.Foldout(HotReloadPrefs.ShowAdvanced, "Advanced", true, HotReloadWindowStyles.FoldoutStyle);
if (HotReloadPrefs.ShowAdvanced) {
EditorGUILayout.Space();
DeactivateHotReload();
DisableDetailedErrorReporting();
}
}
}
}
}
}
}
void RenderUnityAutoRefresh() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Allow to manage Unity's Auto Compile settings (recommended)"), HotReloadPrefs.AllowDisableUnityAutoRefresh);
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Manage Unity auto-refresh (recommended)"), HotReloadPrefs.AllowDisableUnityAutoRefresh);
if (newSettings != HotReloadPrefs.AllowDisableUnityAutoRefresh) {
HotReloadPrefs.AllowDisableUnityAutoRefresh = newSettings;
}
string toggleDescription;
if (HotReloadPrefs.AllowDisableUnityAutoRefresh) {
toggleDescription = "Hot Reload will manage Unity's Auto Refresh and Script Compilation settings when it's running. Previous settings will be restored when Hot Reload is stopped.";
toggleDescription = "To avoid unnecessary recompiling, Hot Reload will automatically change Unity's Auto Refresh and Script Compilation settings. Previous settings will be restored when Hot Reload is stopped";
} else {
toggleDescription = "Enable to allow Hot Reload to manage Unity's Auto Refresh and Script Compilation settings when it's running. If enabled, previous settings will be restored when Hot Reload is stopped.";
toggleDescription = "Enabled this setting to auto-manage Unity's Auto Refresh and Script Compilation settings. This reduces unncessary recompiling";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(3f);
EditorGUILayout.Space(6f);
}
void RenderAssetRefresh() {
@@ -190,7 +225,7 @@ namespace SingularityGroup.HotReload.Editor {
var restartServer = EditorUtility.DisplayDialog("Hot Reload",
$"When changing 'Asset refresh', the Hot Reload server must be restarted for this to take effect." +
"\nDo you want to restart it now?",
"Restart server", "Don't restart");
"Restart Hot Reload", "Don't restart");
if (restartServer) {
EditorCodePatcher.RestartCodePatcher().Forget();
}
@@ -198,13 +233,30 @@ namespace SingularityGroup.HotReload.Editor {
}
string toggleDescription;
if (HotReloadPrefs.AllAssetChanges) {
toggleDescription = "Hot Reload will refresh changed assets in the project.";
toggleDescription = "Hot Reload will refresh changed assets such as sprites, prefabs, etc";
} else {
toggleDescription = "Enable to allow Hot Reload to refresh changed assets in the project. All asset types are supported including sprites, prefabs, shaders etc.";
toggleDescription = "Enable to allow Hot Reload to refresh changed assets in the project. All asset types are supported including sprites, prefabs, shaders etc";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(3f);
EditorGUILayout.Space(6f);
}
void RenderDebuggerCompatibility() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Auto-disable Hot Reload while a debugger is attached (recommended)"), HotReloadPrefs.AutoDisableHotReloadWithDebugger);
if (newSettings != HotReloadPrefs.AutoDisableHotReloadWithDebugger) {
HotReloadPrefs.AutoDisableHotReloadWithDebugger = newSettings;
CodePatcher.I.debuggerCompatibilityEnabled = !HotReloadPrefs.AutoDisableHotReloadWithDebugger;
}
string toggleDescription;
if (HotReloadPrefs.AutoDisableHotReloadWithDebugger) {
toggleDescription = "Hot Reload automatically disables itself while a debugger is attached, as it can otherwise interfere with certain debugger features. Please read the documentation if you consider disabling this setting.";
} else {
toggleDescription = "When a debugger is attached, Hot Reload will be active, but certain debugger features might not work as expected. Please read our documentation to learn about the limitations.";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
void RenderIncludeShaderChanges() {
@@ -245,7 +297,69 @@ 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() {
@@ -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() {
@@ -290,6 +414,36 @@ namespace SingularityGroup.HotReload.Editor {
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);
string toggleDescription;
@@ -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;

View File

@@ -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

View File

@@ -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<MethodPatch> patchedMethods = new List<MethodPatch>();
public List<SField> addedFields = new List<SField>();
public readonly List<SMethod> patchedSMethods = new List<SMethod>();
public bool inspectorModified;
public readonly List<Tuple<SMethod, string>> patchFailures = new List<Tuple<SMethod, string>>();
public readonly List<string> patchExceptions = new List<string>();
}
class FieldHandler {
public readonly Action<Type, FieldInfo> storeField;
public readonly Action<Type, FieldInfo, FieldInfo> registerInspectorFieldAttributes;
public readonly Func<Type, string, bool> hideField;
public FieldHandler(Action<Type, FieldInfo> storeField, Func<Type, string, bool> hideField, Action<Type, FieldInfo, FieldInfo> 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<MethodPatchResponse>();
@@ -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);
} catch(Exception ex) {
Log.Warning("Failed to apply patch with id: {0}\n{1}", patch.patchId, ex);
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("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<SField>();
var propertyAttributesFieldUpdated = patch.propertyAttributesFieldUpdated ?? Array.Empty<SField>();
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<MethodBase, MethodBase> previousPatchMethods = new Dictionary<MethodBase, MethodBase>();
public IEnumerable<MethodBase> OriginalPatchMethods => previousPatchMethods.Keys;
List<MethodBase> newMethods = new List<MethodBase>();
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() {

View File

@@ -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

View File

@@ -15,10 +15,13 @@ namespace SingularityGroup.HotReload.Demo {
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);
@@ -34,16 +37,16 @@ namespace SingularityGroup.HotReload.Demo {
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) {
@@ -51,7 +54,7 @@ namespace SingularityGroup.HotReload.Demo {
// }
}
// 2. Editing lambda methods
// 3. Editing lambda methods
static Func<int, int> addFunction = x => {
var result = x + 10;
Debug.Log("Add: " + result);
@@ -61,7 +64,7 @@ namespace SingularityGroup.HotReload.Demo {
return result;
};
// 3. Editing async/await methods
// 4. Editing async/await methods
async Task AsyncMethod() {
// await Task.Delay(500);
// Debug.Log("AsyncMethod");
@@ -70,7 +73,7 @@ namespace SingularityGroup.HotReload.Demo {
await Task.CompletedTask;
}
// 4. Editing properties (get/set)
// 5. Editing properties (get/set)
public static string SomeString {
// edit the get method
get {
@@ -79,7 +82,7 @@ namespace SingularityGroup.HotReload.Demo {
}
}
// 5. Editing indexers (square bracket access such as dictionaries)
// 6. Editing indexers (square bracket access such as dictionaries)
class CustomDictionary : Dictionary<string, int> {
public new int this[string key] {
get {
@@ -103,7 +106,7 @@ namespace SingularityGroup.HotReload.Demo {
{ "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; }
@@ -126,24 +129,43 @@ 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<T> {
// // 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);
@@ -154,23 +176,39 @@ namespace SingularityGroup.HotReload.Demo {
// Debug.Log(hexRepresentation);
// Debug.Log(Encoding.UTF8.GetString(byteEmail));
// // 7. Editing lambda methods with closures
// // 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();
// var newClass = new GenericClass<int>();
// // 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");
// }

View File

@@ -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<T>() { } <-> 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<ParameterInfo>(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<ParameterInfo> x, ArraySegment<ParameterInfo> y) {
static string CompareParameters(ArraySegment<ParameterInfo> x, ArraySegment<ParameterInfo> 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");
}
}
}

View File

@@ -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) {
@@ -57,6 +62,26 @@ namespace SingularityGroup.HotReload.JsonConverters {
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
break;
@@ -68,8 +93,14 @@ namespace SingularityGroup.HotReload.JsonConverters {
patches ?? Array.Empty<CodePatch>(),
failures ?? Array.Empty<string>(),
removedMethod ?? Array.Empty<SMethod>(),
// Note: doesn't have to be persisted here
Array.Empty<PartiallySupportedChange>()
alteredFields ?? Array.Empty<SField>(),
// Note: suggestions don't have to be persisted here
Array.Empty<PartiallySupportedChange>(),
Array.Empty<HotReloadSuggestionKind>(),
addedFieldInitializerFields ?? Array.Empty<SField>(),
addedFieldInitializerInitializers ?? Array.Empty<SMethod>(),
removedFieldInitializers ?? Array.Empty<SField>(),
newFieldDefinitions ?? Array.Empty<SField>()
);
}
@@ -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) {
@@ -134,6 +171,29 @@ namespace SingularityGroup.HotReload.JsonConverters {
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
break;
@@ -148,7 +208,13 @@ namespace SingularityGroup.HotReload.JsonConverters {
modifiedMethods: modifiedMethods ?? Array.Empty<SMethod>(),
patchMethods: patchMethods ?? Array.Empty<SMethod>(),
newMethods: newMethods ?? Array.Empty<SMethod>(),
unityJobs: unityJobs ?? Array.Empty<SUnityJob>()
unityJobs: unityJobs ?? Array.Empty<SUnityJob>(),
newFields: newFields ?? Array.Empty<SField>(),
deletedFields: deletedFields ?? Array.Empty<SField>(),
renamedFieldsFrom: renamedFieldsFrom ?? Array.Empty<SField>(),
renamedFieldsTo: renamedFieldsTo ?? Array.Empty<SField>(),
propertyAttributesFieldOriginal: propertyAttributesFieldOriginal ?? Array.Empty<SField>(),
propertyAttributesFieldUpdated: propertyAttributesFieldUpdated ?? Array.Empty<SField>()
));
}
@@ -211,12 +277,22 @@ namespace SingularityGroup.HotReload.JsonConverters {
return array.ToArray();
}
private SField[] ReadSFields(JsonReader reader) {
var array = new List<SField>();
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<SType>(),
genericArguments ?? Array.Empty<SType>(),
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<SType>()
metadataToken ?? 0
);
}
@@ -341,6 +410,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<MethodPatchResponse>)value;
if (responses == null) {
@@ -413,6 +533,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();
}
writer.WriteEndArray();
@@ -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 WriteSType(JsonWriter writer, SType type) {
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();
}
}
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

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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";

View File

@@ -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);
}

View File

@@ -66,6 +66,8 @@ namespace SingularityGroup.HotReload {
return;
}
CodePatcher.I.debuggerCompatibilityEnabled = true;
try {
var customIp = PlayerPrefs.GetString("HotReloadRuntime.CustomIP", "");
if (!string.IsNullOrEmpty(customIp)) {

View File

@@ -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<string> 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<List<string>>(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<List<ChangelogVersion>> FetchChangelog(int timeoutSeconds = 20) {

View File

@@ -6,7 +6,8 @@
"GUID:2bafac87e7f4b9b418d9448d219b01ab",
"GUID:46c537318f0d530469a1df1fafe86c9c",
"GUID:af67dfb2ec5c9c740be50ae4470ed85f",
"SingularityGroup.HotReload.Runtime.Public"
"SingularityGroup.HotReload.Runtime.Public",
"Unity.InputSystem"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -23,25 +23,50 @@ namespace SingularityGroup.HotReload {
assemblies.Add(asm);
}
public Type Resolve(SType t) {
List<Assembly> 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<Assembly> 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<Type>();
}
var result = new Type[sTypes.Length];
for (int i = 0; i < sTypes.Length; i++) {
result[i] = Resolve(sTypes[i]);
}
return result;
}
}
}
#endif

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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}"
}
]
}