HotReloadTimelineHelper.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text.RegularExpressions;
  6. using JetBrains.Annotations;
  7. using SingularityGroup.HotReload.DTO;
  8. using SingularityGroup.HotReload.Newtonsoft.Json;
  9. using UnityEditor;
  10. using UnityEngine;
  11. namespace SingularityGroup.HotReload.Editor {
  12. internal enum TimelineType {
  13. Suggestions,
  14. Timeline,
  15. }
  16. internal enum AlertType {
  17. Suggestion,
  18. UnsupportedChange,
  19. CompileError,
  20. PartiallySupportedChange,
  21. AppliedChange,
  22. UndetectedChange,
  23. }
  24. internal enum AlertEntryType {
  25. Error,
  26. Failure,
  27. InlinedMethod,
  28. PatchApplied,
  29. PartiallySupportedChange,
  30. UndetectedChange,
  31. }
  32. internal enum EntryType {
  33. Parent,
  34. Child,
  35. Standalone,
  36. Foldout,
  37. }
  38. internal class PersistedAlertData {
  39. public readonly AlertData[] alertDatas;
  40. public PersistedAlertData(AlertData[] alertDatas) {
  41. this.alertDatas = alertDatas;
  42. }
  43. }
  44. internal class AlertData {
  45. public readonly AlertEntryType alertEntryType;
  46. public readonly string errorString;
  47. public readonly string methodName;
  48. public readonly string methodSimpleName;
  49. public readonly PartiallySupportedChange partiallySupportedChange;
  50. public readonly EntryType entryType;
  51. public readonly bool detiled;
  52. public readonly DateTime createdAt;
  53. public readonly string[] patchedMembersDisplayNames;
  54. 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) {
  55. this.alertEntryType = alertEntryType;
  56. this.createdAt = createdAt;
  57. this.detiled = detiled;
  58. this.entryType = entryType;
  59. this.errorString = errorString;
  60. this.methodName = methodName;
  61. this.methodSimpleName = methodSimpleName;
  62. this.partiallySupportedChange = partiallySupportedChange;
  63. this.patchedMembersDisplayNames = patchedMembersDisplayNames;
  64. }
  65. }
  66. internal class AlertEntry {
  67. internal readonly AlertType alertType;
  68. internal readonly string title;
  69. internal readonly DateTime timestamp;
  70. internal readonly string description;
  71. [CanBeNull] internal readonly Action actionData;
  72. internal readonly AlertType iconType;
  73. internal readonly string shortDescription;
  74. internal readonly EntryType entryType;
  75. internal readonly AlertData alertData;
  76. internal readonly bool hasExitButton;
  77. internal AlertEntry(AlertType alertType, string title, string description, DateTime timestamp, string shortDescription = null, Action actionData = null, AlertType? iconType = null, EntryType entryType = EntryType.Standalone, AlertData alertData = default(AlertData), bool hasExitButton = true) {
  78. this.alertType = alertType;
  79. this.title = title;
  80. this.description = description;
  81. this.shortDescription = shortDescription;
  82. this.actionData = actionData;
  83. this.iconType = iconType ?? alertType;
  84. this.timestamp = timestamp;
  85. this.entryType = entryType;
  86. this.alertData = alertData;
  87. this.hasExitButton = hasExitButton;
  88. }
  89. }
  90. internal static class HotReloadTimelineHelper {
  91. internal const int maxVisibleEntries = 40;
  92. private static List<AlertEntry> eventsTimeline = new List<AlertEntry>();
  93. internal static List<AlertEntry> EventsTimeline => eventsTimeline;
  94. static readonly string filePath = Path.Combine(PackageConst.LibraryCachePath, "eventEntries.json");
  95. public static void InitPersistedEvents() {
  96. if (!File.Exists(filePath)) {
  97. return;
  98. }
  99. var redDotShown = HotReloadState.ShowingRedDot;
  100. try {
  101. var persistedAlertData = JsonConvert.DeserializeObject<PersistedAlertData>(File.ReadAllText(filePath));
  102. eventsTimeline = new List<AlertEntry>(persistedAlertData.alertDatas.Length);
  103. for (int i = persistedAlertData.alertDatas.Length - 1; i >= 0; i--) {
  104. AlertData alertData = persistedAlertData.alertDatas[i];
  105. switch (alertData.alertEntryType) {
  106. case AlertEntryType.Error:
  107. CreateErrorEventEntry(errorString: alertData.errorString, entryType: alertData.entryType, createdAt: alertData.createdAt);
  108. break;
  109. #if UNITY_2020_1_OR_NEWER
  110. case AlertEntryType.InlinedMethod:
  111. CreateInlinedMethodsEntry(alertData.patchedMembersDisplayNames, alertData.entryType, alertData.createdAt);
  112. break;
  113. #endif
  114. case AlertEntryType.Failure:
  115. if (alertData.entryType == EntryType.Parent) {
  116. CreateReloadFinishedWithWarningsEventEntry(createdAt: alertData.createdAt, patchedMembersDisplayNames: alertData.patchedMembersDisplayNames);
  117. } else {
  118. CreatePatchFailureEventEntry(errorString: alertData.errorString, methodName: alertData.methodName, methodSimpleName: alertData.methodSimpleName, entryType: alertData.entryType, createdAt: alertData.createdAt);
  119. }
  120. break;
  121. case AlertEntryType.PatchApplied:
  122. CreateReloadFinishedEventEntry(
  123. createdAt: alertData.createdAt,
  124. patchedMethodsDisplayNames: alertData.patchedMembersDisplayNames
  125. );
  126. break;
  127. case AlertEntryType.PartiallySupportedChange:
  128. if (alertData.entryType == EntryType.Parent) {
  129. CreateReloadPartiallyAppliedEventEntry(createdAt: alertData.createdAt, patchedMethodsDisplayNames: alertData.patchedMembersDisplayNames);
  130. } else {
  131. CreatePartiallyAppliedEventEntry(alertData.partiallySupportedChange, entryType: alertData.entryType, detailed: alertData.detiled, createdAt: alertData.createdAt);
  132. }
  133. break;
  134. case AlertEntryType.UndetectedChange:
  135. CreateReloadUndetectedChangeEventEntry(createdAt: alertData.createdAt);
  136. break;
  137. }
  138. }
  139. } catch (Exception e) {
  140. Log.Warning($"Failed initializing Hot Reload event entries on start: {e}");
  141. } finally {
  142. // Ensure red dot is not triggered for existing entries
  143. HotReloadState.ShowingRedDot = redDotShown;
  144. }
  145. }
  146. internal static void PersistTimeline() {
  147. var alertDatas = new AlertData[eventsTimeline.Count];
  148. for (var i = 0; i < eventsTimeline.Count; i++) {
  149. alertDatas[i] = eventsTimeline[i].alertData;
  150. }
  151. var persistedData = new PersistedAlertData(alertDatas);
  152. try {
  153. File.WriteAllText(path: filePath, contents: JsonConvert.SerializeObject(persistedData));
  154. } catch (Exception e) {
  155. Log.Warning($"Failed persisting Hot Reload event entries: {e}");
  156. }
  157. }
  158. internal static void ClearPersistance() {
  159. try {
  160. File.Delete(filePath);
  161. } catch {
  162. // ignore
  163. }
  164. eventsTimeline = new List<AlertEntry>();
  165. }
  166. internal static readonly Dictionary<AlertType, string> alertIconString = new Dictionary<AlertType, string> {
  167. { AlertType.Suggestion, "alert_info" },
  168. { AlertType.UnsupportedChange, "warning" },
  169. { AlertType.CompileError, "error" },
  170. { AlertType.PartiallySupportedChange, "infos" },
  171. { AlertType.AppliedChange, "applied_patch" },
  172. { AlertType.UndetectedChange, "undetected" },
  173. };
  174. #pragma warning disable CS0612 // obsolete
  175. public static Dictionary<PartiallySupportedChange, string> partiallySupportedChangeDescriptions = new Dictionary<PartiallySupportedChange, string> {
  176. {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)."},
  177. {PartiallySupportedChange.EditAsyncMethod, "An async method was edited. Changes to it will only be visible the next time this method is called."},
  178. {PartiallySupportedChange.AddMonobehaviourMethod, "A new method was added. It will not show up in the Inspector until the next full recompilation."},
  179. {PartiallySupportedChange.EditMonobehaviourField, "A field in a MonoBehaviour was removed or reordered. The inspector will not notice this change until the next full recompilation."},
  180. {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."},
  181. {PartiallySupportedChange.EditGenericFieldInitializer, "A field initializer inside generic class was edited. Field initializer will not have any effect until the next full recompilation."},
  182. {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."},
  183. {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."},
  184. {PartiallySupportedChange.AddMethodWithAttributes, "A method with attributes was added. Method attributes will not have any effect until the next full recompilation."},
  185. {PartiallySupportedChange.AddFieldWithAttributes, "A field with attributes was added. Field attributes will not have any effect until the next full recompilation."},
  186. {PartiallySupportedChange.GenericMethodInGenericClass, "A generic method was edited. Usages in non-generic classes applied, but usages in the generic classes are not supported."},
  187. {PartiallySupportedChange.NewCustomSerializableField, "A new custom serializable field was added. The inspector will not notice this change until the next full recompilation."},
  188. {PartiallySupportedChange.MultipleFieldsEditedInTheSameType, "Multiple fields modified in the same type during a single patch. Their values have been reset."},
  189. };
  190. #pragma warning restore CS0612
  191. internal static List<AlertEntry> Suggestions = new List<AlertEntry>();
  192. internal static int UnsupportedChangesCount => EventsTimeline.Count(alert => alert.alertType == AlertType.UnsupportedChange && alert.entryType != EntryType.Child);
  193. internal static int PartiallySupportedChangesCount => EventsTimeline.Count(alert => alert.alertType == AlertType.PartiallySupportedChange && alert.entryType != EntryType.Child);
  194. internal static int UndetectedChangesCount => EventsTimeline.Count(alert => alert.alertType == AlertType.UndetectedChange && alert.entryType != EntryType.Child);
  195. internal static int CompileErrorsCount => EventsTimeline.Count(alert => alert.alertType == AlertType.CompileError);
  196. internal static int AppliedChangesCount => EventsTimeline.Count(alert => alert.alertType == AlertType.AppliedChange);
  197. static Regex shortDescriptionRegex = new Regex(@"^(\w+)\s(\w+)(?=:)", RegexOptions.Compiled);
  198. internal static int GetRunTabTimelineEventCount() {
  199. int total = 0;
  200. if (HotReloadPrefs.RunTabUnsupportedChangesFilter) {
  201. total += UnsupportedChangesCount;
  202. }
  203. if (HotReloadPrefs.RunTabPartiallyAppliedPatchesFilter) {
  204. total += PartiallySupportedChangesCount;
  205. }
  206. if (HotReloadPrefs.RunTabUndetectedPatchesFilter) {
  207. total += UndetectedChangesCount;
  208. }
  209. if (HotReloadPrefs.RunTabCompileErrorFilter) {
  210. total += CompileErrorsCount;
  211. }
  212. if (HotReloadPrefs.RunTabAppliedPatchesFilter) {
  213. total += AppliedChangesCount;
  214. }
  215. return total;
  216. }
  217. internal static List<AlertEntry> expandedEntries = new List<AlertEntry>();
  218. internal static void RenderCompileButton() {
  219. if (GUILayout.Button("Recompile", GUILayout.Width(80))) {
  220. HotReloadRunTab.RecompileWithChecks();
  221. }
  222. }
  223. private static float maxScrollPos;
  224. internal static void RenderErrorEventActions(string description, ErrorData errorData) {
  225. int maxLen = 2400;
  226. string text = errorData.stacktrace;
  227. if (text.Length > maxLen) {
  228. text = text.Substring(0, maxLen) + "...";
  229. }
  230. GUILayout.TextArea(text, HotReloadWindowStyles.StacktraceTextAreaStyle);
  231. if (errorData.file || !errorData.stacktrace.Contains("error CS")) {
  232. GUILayout.Space(10f);
  233. }
  234. using (new EditorGUILayout.HorizontalScope()) {
  235. if (!errorData.stacktrace.Contains("error CS")) {
  236. RenderCompileButton();
  237. }
  238. // Link
  239. if (errorData.file) {
  240. GUILayout.FlexibleSpace();
  241. if (GUILayout.Button(errorData.linkString, HotReloadWindowStyles.LinkStyle)) {
  242. AssetDatabase.OpenAsset(errorData.file, Math.Max(errorData.lineNumber, 1));
  243. }
  244. }
  245. }
  246. }
  247. private static Texture2D GetFilterIcon(int count, AlertType alertType) {
  248. if (count == 0) {
  249. return GUIHelper.ConvertToGrayscale(alertIconString[alertType]);
  250. }
  251. return GUIHelper.GetLocalIcon(alertIconString[alertType]);
  252. }
  253. internal static void RenderAlertFilters() {
  254. using (new EditorGUILayout.HorizontalScope()) {
  255. var text = AppliedChangesCount > 999 ? "999+" : " " + AppliedChangesCount;
  256. HotReloadPrefs.RunTabAppliedPatchesFilter = GUILayout.Toggle(
  257. HotReloadPrefs.RunTabAppliedPatchesFilter,
  258. new GUIContent(text, GetFilterIcon(AppliedChangesCount, AlertType.AppliedChange)),
  259. HotReloadWindowStyles.EventFiltersStyle);
  260. GUILayout.Space(-1f);
  261. text = UndetectedChangesCount > 999 ? "999+" : " " + UndetectedChangesCount;
  262. HotReloadPrefs.RunTabUndetectedPatchesFilter = GUILayout.Toggle(
  263. HotReloadPrefs.RunTabUndetectedPatchesFilter,
  264. new GUIContent(text, GetFilterIcon(UnsupportedChangesCount, AlertType.UndetectedChange)),
  265. HotReloadWindowStyles.EventFiltersStyle);
  266. GUILayout.Space(-1f);
  267. text = PartiallySupportedChangesCount > 999 ? "999+" : " " + PartiallySupportedChangesCount;
  268. HotReloadPrefs.RunTabPartiallyAppliedPatchesFilter = GUILayout.Toggle(
  269. HotReloadPrefs.RunTabPartiallyAppliedPatchesFilter,
  270. new GUIContent(text, GetFilterIcon(PartiallySupportedChangesCount, AlertType.PartiallySupportedChange)),
  271. HotReloadWindowStyles.EventFiltersStyle);
  272. GUILayout.Space(-1f);
  273. text = UnsupportedChangesCount > 999 ? "999+" : " " + UnsupportedChangesCount;
  274. HotReloadPrefs.RunTabUnsupportedChangesFilter = GUILayout.Toggle(
  275. HotReloadPrefs.RunTabUnsupportedChangesFilter,
  276. new GUIContent(text, GetFilterIcon(UnsupportedChangesCount, AlertType.UnsupportedChange)),
  277. HotReloadWindowStyles.EventFiltersStyle);
  278. GUILayout.Space(-1f);
  279. text = CompileErrorsCount > 999 ? "999+" : " " + CompileErrorsCount;
  280. HotReloadPrefs.RunTabCompileErrorFilter = GUILayout.Toggle(
  281. HotReloadPrefs.RunTabCompileErrorFilter,
  282. new GUIContent(text, GetFilterIcon(CompileErrorsCount, AlertType.CompileError)),
  283. HotReloadWindowStyles.EventFiltersStyle);
  284. }
  285. }
  286. internal static void CreateErrorEventEntry(string errorString, EntryType entryType = EntryType.Standalone, DateTime? createdAt = null) {
  287. var timestamp = createdAt ?? DateTime.Now;
  288. var alertType = errorString.Contains("error CS")
  289. ? AlertType.CompileError
  290. : AlertType.UnsupportedChange;
  291. var title = errorString.Contains("error CS")
  292. ? "Compile error"
  293. : "Unsupported change";
  294. ErrorData errorData = ErrorData.GetErrorData(errorString);
  295. var description = errorData.error;
  296. string shortDescription = null;
  297. if (alertType != AlertType.CompileError) {
  298. shortDescription = shortDescriptionRegex.Match(description).Value;
  299. }
  300. Action actionData = () => RenderErrorEventActions(description, errorData);
  301. InsertEntry(new AlertEntry(
  302. timestamp: timestamp,
  303. alertType: alertType,
  304. title: title,
  305. description: description,
  306. shortDescription: shortDescription,
  307. actionData: actionData,
  308. entryType: entryType,
  309. alertData: new AlertData(AlertEntryType.Error, createdAt: timestamp, errorString: errorString, entryType: entryType)
  310. ));
  311. }
  312. #if UNITY_2020_1_OR_NEWER
  313. internal static void CreateInlinedMethodsEntry(string[] patchedMethodsDisplayNames, EntryType entryType = EntryType.Standalone, DateTime? createdAt = null) {
  314. var truncated = false;
  315. if (patchedMethodsDisplayNames?.Length > 25) {
  316. patchedMethodsDisplayNames = TruncateList(patchedMethodsDisplayNames, 25);
  317. truncated = true;
  318. }
  319. var patchesList = patchedMethodsDisplayNames?.Length > 0 ? string.Join("\n• ", patchedMethodsDisplayNames) : "";
  320. var timestamp = createdAt ?? DateTime.Now;
  321. var entry = new AlertEntry(
  322. timestamp: timestamp,
  323. alertType : AlertType.UnsupportedChange,
  324. title: "Failed applying patch to method",
  325. 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)}",
  326. entryType: EntryType.Parent,
  327. actionData: () => {
  328. GUILayout.Space(10f);
  329. using (new EditorGUILayout.HorizontalScope()) {
  330. RenderCompileButton();
  331. var suggestion = HotReloadSuggestionsHelper.suggestionMap[HotReloadSuggestionKind.SwitchToDebugModeForInlinedMethods];
  332. if (suggestion?.actionData != null) {
  333. suggestion.actionData();
  334. }
  335. }
  336. },
  337. alertData: new AlertData(AlertEntryType.InlinedMethod, createdAt: timestamp, patchedMembersDisplayNames: patchedMethodsDisplayNames, entryType: EntryType.Parent)
  338. );
  339. InsertEntry(entry);
  340. if (patchedMethodsDisplayNames?.Length > 0) {
  341. expandedEntries.Add(entry);
  342. }
  343. }
  344. #endif
  345. internal static void CreatePatchFailureEventEntry(string errorString, string methodName, string methodSimpleName = null, EntryType entryType = EntryType.Standalone, DateTime? createdAt = null) {
  346. var timestamp = createdAt ?? DateTime.Now;
  347. ErrorData errorData = ErrorData.GetErrorData(errorString);
  348. var title = $"Failed applying patch to method";
  349. Action actionData = () => RenderErrorEventActions(errorData.error, errorData);
  350. InsertEntry(new AlertEntry(
  351. timestamp: timestamp,
  352. alertType : AlertType.UnsupportedChange,
  353. title: title,
  354. description: $"{title}: {methodName}, tap here to see more.",
  355. shortDescription: methodSimpleName,
  356. actionData: actionData,
  357. entryType: entryType,
  358. alertData: new AlertData(AlertEntryType.Failure, createdAt: timestamp, errorString: errorString, methodName: methodName, methodSimpleName: methodSimpleName, entryType: entryType)
  359. ));
  360. }
  361. public static T[] TruncateList<T>(T[] originalList, int len) {
  362. if (originalList.Length <= len) {
  363. return originalList;
  364. }
  365. // Create a new list with a maximum of 25 items
  366. T[] truncatedList = new T[len];
  367. for (int i = 0; i < originalList.Length && i < len; i++) {
  368. truncatedList[i] = originalList[i];
  369. }
  370. return truncatedList;
  371. }
  372. internal static void CreateReloadFinishedEventEntry(DateTime? createdAt = null, string[] patchedMethodsDisplayNames = null) {
  373. var truncated = false;
  374. if (patchedMethodsDisplayNames?.Length > 25) {
  375. patchedMethodsDisplayNames = TruncateList(patchedMethodsDisplayNames, 25);
  376. truncated = true;
  377. }
  378. var patchesList = patchedMethodsDisplayNames?.Length > 0 ? string.Join("\n• ", patchedMethodsDisplayNames) : "";
  379. var timestamp = createdAt ?? DateTime.Now;
  380. var entry = new AlertEntry(
  381. timestamp: timestamp,
  382. alertType: AlertType.AppliedChange,
  383. title: EditorIndicationState.IndicationText[EditorIndicationState.IndicationStatus.Reloaded],
  384. description: patchedMethodsDisplayNames?.Length > 0
  385. ? $"• {(truncated ? patchesList + "\n..." : patchesList)}"
  386. : "No issues found",
  387. entryType: patchedMethodsDisplayNames?.Length > 0 ? EntryType.Parent : EntryType.Standalone,
  388. alertData: new AlertData(
  389. AlertEntryType.PatchApplied,
  390. createdAt: timestamp,
  391. entryType: EntryType.Standalone,
  392. patchedMembersDisplayNames: patchedMethodsDisplayNames)
  393. );
  394. InsertEntry(entry);
  395. if (patchedMethodsDisplayNames?.Length > 0) {
  396. expandedEntries.Add(entry);
  397. }
  398. }
  399. internal static void CreateReloadFinishedWithWarningsEventEntry(DateTime? createdAt = null, string[] patchedMembersDisplayNames = null) {
  400. var truncated = false;
  401. if (patchedMembersDisplayNames?.Length > 25) {
  402. patchedMembersDisplayNames = TruncateList(patchedMembersDisplayNames, 25);
  403. truncated = true;
  404. }
  405. var patchesList = patchedMembersDisplayNames?.Length > 0 ? string.Join("\n• ", patchedMembersDisplayNames) : "";
  406. var timestamp = createdAt ?? DateTime.Now;
  407. var entry = new AlertEntry(
  408. timestamp: timestamp,
  409. alertType: AlertType.UnsupportedChange,
  410. title: EditorIndicationState.IndicationText[EditorIndicationState.IndicationStatus.Unsupported],
  411. description: patchedMembersDisplayNames?.Length > 0 ? $"• {(truncated ? patchesList + "\n...\n\nSee unsupported changes below" : patchesList + "\n\nSee unsupported changes below")}" : "See detailed entries below",
  412. entryType: EntryType.Parent,
  413. alertData: new AlertData(AlertEntryType.Failure, createdAt: timestamp, entryType: EntryType.Parent, patchedMembersDisplayNames: patchedMembersDisplayNames)
  414. );
  415. InsertEntry(entry);
  416. if (patchedMembersDisplayNames?.Length > 0) {
  417. expandedEntries.Add(entry);
  418. }
  419. }
  420. internal static void CreateReloadPartiallyAppliedEventEntry(DateTime? createdAt = null, string[] patchedMethodsDisplayNames = null) {
  421. var truncated = false;
  422. if (patchedMethodsDisplayNames?.Length > 25) {
  423. patchedMethodsDisplayNames = TruncateList(patchedMethodsDisplayNames, 25);
  424. truncated = true;
  425. }
  426. var patchesList = patchedMethodsDisplayNames?.Length > 0 ? string.Join("\n• ", patchedMethodsDisplayNames) : "";
  427. var timestamp = createdAt ?? DateTime.Now;
  428. var entry = new AlertEntry(
  429. timestamp: timestamp,
  430. alertType: AlertType.PartiallySupportedChange,
  431. title: EditorIndicationState.IndicationText[EditorIndicationState.IndicationStatus.PartiallySupported],
  432. description: patchedMethodsDisplayNames?.Length > 0 ? $"• {(truncated ? patchesList + "\n...\n\nSee partially applied changes below" : patchesList + "\n\nSee partially applied changes below")}" : "See detailed entries below",
  433. entryType: EntryType.Parent,
  434. alertData: new AlertData(AlertEntryType.PartiallySupportedChange, createdAt: timestamp, entryType: EntryType.Parent, patchedMembersDisplayNames: patchedMethodsDisplayNames)
  435. );
  436. InsertEntry(entry);
  437. if (patchedMethodsDisplayNames?.Length > 0) {
  438. expandedEntries.Add(entry);
  439. }
  440. }
  441. internal static void CreateReloadUndetectedChangeEventEntry(DateTime? createdAt = null) {
  442. var timestamp = createdAt ?? DateTime.Now;
  443. InsertEntry(new AlertEntry(
  444. timestamp: timestamp,
  445. alertType : AlertType.UndetectedChange,
  446. title: EditorIndicationState.IndicationText[EditorIndicationState.IndicationStatus.Undetected],
  447. description: "Code semantics didn't change (e.g. whitespace) or the change requires manual recompile.\n\n" +
  448. "Recompile to force-apply changes.",
  449. actionData: () => {
  450. GUILayout.Space(10f);
  451. using (new EditorGUILayout.HorizontalScope()) {
  452. RenderCompileButton();
  453. GUILayout.FlexibleSpace();
  454. OpenURLButton.Render("Docs", Constants.UndetectedChangesURL);
  455. GUILayout.Space(10f);
  456. }
  457. },
  458. entryType: EntryType.Foldout,
  459. alertData: new AlertData(AlertEntryType.UndetectedChange, createdAt: timestamp, entryType: EntryType.Parent)
  460. ));
  461. }
  462. internal static void CreatePartiallyAppliedEventEntry(PartiallySupportedChange partiallySupportedChange, EntryType entryType = EntryType.Standalone, bool detailed = true, DateTime? createdAt = null) {
  463. var timestamp = createdAt ?? DateTime.Now;
  464. string description;
  465. if (!partiallySupportedChangeDescriptions.TryGetValue(partiallySupportedChange, out description)) {
  466. return;
  467. }
  468. InsertEntry(new AlertEntry(
  469. timestamp: timestamp,
  470. alertType : AlertType.PartiallySupportedChange,
  471. title : detailed ? "Change partially applied" : ToString(partiallySupportedChange),
  472. description : description,
  473. shortDescription: detailed ? ToString(partiallySupportedChange) : null,
  474. actionData: () => {
  475. GUILayout.Space(10f);
  476. using (new EditorGUILayout.HorizontalScope()) {
  477. RenderCompileButton();
  478. GUILayout.FlexibleSpace();
  479. if (GetPartiallySupportedChangePref(partiallySupportedChange)) {
  480. if (GUILayout.Button("Ignore this event type ", HotReloadWindowStyles.LinkStyle)) {
  481. HidePartiallySupportedChange(partiallySupportedChange);
  482. HotReloadRunTab.RepaintInstant();
  483. }
  484. }
  485. }
  486. },
  487. entryType: entryType,
  488. alertData: new AlertData(AlertEntryType.PartiallySupportedChange, createdAt: timestamp, partiallySupportedChange: partiallySupportedChange, entryType: entryType, detiled: detailed)
  489. ));
  490. }
  491. internal static void InsertEntry(AlertEntry entry) {
  492. eventsTimeline.Insert(0, entry);
  493. if (entry.alertType != AlertType.AppliedChange) {
  494. HotReloadState.ShowingRedDot = true;
  495. }
  496. }
  497. internal static void ClearEntries() {
  498. eventsTimeline.Clear();
  499. }
  500. internal static bool GetPartiallySupportedChangePref(PartiallySupportedChange key) {
  501. return EditorPrefs.GetBool($"HotReloadWindow.ShowPartiallySupportedChangeType.{key}", true);
  502. }
  503. internal static void HidePartiallySupportedChange(PartiallySupportedChange key) {
  504. EditorPrefs.SetBool($"HotReloadWindow.ShowPartiallySupportedChangeType.{key}", false);
  505. // loop over scroll entries to remove hidden entries
  506. for (var i = EventsTimeline.Count - 1; i >= 0; i--) {
  507. var eventEntry = EventsTimeline[i];
  508. if (eventEntry.alertData.partiallySupportedChange == key) {
  509. EventsTimeline.Remove(eventEntry);
  510. }
  511. }
  512. }
  513. // performance optimization (Enum.ToString uses reflection)
  514. internal static string ToString(this PartiallySupportedChange change) {
  515. #pragma warning disable CS0612 // obsolete
  516. switch (change) {
  517. case PartiallySupportedChange.LambdaClosure:
  518. return nameof(PartiallySupportedChange.LambdaClosure);
  519. case PartiallySupportedChange.EditAsyncMethod:
  520. return nameof(PartiallySupportedChange.EditAsyncMethod);
  521. case PartiallySupportedChange.AddMonobehaviourMethod:
  522. return nameof(PartiallySupportedChange.AddMonobehaviourMethod);
  523. case PartiallySupportedChange.EditMonobehaviourField:
  524. return nameof(PartiallySupportedChange.EditMonobehaviourField);
  525. case PartiallySupportedChange.EditCoroutine:
  526. return nameof(PartiallySupportedChange.EditCoroutine);
  527. case PartiallySupportedChange.EditGenericFieldInitializer:
  528. return nameof(PartiallySupportedChange.EditGenericFieldInitializer);
  529. case PartiallySupportedChange.AddEnumMember:
  530. return nameof(PartiallySupportedChange.AddEnumMember);
  531. case PartiallySupportedChange.EditFieldInitializer:
  532. return nameof(PartiallySupportedChange.EditFieldInitializer);
  533. case PartiallySupportedChange.AddMethodWithAttributes:
  534. return nameof(PartiallySupportedChange.AddMethodWithAttributes);
  535. case PartiallySupportedChange.GenericMethodInGenericClass:
  536. return nameof(PartiallySupportedChange.GenericMethodInGenericClass);
  537. case PartiallySupportedChange.AddFieldWithAttributes:
  538. return nameof(PartiallySupportedChange.AddFieldWithAttributes);
  539. case PartiallySupportedChange.NewCustomSerializableField:
  540. return nameof(PartiallySupportedChange.NewCustomSerializableField);
  541. case PartiallySupportedChange.MultipleFieldsEditedInTheSameType:
  542. return nameof(PartiallySupportedChange.MultipleFieldsEditedInTheSameType);
  543. #pragma warning restore CS0612
  544. default:
  545. throw new ArgumentOutOfRangeException(nameof(change), change, null);
  546. }
  547. }
  548. }
  549. }