InspectorFreezeFix.cs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. using System.Reflection;
  2. using SingularityGroup.HotReload.Editor;
  3. using UnityEditor;
  4. using UnityEngine;
  5. [InitializeOnLoad]
  6. public class InspectorFreezeFix
  7. {
  8. // Inspector window getting stuck is fixed by calling UnityEditor.InspectorWindow.RefreshInspectors()
  9. // Below code subscribes to selection changed callback and calls the method if the inspector is actually stuck
  10. static InspectorFreezeFix()
  11. {
  12. Selection.selectionChanged += OnSelectionChanged;
  13. }
  14. private static int _lastInitialEditorId;
  15. private static void OnSelectionChanged() {
  16. if (!EditorCodePatcher.config.enableInspectorFreezeFix) {
  17. return;
  18. }
  19. try {
  20. // Most of stuff is internal so we use reflection here
  21. var inspectorType = typeof(Editor).Assembly.GetType("UnityEditor.InspectorWindow");
  22. foreach (var inspector in Resources.FindObjectsOfTypeAll(inspectorType)) {
  23. object isLockedValue = inspectorType.GetProperty("isLocked")?.GetValue(inspector);
  24. if (isLockedValue == null) {
  25. continue;
  26. }
  27. // If inspector window is locked deliberately by user (via the lock icon on top-right), we don't need to refresh
  28. var isLocked = (bool)isLockedValue;
  29. if (isLocked) {
  30. continue;
  31. }
  32. // Inspector getting stuck is due to ActiveEditorTracker of that window getting stuck internally.
  33. // The tracker starts returning same values from m_Tracker.activeEditors property.
  34. // (Root of cause of this is out of my reach as the tracker code is mainly native code)
  35. // We detect that by checking first element of activeEditors array
  36. // We do the check because we don't want to RefreshInspectors every selection change, RefreshInspectors is expensive
  37. var tracker = inspectorType.GetField("m_Tracker", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(inspector);
  38. if (tracker == null) {
  39. continue;
  40. }
  41. var activeEditors = tracker.GetType().GetProperty("activeEditors");
  42. if (activeEditors == null) {
  43. continue;
  44. }
  45. var editors = (Editor[])activeEditors.GetValue(tracker);
  46. if (editors.Length == 0) {
  47. continue;
  48. }
  49. var first = editors[0].GetInstanceID();
  50. if (_lastInitialEditorId == first) {
  51. // This forces the tracker to be rebuilt
  52. var m = inspectorType.GetMethod("RefreshInspectors", BindingFlags.Static | BindingFlags.NonPublic);
  53. if (m == null) {
  54. // support for older versions
  55. RefreshInspectors(inspectorType);
  56. } else {
  57. m.Invoke(null, null);
  58. }
  59. }
  60. _lastInitialEditorId = first;
  61. // Calling RefreshInspectors once refreshes all the editors
  62. break;
  63. }
  64. } catch {
  65. // ignore, we don't want to make user experience worse by displaying a warning in this case
  66. }
  67. }
  68. static void RefreshInspectors(System.Type inspectorType) {
  69. var allInspectorsField = inspectorType.GetField("m_AllInspectors", BindingFlags.NonPublic | BindingFlags.Static);
  70. if (allInspectorsField == null) {
  71. return;
  72. }
  73. var allInspectors = allInspectorsField.GetValue(null) as System.Collections.IEnumerable;
  74. if (allInspectors == null) {
  75. return;
  76. }
  77. foreach (var inspector in allInspectors) {
  78. var trackerField = FindFieldInHierarchy(inspector.GetType(), "tracker");
  79. if (trackerField == null) {
  80. continue;
  81. }
  82. var tracker = trackerField.GetValue(inspector);
  83. var forceRebuildMethod = tracker.GetType().GetMethod("ForceRebuild", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  84. if (forceRebuildMethod == null) {
  85. continue;
  86. }
  87. forceRebuildMethod.Invoke(tracker, null);
  88. }
  89. }
  90. static PropertyInfo FindFieldInHierarchy(System.Type type, string fieldName) {
  91. PropertyInfo field = null;
  92. while (type != null && field == null) {
  93. field = type.GetProperty(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  94. type = type.BaseType;
  95. }
  96. return field;
  97. }
  98. }