VR4Medical/ICI/Library/PackageCache/com.unity.xr.core-utils@5b282bc7378d/Runtime/ReflectionUtils.cs
2025-07-29 13:45:50 +03:00

260 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine;
namespace Unity.XR.CoreUtils
{
/// <summary>
/// Utility methods for common reflection-based operations.
/// </summary>
public static class ReflectionUtils
{
static Assembly[] s_Assemblies;
static List<Type[]> s_TypesPerAssembly;
static List<Dictionary<string, Type>> s_AssemblyTypeMaps;
static Assembly[] GetCachedAssemblies() { return s_Assemblies ?? (s_Assemblies = AppDomain.CurrentDomain.GetAssemblies()); }
static List<Type[]> GetCachedTypesPerAssembly()
{
if (s_TypesPerAssembly == null)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
s_TypesPerAssembly = new List<Type[]>(assemblies.Length);
foreach (var assembly in assemblies)
{
try
{
s_TypesPerAssembly.Add(assembly.GetTypes());
}
catch (ReflectionTypeLoadException)
{
// Skip any assemblies that don't load properly -- suppress errors
}
}
}
return s_TypesPerAssembly;
}
static List<Dictionary<string, Type>> GetCachedAssemblyTypeMaps()
{
if (s_AssemblyTypeMaps == null)
{
var typesPerAssembly = GetCachedTypesPerAssembly();
s_AssemblyTypeMaps = new List<Dictionary<string, Type>>(typesPerAssembly.Count);
foreach (var types in typesPerAssembly)
{
try
{
var typeMap = new Dictionary<string, Type>();
foreach (var type in types)
{
typeMap[type.FullName] = type;
}
s_AssemblyTypeMaps.Add(typeMap);
}
catch (ReflectionTypeLoadException)
{
// Skip any assemblies that don't load properly -- suppress errors
}
}
}
return s_AssemblyTypeMaps;
}
/// <summary>
/// Caches type information from all currently loaded assemblies.
/// </summary>
public static void PreWarmTypeCache() { GetCachedAssemblyTypeMaps(); }
/// <summary>
/// Executes a delegate function for every assembly that can be loaded.
/// </summary>
/// <remarks>
/// `ForEachAssembly` iterates through all assemblies and executes a method on each one.
/// If an <see cref="ReflectionTypeLoadException"/> is thrown, it is caught and ignored.
/// </remarks>
/// <param name="callback">The callback method to execute for each assembly.</param>
public static void ForEachAssembly(Action<Assembly> callback)
{
var assemblies = GetCachedAssemblies();
foreach (var assembly in assemblies)
{
try
{
callback(assembly);
}
catch (ReflectionTypeLoadException)
{
// Skip any assemblies that don't load properly -- suppress errors
}
}
}
/// <summary>
/// Executes a delegate function for each type in every assembly.
/// </summary>
/// <param name="callback">The callback to execute.</param>
public static void ForEachType(Action<Type> callback)
{
var typesPerAssembly = GetCachedTypesPerAssembly();
foreach (var types in typesPerAssembly)
{
foreach (var type in types)
{
callback(type);
}
}
}
/// <summary>
/// Search all assemblies for a type that matches a given predicate delegate.
/// </summary>
/// <param name="predicate">The predicate function. Must return <see langword="true"/> for the type that matches the search.</param>
/// <returns>The first type for which <paramref name="predicate"/> returns <see langword="true"/>, or `null` if no matching type exists.</returns>
public static Type FindType(Func<Type, bool> predicate)
{
var typesPerAssembly = GetCachedTypesPerAssembly();
foreach (var types in typesPerAssembly)
{
foreach (var type in types)
{
if (predicate(type))
return type;
}
}
return null;
}
/// <summary>
/// Find a type in any assembly by its full name.
/// </summary>
/// <param name="fullName">The name of the type as returned by <see cref="Type.FullName"/>.</param>
/// <returns>The type found, or null if no matching type exists.</returns>
public static Type FindTypeByFullName(string fullName)
{
var typesPerAssembly = GetCachedAssemblyTypeMaps();
foreach (var assemblyTypes in typesPerAssembly)
{
if (assemblyTypes.TryGetValue(fullName, out var type))
return type;
}
return null;
}
/// <summary>
/// Search all assemblies for a set of types that matches any one of a set of predicates.
/// </summary>
/// <remarks>
/// This function tests each predicate against each type in each assembly. If the predicate returns
/// <see langword="true"/> for a type, then that <see cref="Type"/> object is assigned to the corresponding index of
/// the <paramref name="resultList"/>. If a predicate returns <see langword="true"/> for more than one type, then the
/// last matching result is used. If no type matches the predicate, then that index of <paramref name="resultList"/>
/// is left unchanged.
/// </remarks>
/// <param name="predicates">The predicate functions. A predicate function must return <see langword="true"/>
/// for the type that matches the search and should only match one type.</param>
/// <param name="resultList">The list to which found types will be added. The list must have
/// the same number of elements as the <paramref name="predicates"/> list.</param>
public static void FindTypesBatch(List<Func<Type, bool>> predicates, List<Type> resultList)
{
var typesPerAssembly = GetCachedTypesPerAssembly();
for (var i = 0; i < predicates.Count; i++)
{
var predicate = predicates[i];
foreach (var assemblyTypes in typesPerAssembly)
{
foreach (var type in assemblyTypes)
{
if (predicate(type))
resultList[i] = type;
}
}
}
}
/// <summary>
/// Searches all assemblies for a set of types by their <see cref="Type.FullName"/> strings.
/// </summary>
/// <remarks>
/// If a type name in <paramref name="typeNames"/> is not found, then the corresponding index of <paramref name="resultList"/>
/// is set to `null`.
/// </remarks>
/// <param name="typeNames">A list containing the <see cref="Type.FullName"/> strings of the types to find.</param>
/// <param name="resultList">An empty list to which any matching <see cref="Type"/> objects are added. A
/// result in <paramref name="resultList"/> has the same index as corresponding name in <paramref name="typeNames"/>.</param>
public static void FindTypesByFullNameBatch(List<string> typeNames, List<Type> resultList)
{
var assemblyTypeMap = GetCachedAssemblyTypeMaps();
foreach (var typeName in typeNames)
{
var found = false;
foreach (var typeMap in assemblyTypeMap)
{
if (typeMap.TryGetValue(typeName, out var type))
{
resultList.Add(type);
found = true;
break;
}
}
// If a type can't be found, add a null entry to the list to ensure indexes match
if (!found)
resultList.Add(null);
}
}
/// <summary>
/// Searches for a type by assembly simple name and its <see cref="Type.FullName"/>.
/// an assembly with the given simple name and returns the type with the given full name in that assembly
/// </summary>
/// <param name="assemblyName">Simple name of the assembly (<see cref="Assembly.GetName()"/>).</param>
/// <param name="typeName">Full name of the type to find (<see cref="Type.FullName"/>).</param>
/// <returns>The type if found, otherwise null</returns>
public static Type FindTypeInAssemblyByFullName(string assemblyName, string typeName)
{
var assemblies = GetCachedAssemblies();
var assemblyTypeMaps = GetCachedAssemblyTypeMaps();
for (var i = 0; i < assemblies.Length; i++)
{
if (assemblies[i].GetName().Name != assemblyName)
continue;
return assemblyTypeMaps[i].TryGetValue(typeName, out var type) ? type : null;
}
return null;
}
/// <summary>
/// Cleans up a variable name for display in UI.
/// </summary>
/// <param name="name">The variable name to clean up.</param>
/// <returns>The display name for the variable.</returns>
public static string NicifyVariableName(string name)
{
if (name.StartsWith("m_"))
name = name.Substring(2, name.Length - 2);
else if (name.StartsWith("_"))
name = name.Substring(1, name.Length - 1);
if (name[0] == 'k' && name[1] >= 'A' && name[1] <= 'Z')
name = name.Substring(1, name.Length - 1);
// Insert a space before any capital letter unless it is the beginning or end of a word
name = Regex.Replace(name, @"(\B[A-Z]+?(?=[A-Z][^A-Z])|\B[A-Z]+?(?=[^A-Z]))", " $1");
name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name);
return name;
}
}
}