//#define DEBUG_OVERRIDE_EDITOR
#if UNITY_2023_1_OR_NEWER
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Phantom.XRMOD.UnityFusion.Editor
{
///
/// Wrapper for the dictionary that Unity uses internally to match UnityEngine.Object types with the custom editors targeting them.
///
/// Dictionary is retrieved from the non-public field UnityEditor.CustomEditorAttributes.m_Cache.m_CustomEditorCache using reflection.
///
///
public sealed class InternalCustomEditorCache
{
private readonly IDictionary customEditorCache; // Dictionary
private readonly Type monoEditorTypeStorageType;
private readonly FieldInfo customEditorsField;
private readonly FieldInfo customEditorsMultiEditionField;
///
/// Wrapper for the dictionary that Unity uses internally to match UnityEngine.Object types with the custom editors targeting them.
///
/// Dictionary is retrieved from the non-public field UnityEditor.CustomEditorAttributes.m_Cache.m_CustomEditorCache using reflection.
///
///
public InternalCustomEditorCache()
{
if(!TryGetInternalEditorType("UnityEditor.CustomEditorAttributes", out Type customEditorAttributesType)
|| !TryGetStaticField(customEditorAttributesType, "k_Instance", out var lazyInstanceField)
|| !TryGetInstanceField(customEditorAttributesType, "m_Cache", out var cacheField))
{
return;
}
object lazyInstance = lazyInstanceField.GetValue(null); // Lazy
object customEditorAttributes = lazyInstance.GetType().GetProperty(nameof(Lazy.Value)).GetValue(lazyInstance); // CustomEditorAttributes
object cache = cacheField.GetValue(customEditorAttributes); // CustomEditorAttributes.CustomEditorCache
if(!TryGetInstanceField(cache.GetType(), "m_CustomEditorCache", out var customEditorCacheField))
{
return;
}
EnsureInternalEditorsDictionaryIsBuilt();
customEditorCache = customEditorCacheField.GetValue(cache) as IDictionary;
var enumerator = customEditorCache.GetEnumerator();
if(!enumerator.MoveNext())
{
#if DEV_MODE
Debug.LogError($"Custom editor cache was empty - can not to build editor type dictionaries at this time.");
#endif
return;
}
var typeAndStorage = (DictionaryEntry)enumerator.Current; // (Type, MonoEditorTypeStorage)
var storage = typeAndStorage.Value; // MonoEditorTypeStorage
monoEditorTypeStorageType = storage.GetType(); // MonoEditorTypeStorage
TryGetInstanceField(monoEditorTypeStorageType, "customEditors", out customEditorsField); // List
TryGetInstanceField(monoEditorTypeStorageType, "customEditorsMultiEdition", out customEditorsMultiEditionField); // List
void EnsureInternalEditorsDictionaryIsBuilt()
{
if(TryGetStaticMethod(customEditorAttributes.GetType(), "FindCustomEditorTypeByType", out var findCustomEditorTypeMethod))
{
findCustomEditorTypeMethod.Invoke(null, new object[] { null, false });
}
}
}
public bool ContainsInspectedType(Type inspectedType) => customEditorCache.Contains(inspectedType);
public bool ContainsEditorType(Type editorType)
{
foreach(DictionaryEntry inspectedToEditors in customEditorCache)
{
if(StorageContainsEditor(inspectedToEditors))
{
return true;
}
}
return false;
bool StorageContainsEditor(DictionaryEntry inspectedToEditors) // (Type, MonoEditorTypeStorage)
{
if(!(inspectedToEditors.Key is Type))
{
return false;
}
var storage = inspectedToEditors.Value; // MonoEditorTypeStorage
foreach(var monoEditorType in customEditorsField.GetValue(storage) as IList)
{
if(CustomEditorInfo.ExtractInspectorType(monoEditorType) == editorType)
{
return true;
}
}
return false;
}
}
public void InjectCustomEditor(Type inspectedType, Type editorType, bool canEditMultipleObjects, bool editorForChildClasses = false, bool isFallback = false)
{
IList customEditors;
IList customEditorsMultiEdition;
if(!customEditorCache.Contains(inspectedType))
{
var listType = customEditorsField.FieldType; // List
var monoEditorTypeStorage = Activator.CreateInstance(monoEditorTypeStorageType);
// Create List with capacity of 1 item.
customEditors = Activator.CreateInstance(listType, 1) as IList;
customEditorsField.SetValue(monoEditorTypeStorage, customEditors); // List
// If multi-editing is enabled, use same list, otherwise use an empty list
if(canEditMultipleObjects)
{
customEditorsMultiEdition = Activator.CreateInstance(listType, customEditors.Count) as IList;
}
else
{
customEditorsMultiEdition = Activator.CreateInstance(listType, 0) as IList;
}
customEditorsMultiEditionField.SetValue(monoEditorTypeStorage, customEditorsMultiEdition); // List
#if DEV_MODE && DEBUG_OVERRIDE_EDITOR
Debug.Log($"Replacing {inspectedType.Name} default editor with {editorType.inspectorType.Name}.");
#endif
customEditorCache.Add(inspectedType, monoEditorTypeStorage);
}
else
{
var monoEditorTypeStorage = customEditorCache[inspectedType]; // MonoEditorTypeStorage
#if DEV_MODE && DEBUG_OVERRIDE_EDITOR
Debug.Log($"Replacing {inspectedType.Name} custom editor with {editorType.editorType.Name}.");
#endif
customEditors = customEditorsField.GetValue(monoEditorTypeStorage) as IList;
customEditorsMultiEdition = customEditorsMultiEditionField.GetValue(monoEditorTypeStorage) as IList;
if(editorForChildClasses && !isFallback)
{
customEditors.Clear();
if(canEditMultipleObjects)
{
customEditorsMultiEdition.Clear();
}
}
else
{
for(int i = customEditors.Count - 1; i >= 0; i--)
{
var info = new CustomEditorInfo(customEditors[i]);
if(!info.editorForChildClasses)
{
customEditors.RemoveAt(i);
continue;
}
if(!isFallback && !info.isFallback)
{
customEditors[i] = new CustomEditorInfo(info.inspectorType, true, true).ToInternalType();
}
}
if(canEditMultipleObjects)
{
for(int i = customEditorsMultiEdition.Count - 1; i >= 0; i--)
{
var info = new CustomEditorInfo(customEditorsMultiEdition[i]);
if(!info.editorForChildClasses)
{
customEditorsMultiEdition.RemoveAt(i);
continue;
}
if(!isFallback && !info.isFallback)
{
customEditorsMultiEdition[i] = new CustomEditorInfo(info.inspectorType, true, true).ToInternalType();
}
}
}
}
}
// (Type inspectorType, Type[] supportedRenderPipelineTypes, bool editorForChildClasses, bool isFallback)
var monoEditorTypeConstructorArguments = new object[] { editorType, null, editorForChildClasses, isFallback};
var monoEditorTypeInstance = Activator.CreateInstance(CustomEditorInfo.monoEditorTypeType, monoEditorTypeConstructorArguments);
customEditors.Add(monoEditorTypeInstance);
if(canEditMultipleObjects)
{
customEditorsMultiEdition.Add(monoEditorTypeInstance);
}
#if ODIN_INSPECTOR
Sirenix.OdinInspector.Editor.CustomEditorUtility.SetCustomEditor(inspectedType, editorType, editorForChildClasses, isFallback);
#endif
}
public void CopyTo(Dictionary dictionary)
{
int count = customEditorCache.Count;
if(count == 0)
{
#if DEV_MODE
Debug.LogError($"Custom editor cache was empty - can not to build editor type dictionaries at this time.");
#endif
return;
}
dictionary.EnsureCapacity(count);
var enumerator = customEditorCache.GetEnumerator();
if(!enumerator.MoveNext())
{
#if DEV_MODE
Debug.LogError($"Custom editor cache was empty - can not to build editor type dictionaries at this time.");
#endif
return;
}
var typeAndStorage = (DictionaryEntry)enumerator.Current; // (Type, MonoEditorTypeStorage)
var storage = typeAndStorage.Value; // MonoEditorTypeStorage
var storageType = storage.GetType(); // MonoEditorTypeStorage
if(!TryGetInstanceField(storageType, "customEditors", out var customEditorsField) // List
|| !TryGetInstanceField(storageType, "customEditors", out var customMultiEditorsField)) // List
{
return;
}
AddEditorsFromStorage(typeAndStorage);
while(enumerator.MoveNext())
{
AddEditorsFromStorage((DictionaryEntry)enumerator.Current);
}
void AddEditorsFromStorage(DictionaryEntry typeAndStorage) // MonoEditorTypeStorage
{
if(!(typeAndStorage.Key is Type type))
{
return;
}
var storage = typeAndStorage.Value; // MonoEditorTypeStorage
var editors = CustomEditorInfo.Create(customEditorsField.GetValue(storage) as IList); // List
var multiEditors = CustomEditorInfo.Create(customMultiEditorsField.GetValue(storage) as IList); // List
dictionary[type] = new CustomEditorInfoGroup(editors, multiEditors);
}
}
internal static bool TryGetInternalEditorType(string fullTypeName, out Type result)
{
#if DEV_MODE
Debug.Assert(fullTypeName.IndexOf(".") != -1, fullTypeName);
#endif
result = typeof(UnityEditor.Editor).Assembly.GetType(fullTypeName);
if(result is null)
{
Debug.LogError($"Type {fullTypeName} not found in the assembly {typeof(UnityEditor.Editor).Assembly.GetName().Name}. Contact the developer in the forums to add support for your Unity version.");
return false;
}
return true;
}
private static bool TryGetStaticField(Type type, string fieldName, out FieldInfo result)
{
result = type.GetField(fieldName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if(result is null)
{
#if UNITY_2023_1_0
Debug.LogError($"Field {type.FullName}.{fieldName} not found. Please update to 2023.1.0a14 or newer.");
#else
Debug.LogError($"Field {type.FullName}.{fieldName} not found. Contact the developer in the forums to add support for your Unity version.");
#endif
return false;
}
return true;
}
private static bool TryGetStaticMethod(Type type, string methodName, out MethodInfo result)
{
result = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if(result is null)
{
#if UNITY_2023_1_0
Debug.LogWarning($"Method {type.FullName}.{methodName} not found. Please update to 2023.1.0a14 or newer.");
#else
Debug.LogWarning($"Method {type.FullName}.{methodName} not found. Contact the developer in the forums to add support for your Unity version.");
#endif
return false;
}
return true;
}
private static bool TryGetInstanceField(Type type, string fieldName, out FieldInfo result)
{
result = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if(result is null)
{
#if UNITY_2023_1_0
Debug.LogError($"Field {type.FullName}.{fieldName} not found. Please update to 2023.1.0a14 or newer.");
#else
Debug.LogError($"Field {type.FullName}.{fieldName} not found. Contact the developer in the forums to add support for your Unity version.");
#endif
return false;
}
return true;
}
}
}
#endif