ProjectDDD/Packages/com.singularitygroup.hotreload/Editor/InspectorFreezeFix.cs

118 lines
4.7 KiB (Stored with Git LFS)
C#

using System.Reflection;
using SingularityGroup.HotReload.Editor;
using UnityEditor;
using UnityEngine;
[InitializeOnLoad]
public class InspectorFreezeFix
{
// Inspector window getting stuck is fixed by calling UnityEditor.InspectorWindow.RefreshInspectors()
// Below code subscribes to selection changed callback and calls the method if the inspector is actually stuck
static InspectorFreezeFix()
{
Selection.selectionChanged += OnSelectionChanged;
}
private static int _lastInitialEditorId;
private static void OnSelectionChanged() {
if (!EditorCodePatcher.config.enableInspectorFreezeFix) {
return;
}
try {
// Most of stuff is internal so we use reflection here
var inspectorType = typeof(Editor).Assembly.GetType("UnityEditor.InspectorWindow");
foreach (var inspector in Resources.FindObjectsOfTypeAll(inspectorType)) {
object isLockedValue = inspectorType.GetProperty("isLocked")?.GetValue(inspector);
if (isLockedValue == null) {
continue;
}
// If inspector window is locked deliberately by user (via the lock icon on top-right), we don't need to refresh
var isLocked = (bool)isLockedValue;
if (isLocked) {
continue;
}
// Inspector getting stuck is due to ActiveEditorTracker of that window getting stuck internally.
// The tracker starts returning same values from m_Tracker.activeEditors property.
// (Root of cause of this is out of my reach as the tracker code is mainly native code)
// We detect that by checking first element of activeEditors array
// We do the check because we don't want to RefreshInspectors every selection change, RefreshInspectors is expensive
var tracker = inspectorType.GetField("m_Tracker", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(inspector);
if (tracker == null) {
continue;
}
var activeEditors = tracker.GetType().GetProperty("activeEditors");
if (activeEditors == null) {
continue;
}
var editors = (Editor[])activeEditors.GetValue(tracker);
if (editors.Length == 0) {
continue;
}
var first = editors[0].GetInstanceID();
if (_lastInitialEditorId == first) {
// This forces the tracker to be rebuilt
var m = inspectorType.GetMethod("RefreshInspectors", BindingFlags.Static | BindingFlags.NonPublic);
if (m == null) {
// support for older versions
RefreshInspectors(inspectorType);
} else {
m.Invoke(null, null);
}
}
_lastInitialEditorId = first;
// Calling RefreshInspectors once refreshes all the editors
break;
}
} catch {
// ignore, we don't want to make user experience worse by displaying a warning in this case
}
}
static void RefreshInspectors(System.Type inspectorType) {
var allInspectorsField = inspectorType.GetField("m_AllInspectors", BindingFlags.NonPublic | BindingFlags.Static);
if (allInspectorsField == null) {
return;
}
var allInspectors = allInspectorsField.GetValue(null) as System.Collections.IEnumerable;
if (allInspectors == null) {
return;
}
foreach (var inspector in allInspectors) {
var trackerField = FindFieldInHierarchy(inspector.GetType(), "tracker");
if (trackerField == null) {
continue;
}
var tracker = trackerField.GetValue(inspector);
var forceRebuildMethod = tracker.GetType().GetMethod("ForceRebuild", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (forceRebuildMethod == null) {
continue;
}
forceRebuildMethod.Invoke(tracker, null);
}
}
static PropertyInfo FindFieldInHierarchy(System.Type type, string fieldName) {
PropertyInfo field = null;
while (type != null && field == null) {
field = type.GetProperty(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
type = type.BaseType;
}
return field;
}
}