HotReloadSuggestionsHelper.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Threading.Tasks;
  6. using SingularityGroup.HotReload.DTO;
  7. using UnityEditor;
  8. using UnityEditor.Compilation;
  9. using UnityEditor.PackageManager;
  10. using UnityEditor.PackageManager.Requests;
  11. using UnityEngine;
  12. namespace SingularityGroup.HotReload.Editor {
  13. internal static class HotReloadSuggestionsHelper {
  14. internal static void SetSuggestionsShown(HotReloadSuggestionKind hotReloadSuggestionKind) {
  15. if (EditorPrefs.GetBool($"HotReloadWindow.SuggestionsShown.{hotReloadSuggestionKind}")) {
  16. return;
  17. }
  18. EditorPrefs.SetBool($"HotReloadWindow.SuggestionsActive.{hotReloadSuggestionKind}", true);
  19. EditorPrefs.SetBool($"HotReloadWindow.SuggestionsShown.{hotReloadSuggestionKind}", true);
  20. AlertEntry entry;
  21. if (suggestionMap.TryGetValue(hotReloadSuggestionKind, out entry) && !HotReloadTimelineHelper.Suggestions.Contains(entry)) {
  22. HotReloadTimelineHelper.Suggestions.Insert(0, entry);
  23. HotReloadState.ShowingRedDot = true;
  24. }
  25. }
  26. internal static bool CheckSuggestionActive(HotReloadSuggestionKind hotReloadSuggestionKind) {
  27. return EditorPrefs.GetBool($"HotReloadWindow.SuggestionsActive.{hotReloadSuggestionKind}");
  28. }
  29. internal static bool CheckSuggestionShown(HotReloadSuggestionKind hotReloadSuggestionKind) {
  30. return EditorPrefs.GetBool($"HotReloadWindow.SuggestionsShown.{hotReloadSuggestionKind}");
  31. }
  32. internal static bool CanShowServerSuggestion(HotReloadSuggestionKind hotReloadSuggestionKind) {
  33. if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerWithSideEffects) {
  34. return !HotReloadState.ShowedFieldInitializerWithSideEffects;
  35. } else if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited) {
  36. return !HotReloadState.ShowedFieldInitializerExistingInstancesEdited;
  37. } else if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerExistingInstancesUnedited) {
  38. return !HotReloadState.ShowedFieldInitializerExistingInstancesUnedited;
  39. } else if (hotReloadSuggestionKind == HotReloadSuggestionKind.AddMonobehaviourMethod) {
  40. return !HotReloadState.ShowedAddMonobehaviourMethods;
  41. } else if (hotReloadSuggestionKind == HotReloadSuggestionKind.DetailedErrorReportingIsEnabled) {
  42. return !CheckSuggestionShown(HotReloadSuggestionKind.DetailedErrorReportingIsEnabled);
  43. }
  44. return false;
  45. }
  46. internal static void SetServerSuggestionShown(HotReloadSuggestionKind hotReloadSuggestionKind) {
  47. if (hotReloadSuggestionKind == HotReloadSuggestionKind.DetailedErrorReportingIsEnabled) {
  48. HotReloadSuggestionsHelper.SetSuggestionsShown(hotReloadSuggestionKind);
  49. return;
  50. }
  51. if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerWithSideEffects) {
  52. HotReloadState.ShowedFieldInitializerWithSideEffects = true;
  53. } else if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited) {
  54. HotReloadState.ShowedFieldInitializerExistingInstancesEdited = true;
  55. } else if (hotReloadSuggestionKind == HotReloadSuggestionKind.FieldInitializerExistingInstancesUnedited) {
  56. HotReloadState.ShowedFieldInitializerExistingInstancesUnedited = true;
  57. } else if (hotReloadSuggestionKind == HotReloadSuggestionKind.AddMonobehaviourMethod) {
  58. HotReloadState.ShowedAddMonobehaviourMethods = true;
  59. } else {
  60. return;
  61. }
  62. HotReloadSuggestionsHelper.SetSuggestionActive(hotReloadSuggestionKind);
  63. }
  64. // used for cases where suggestion might need to be shown more than once
  65. internal static void SetSuggestionActive(HotReloadSuggestionKind hotReloadSuggestionKind) {
  66. if (EditorPrefs.GetBool($"HotReloadWindow.SuggestionsShown.{hotReloadSuggestionKind}")) {
  67. return;
  68. }
  69. EditorPrefs.SetBool($"HotReloadWindow.SuggestionsActive.{hotReloadSuggestionKind}", true);
  70. AlertEntry entry;
  71. if (suggestionMap.TryGetValue(hotReloadSuggestionKind, out entry) && !HotReloadTimelineHelper.Suggestions.Contains(entry)) {
  72. HotReloadTimelineHelper.Suggestions.Insert(0, entry);
  73. HotReloadState.ShowingRedDot = true;
  74. }
  75. }
  76. internal static void SetSuggestionInactive(HotReloadSuggestionKind hotReloadSuggestionKind) {
  77. EditorPrefs.SetBool($"HotReloadWindow.SuggestionsActive.{hotReloadSuggestionKind}", false);
  78. AlertEntry entry;
  79. if (suggestionMap.TryGetValue(hotReloadSuggestionKind, out entry)) {
  80. HotReloadTimelineHelper.Suggestions.Remove(entry);
  81. }
  82. }
  83. internal static void InitSuggestions() {
  84. foreach (HotReloadSuggestionKind value in Enum.GetValues(typeof(HotReloadSuggestionKind))) {
  85. if (!CheckSuggestionActive(value)) {
  86. continue;
  87. }
  88. AlertEntry entry;
  89. if (suggestionMap.TryGetValue(value, out entry) && !HotReloadTimelineHelper.Suggestions.Contains(entry)) {
  90. HotReloadTimelineHelper.Suggestions.Insert(0, entry);
  91. }
  92. }
  93. }
  94. internal static HotReloadSuggestionKind? FindSuggestionKind(AlertEntry targetEntry) {
  95. foreach (KeyValuePair<HotReloadSuggestionKind, AlertEntry> pair in suggestionMap) {
  96. if (pair.Value.Equals(targetEntry)) {
  97. return pair.Key;
  98. }
  99. }
  100. return null;
  101. }
  102. internal static readonly OpenURLButton recompileTroubleshootingButton = new OpenURLButton("Docs", Constants.RecompileTroubleshootingURL);
  103. internal static readonly OpenURLButton featuresDocumentationButton = new OpenURLButton("Docs", Constants.FeaturesDocumentationURL);
  104. internal static readonly OpenURLButton multipleEditorsDocumentationButton = new OpenURLButton("Docs", Constants.MultipleEditorsURL);
  105. internal static readonly OpenURLButton debuggerDocumentationButton = new OpenURLButton("More Info", Constants.DebuggerURL);
  106. public static Dictionary<HotReloadSuggestionKind, AlertEntry> suggestionMap = new Dictionary<HotReloadSuggestionKind, AlertEntry> {
  107. { HotReloadSuggestionKind.UnityBestDevelopmentToolAward2023, new AlertEntry(
  108. AlertType.Suggestion,
  109. "Vote for the \"Best Development Tool\" Award!",
  110. "Hot Reload was nominated for the \"Best Development Tool\" Award. Please consider voting. Thank you!",
  111. actionData: () => {
  112. GUILayout.Space(6f);
  113. using (new EditorGUILayout.HorizontalScope()) {
  114. if (GUILayout.Button(" Vote ")) {
  115. Application.OpenURL(Constants.VoteForAwardURL);
  116. SetSuggestionInactive(HotReloadSuggestionKind.UnityBestDevelopmentToolAward2023);
  117. }
  118. GUILayout.FlexibleSpace();
  119. }
  120. },
  121. timestamp: DateTime.Now,
  122. entryType: EntryType.Foldout
  123. )},
  124. { HotReloadSuggestionKind.UnsupportedChanges, new AlertEntry(
  125. AlertType.Suggestion,
  126. "Which changes does Hot Reload support?",
  127. "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",
  128. actionData: () => {
  129. GUILayout.Space(10f);
  130. using (new EditorGUILayout.HorizontalScope()) {
  131. featuresDocumentationButton.OnGUI();
  132. GUILayout.FlexibleSpace();
  133. }
  134. },
  135. timestamp: DateTime.Now,
  136. entryType: EntryType.Foldout
  137. )},
  138. { HotReloadSuggestionKind.UnsupportedPackages, new AlertEntry(
  139. AlertType.Suggestion,
  140. "Unsupported package detected",
  141. "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",
  142. iconType: AlertType.UnsupportedChange,
  143. actionData: () => {
  144. GUILayout.Space(10f);
  145. using (new EditorGUILayout.HorizontalScope()) {
  146. HotReloadAboutTab.contactButton.OnGUI();
  147. GUILayout.FlexibleSpace();
  148. }
  149. },
  150. timestamp: DateTime.Now,
  151. entryType: EntryType.Foldout
  152. )},
  153. { HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges, new AlertEntry(
  154. AlertType.Suggestion,
  155. "Unity recompiles on enter/exit play mode?",
  156. "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.",
  157. actionData: () => {
  158. GUILayout.Space(10f);
  159. using (new EditorGUILayout.HorizontalScope()) {
  160. recompileTroubleshootingButton.OnGUI();
  161. GUILayout.Space(5f);
  162. HotReloadAboutTab.discordButton.OnGUI();
  163. GUILayout.Space(5f);
  164. HotReloadAboutTab.contactButton.OnGUI();
  165. GUILayout.FlexibleSpace();
  166. }
  167. },
  168. timestamp: DateTime.Now,
  169. entryType: EntryType.Foldout
  170. )},
  171. #if UNITY_2022_1_OR_NEWER
  172. { HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges2022, new AlertEntry(
  173. AlertType.Suggestion,
  174. "Unsupported setting detected",
  175. "The 'Sprite Packer Mode' setting can cause unintended recompilations if set to 'Sprite Atlas V1 - Always Enabled'",
  176. iconType: AlertType.UnsupportedChange,
  177. actionData: () => {
  178. GUILayout.Space(10f);
  179. using (new EditorGUILayout.HorizontalScope()) {
  180. if (GUILayout.Button(" Use \"Build Time Only Atlas\" ")) {
  181. if (EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2) {
  182. EditorSettings.spritePackerMode = SpritePackerMode.SpriteAtlasV2Build;
  183. } else {
  184. EditorSettings.spritePackerMode = SpritePackerMode.BuildTimeOnlyAtlas;
  185. }
  186. }
  187. if (GUILayout.Button(" Open Settings ")) {
  188. SettingsService.OpenProjectSettings("Project/Editor");
  189. }
  190. if (GUILayout.Button(" Ignore suggestion ")) {
  191. SetSuggestionInactive(HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges2022);
  192. }
  193. GUILayout.FlexibleSpace();
  194. }
  195. },
  196. timestamp: DateTime.Now,
  197. entryType: EntryType.Foldout,
  198. hasExitButton: false
  199. )},
  200. #endif
  201. { HotReloadSuggestionKind.MultidimensionalArrays, new AlertEntry(
  202. AlertType.Suggestion,
  203. "Use jagged instead of multidimensional arrays",
  204. "Hot Reload doesn't support methods with multidimensional arrays ([,]). You can work around this by using jagged arrays ([][])",
  205. iconType: AlertType.UnsupportedChange,
  206. actionData: () => {
  207. GUILayout.Space(10f);
  208. using (new EditorGUILayout.HorizontalScope()) {
  209. if (GUILayout.Button(" Learn more ")) {
  210. Application.OpenURL("https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1814");
  211. }
  212. GUILayout.FlexibleSpace();
  213. }
  214. },
  215. timestamp: DateTime.Now,
  216. entryType: EntryType.Foldout
  217. )},
  218. { HotReloadSuggestionKind.EditorsWithoutHRRunning, new AlertEntry(
  219. AlertType.Suggestion,
  220. "Some Unity instances don't have Hot Reload running.",
  221. "Make sure that either: \n1) Hot Reload is installed and running on all Editor instances, or \n2) Hot Reload is stopped in all Editor instances where it is installed.",
  222. actionData: () => {
  223. GUILayout.Space(10f);
  224. using (new EditorGUILayout.HorizontalScope()) {
  225. if (GUILayout.Button(" Stop Hot Reload ")) {
  226. EditorCodePatcher.StopCodePatcher().Forget();
  227. }
  228. GUILayout.Space(5f);
  229. multipleEditorsDocumentationButton.OnGUI();
  230. GUILayout.Space(5f);
  231. if (GUILayout.Button(" Don't show again ")) {
  232. HotReloadSuggestionsHelper.SetSuggestionsShown(HotReloadSuggestionKind.EditorsWithoutHRRunning);
  233. HotReloadSuggestionsHelper.SetSuggestionInactive(HotReloadSuggestionKind.EditorsWithoutHRRunning);
  234. }
  235. GUILayout.FlexibleSpace();
  236. GUILayout.FlexibleSpace();
  237. }
  238. },
  239. timestamp: DateTime.Now,
  240. entryType: EntryType.Foldout,
  241. iconType: AlertType.UnsupportedChange
  242. )},
  243. // Not in use (never reported from the server)
  244. { HotReloadSuggestionKind.FieldInitializerWithSideEffects, new AlertEntry(
  245. AlertType.Suggestion,
  246. "Field initializer with side-effects detected",
  247. "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'.",
  248. actionData: () => {
  249. GUILayout.Space(10f);
  250. using (new EditorGUILayout.HorizontalScope()) {
  251. if (GUILayout.Button(" OK ")) {
  252. SetSuggestionInactive(HotReloadSuggestionKind.FieldInitializerWithSideEffects);
  253. }
  254. GUILayout.FlexibleSpace();
  255. if (GUILayout.Button(" Don't show again ")) {
  256. SetSuggestionsShown(HotReloadSuggestionKind.FieldInitializerWithSideEffects);
  257. SetSuggestionInactive(HotReloadSuggestionKind.FieldInitializerWithSideEffects);
  258. }
  259. }
  260. },
  261. timestamp: DateTime.Now,
  262. entryType: EntryType.Foldout,
  263. iconType: AlertType.Suggestion
  264. )},
  265. { HotReloadSuggestionKind.DetailedErrorReportingIsEnabled, new AlertEntry(
  266. AlertType.Suggestion,
  267. "Detailed error reporting is enabled",
  268. "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.",
  269. actionData: () => {
  270. GUILayout.Space(10f);
  271. using (new EditorGUILayout.HorizontalScope()) {
  272. GUILayout.Space(4f);
  273. if (GUILayout.Button(" OK ")) {
  274. SetSuggestionInactive(HotReloadSuggestionKind.DetailedErrorReportingIsEnabled);
  275. }
  276. GUILayout.FlexibleSpace();
  277. if (GUILayout.Button(" Disable ")) {
  278. HotReloadSettingsTab.DisableDetailedErrorReportingInner(true);
  279. SetSuggestionInactive(HotReloadSuggestionKind.DetailedErrorReportingIsEnabled);
  280. }
  281. GUILayout.Space(10f);
  282. }
  283. },
  284. timestamp: DateTime.Now,
  285. entryType: EntryType.Foldout,
  286. iconType: AlertType.Suggestion
  287. )},
  288. // Not in use (never reported from the server)
  289. { HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited, new AlertEntry(
  290. AlertType.Suggestion,
  291. "Field initializer edit updated the value of existing class instances",
  292. "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.",
  293. actionData: () => {
  294. GUILayout.Space(10f);
  295. using (new EditorGUILayout.HorizontalScope()) {
  296. if (GUILayout.Button(" Turn off ")) {
  297. #pragma warning disable CS0618
  298. HotReloadSettingsTab.ApplyApplyFieldInitializerEditsToExistingClassInstances(false);
  299. #pragma warning restore CS0618
  300. SetSuggestionInactive(HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited);
  301. }
  302. if (GUILayout.Button(" Open Settings ")) {
  303. HotReloadWindow.Current.SelectTab(typeof(HotReloadSettingsTab));
  304. }
  305. GUILayout.FlexibleSpace();
  306. if (GUILayout.Button(" Don't show again ")) {
  307. SetSuggestionsShown(HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited);
  308. SetSuggestionInactive(HotReloadSuggestionKind.FieldInitializerExistingInstancesEdited);
  309. }
  310. }
  311. },
  312. timestamp: DateTime.Now,
  313. entryType: EntryType.Foldout,
  314. iconType: AlertType.Suggestion
  315. )},
  316. { HotReloadSuggestionKind.FieldInitializerExistingInstancesUnedited, new AlertEntry(
  317. AlertType.Suggestion,
  318. "Field initializer edits don't apply to existing objects",
  319. "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.",
  320. actionData: () => {
  321. GUILayout.Space(8f);
  322. using (new EditorGUILayout.HorizontalScope()) {
  323. if (GUILayout.Button(" OK ")) {
  324. SetSuggestionsShown(HotReloadSuggestionKind.FieldInitializerExistingInstancesUnedited);
  325. SetSuggestionInactive(HotReloadSuggestionKind.FieldInitializerExistingInstancesUnedited);
  326. }
  327. GUILayout.FlexibleSpace();
  328. }
  329. },
  330. timestamp: DateTime.Now,
  331. entryType: EntryType.Foldout,
  332. iconType: AlertType.Suggestion
  333. )},
  334. { HotReloadSuggestionKind.AddMonobehaviourMethod, new AlertEntry(
  335. AlertType.Suggestion,
  336. "New MonoBehaviour methods are not shown in the inspector",
  337. "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.",
  338. actionData: () => {
  339. GUILayout.Space(8f);
  340. using (new EditorGUILayout.HorizontalScope()) {
  341. if (GUILayout.Button(" OK ")) {
  342. SetSuggestionInactive(HotReloadSuggestionKind.AddMonobehaviourMethod);
  343. }
  344. if (GUILayout.Button(" Auto Recompile ")) {
  345. SetSuggestionInactive(HotReloadSuggestionKind.AddMonobehaviourMethod);
  346. HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges = true;
  347. HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported = true;
  348. HotReloadRunTab.RecompileWithChecks();
  349. }
  350. GUILayout.FlexibleSpace();
  351. if (GUILayout.Button(" Don't show again ")) {
  352. SetSuggestionsShown(HotReloadSuggestionKind.AddMonobehaviourMethod);
  353. SetSuggestionInactive(HotReloadSuggestionKind.AddMonobehaviourMethod);
  354. }
  355. }
  356. },
  357. timestamp: DateTime.Now,
  358. entryType: EntryType.Foldout,
  359. iconType: AlertType.Suggestion
  360. )},
  361. #if UNITY_2020_1_OR_NEWER
  362. { HotReloadSuggestionKind.SwitchToDebugModeForInlinedMethods, new AlertEntry(
  363. AlertType.Suggestion,
  364. "Switch code optimization to Debug Mode",
  365. "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",
  366. actionData: () => {
  367. GUILayout.Space(10f);
  368. using (new EditorGUILayout.HorizontalScope()) {
  369. if (GUILayout.Button(" Switch to Debug mode ") && HotReloadRunTab.ConfirmExitPlaymode("Switching code optimization will stop Play Mode.\n\nDo you wish to proceed?")) {
  370. HotReloadRunTab.SwitchToDebugMode();
  371. }
  372. GUILayout.FlexibleSpace();
  373. }
  374. },
  375. timestamp: DateTime.Now,
  376. entryType: EntryType.Foldout,
  377. iconType: AlertType.UnsupportedChange
  378. )},
  379. #endif
  380. { HotReloadSuggestionKind.HotReloadWhileDebuggerIsAttached, new AlertEntry(
  381. AlertType.Suggestion,
  382. "Hot Reload is disabled while a debugger is attached",
  383. "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.",
  384. actionData: () => {
  385. GUILayout.Space(8f);
  386. using (new EditorGUILayout.HorizontalScope()) {
  387. if (GUILayout.Button(" Keep enabled during debugging ")) {
  388. SetSuggestionInactive(HotReloadSuggestionKind.HotReloadWhileDebuggerIsAttached);
  389. HotReloadPrefs.AutoDisableHotReloadWithDebugger = false;
  390. }
  391. GUILayout.FlexibleSpace();
  392. debuggerDocumentationButton.OnGUI();
  393. if (GUILayout.Button(" Don't show again ")) {
  394. SetSuggestionsShown(HotReloadSuggestionKind.HotReloadWhileDebuggerIsAttached);
  395. SetSuggestionInactive(HotReloadSuggestionKind.HotReloadWhileDebuggerIsAttached);
  396. }
  397. }
  398. },
  399. timestamp: DateTime.Now,
  400. entryType: EntryType.Foldout,
  401. iconType: AlertType.Suggestion
  402. )},
  403. { HotReloadSuggestionKind.HotReloadedMethodsWhenDebuggerIsAttached, new AlertEntry(
  404. AlertType.Suggestion,
  405. "Hot Reload may interfere with your debugger session",
  406. "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.",
  407. actionData: () => {
  408. GUILayout.Space(8f);
  409. using (new EditorGUILayout.HorizontalScope()) {
  410. if (GUILayout.Button(" Recompile ")) {
  411. SetSuggestionInactive(HotReloadSuggestionKind.HotReloadedMethodsWhenDebuggerIsAttached);
  412. if (HotReloadRunTab.ConfirmExitPlaymode("Using the Recompile button will stop Play Mode.\n\nDo you wish to proceed?")) {
  413. HotReloadRunTab.Recompile();
  414. }
  415. }
  416. GUILayout.FlexibleSpace();
  417. debuggerDocumentationButton.OnGUI();
  418. GUILayout.Space(8f);
  419. }
  420. },
  421. timestamp: DateTime.Now,
  422. entryType: EntryType.Foldout,
  423. iconType: AlertType.UnsupportedChange,
  424. hasExitButton: false
  425. )},
  426. };
  427. static ListRequest listRequest;
  428. static string[] unsupportedPackages = new[] {
  429. "com.unity.entities",
  430. "com.firstgeargames.fishnet",
  431. };
  432. static List<string> unsupportedPackagesList;
  433. static DateTime lastPlaymodeChange;
  434. public static void Init() {
  435. listRequest = Client.List(offlineMode: false, includeIndirectDependencies: true);
  436. EditorApplication.playModeStateChanged += state => {
  437. lastPlaymodeChange = DateTime.UtcNow;
  438. };
  439. CompilationPipeline.compilationStarted += obj => {
  440. if (DateTime.UtcNow - lastPlaymodeChange < TimeSpan.FromSeconds(1) && !HotReloadState.RecompiledUnsupportedChangesOnExitPlaymode) {
  441. #if UNITY_2022_1_OR_NEWER
  442. SetSuggestionsShown(HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges2022);
  443. #else
  444. SetSuggestionsShown(HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges);
  445. #endif
  446. }
  447. HotReloadState.RecompiledUnsupportedChangesOnExitPlaymode = false;
  448. };
  449. InitSuggestions();
  450. }
  451. private static DateTime lastCheckedUnityInstances = DateTime.UtcNow;
  452. public static void Check() {
  453. if (listRequest.IsCompleted &&
  454. unsupportedPackagesList == null)
  455. {
  456. unsupportedPackagesList = new List<string>();
  457. if (listRequest.Result != null) {
  458. foreach (var packageInfo in listRequest.Result) {
  459. if (unsupportedPackages.Contains(packageInfo.name)) {
  460. unsupportedPackagesList.Add(packageInfo.name);
  461. }
  462. }
  463. }
  464. if (unsupportedPackagesList.Count > 0) {
  465. SetSuggestionsShown(HotReloadSuggestionKind.UnsupportedPackages);
  466. }
  467. }
  468. CheckEditorsWithoutHR();
  469. #if UNITY_2022_1_OR_NEWER
  470. if (EditorSettings.spritePackerMode == SpritePackerMode.AlwaysOnAtlas || EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2) {
  471. SetSuggestionsShown(HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges2022);
  472. } else if (CheckSuggestionActive(HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges2022)) {
  473. SetSuggestionInactive(HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges2022);
  474. EditorPrefs.SetBool($"HotReloadWindow.SuggestionsShown.{HotReloadSuggestionKind.AutoRecompiledWhenPlaymodeStateChanges2022}", false);
  475. }
  476. #endif
  477. }
  478. private static void CheckEditorsWithoutHR() {
  479. if (!ServerHealthCheck.I.IsServerHealthy) {
  480. HotReloadSuggestionsHelper.SetSuggestionInactive(HotReloadSuggestionKind.EditorsWithoutHRRunning);
  481. return;
  482. }
  483. if (checkingEditorsWihtoutHR ||
  484. (DateTime.UtcNow - lastCheckedUnityInstances).TotalSeconds < 5)
  485. {
  486. return;
  487. }
  488. CheckEditorsWithoutHRAsync().Forget();
  489. }
  490. static bool checkingEditorsWihtoutHR;
  491. private static async Task CheckEditorsWithoutHRAsync() {
  492. try {
  493. checkingEditorsWihtoutHR = true;
  494. var showSuggestion = await Task.Run(() => {
  495. try {
  496. var runningUnities = Process.GetProcessesByName("Unity Editor").Length;
  497. var runningPatchers = Process.GetProcessesByName("CodePatcherCLI").Length;
  498. return runningPatchers > 0 && runningUnities > runningPatchers;
  499. } catch (ArgumentException) {
  500. // On some devices GetProcessesByName throws ArgumentException for no good reason.
  501. // it happens rarely and the feature is not the most important so proper solution is not required
  502. return false;
  503. }
  504. });
  505. if (!showSuggestion) {
  506. HotReloadSuggestionsHelper.SetSuggestionInactive(HotReloadSuggestionKind.EditorsWithoutHRRunning);
  507. return;
  508. }
  509. if (!HotReloadState.ShowedEditorsWithoutHR && ServerHealthCheck.I.IsServerHealthy) {
  510. HotReloadSuggestionsHelper.SetSuggestionActive(HotReloadSuggestionKind.EditorsWithoutHRRunning);
  511. HotReloadState.ShowedEditorsWithoutHR = true;
  512. }
  513. } finally {
  514. checkingEditorsWihtoutHR = false;
  515. lastCheckedUnityInstances = DateTime.UtcNow;
  516. }
  517. }
  518. }
  519. }