在编辑器工具中快速提取类型属性
如果你针对Unity 2019.2开发编辑器实用功能或资源包,可以使用UnityEditor.TypeCache API处理类型提取过程,以减少工具的初始化时间,进入运行模式的时间以及域重载的时间。
产生性能问题的原因
在调查和优化进入运行模式的过程时,我们发现从加载的程序集提取类型需要很长的时间。
类型提取在内部被编辑器模块广泛使用,在外部被资源包和用户代码用于扩展编辑功能。积累效果根据项目而不同,可能会给域重载时间增加300-600毫秒,如果系统具有延迟初始化,它会增加更多时间。
在新的Mono运行时中,由于Type.IsSubclassOf的性能下降问题,所需时间会大幅提升,最多可达1300毫秒。
产生性能问题的原因是,代码通常会从当前域提取所有类型,然后迭代所有类型,执行耗费性能的检查。所需时间会根据游戏拥有的类型数量而线性增加,通常游戏的类型数量为3~6万。
解决方法
缓存类型信息允许我们减少在域中迭代类型产生的O(N)复杂度。在本地层级,我们已经有了加速结构,它们会在程序集加载后填充,并缓存类型数据,例如:方法,类属性和接口实现。
在内部,这些结构会通过UnityEditor.EditorAssemblies API公开,以利用快速缓存功能。然而,该API并没有公开发布,而且也不支持重要的SubclassesOf用例。
在Unity 2019.2中,我们优化并扩展了本地缓存,并将其作为公开的UnityEditor.TypeCache API。它可以非常快速的提取信息,允许仅对我们感兴趣的少量类型进行迭代,一般为10-100个,这样可以显著降低编辑器工具获取类型所需的时间。
public static class TypeCache
{
public static TypeCollection GetTypesDerivedFrom < T > ();
public static TypeCollection GetTypesWithAttribute < T > () 其中 T : Attribute ;
public static MethodCollection GetMethodsWithAttribute < T > () 其中 T : Attribute ;
public struct MethodCollection : IList < MethodInfo > { ... }
public struct TypeCollection : IList < Type > { ... }
}
我们在本地拥有的基础数据由数组来表示,并且它对于域生命周期是不可改变的。因此,我们可以使用返回IList<T>接口的API,它会实现为本地Dynamic_array数据的视图,它会给我们提供:
IEnumerable(foreach和LINQ)的灵活性和可用性。
使用for (int i)进行快速迭代
快速转换为List<T>和Array
使用示例
下面查看几个示例。
通常,查找接口实现的代码如下:
static List<Type> ScanInterfaceImplementors(Type interfaceType)
{
var types = new List<Type>();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
Type[] allAssemblyTypes;
try
{
allAssemblyTypes = assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
allAssemblyTypes = e.Types;
}
var myTypes = allAssemblyTypes.Where(t =>!t.IsAbstract && interfaceType.IsAssignableFrom(t));
types.AddRange(myTypes);
}
return types;
}
通过TypeCache API,只需使用以下代码:
TypeCache.GetTypesDerivedFrom<MyInterface>().ToList()
类似的,查找用属性标记的类型需要以下代码:
static List<Type> ScanTypesWithAttributes(Type attributeType)
{
var types = new List<Type>();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
Type[] allAssemblyTypes;
try
{
allAssemblyTypes = assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
allAssemblyTypes = e.Types;
}
var myTypes = allAssemblyTypes.Where(t =>!t.IsAbstract && Attribute.IsDefined(t, attributeType, true));
types.AddRange(myTypes);
}
return types;
}
通过使用TypeCache API,只需要一行代码:
TypeCache.GetTypesWithAttribute<MyAttribute>().ToList();
性能
如果我们使用性能测试框架编写简单的性能测试,我们可以清楚看到使用TypeCache API的优势。
在一个空白项目中,我们可以在域重载后节省超过100毫秒的时间。
未来在Unity 2019.3中,TypeCache.GetTypesDerivedFrom也支持把泛型类和接口作为参数。大部分编辑器代码已经转换为TypeCache API。
所以,我们希望用户尝试使用TypeCache API,你的反馈将帮助我们使编辑器变得更快。
如果你开发的编辑器实用功能或资源包需要扫描域中的所有类型,以使其变得可定制,请考虑使用UnityEditor.TypeCache API,使用它的积累效果会显著降低域重载时间。
小结
了解UnityEditor.TypeCache API:
https://docs.unity3d.com/2019.2/Documentation/ScriptReference/TypeCache.html
更多Unity 2019.2新功能介绍,尽在Unity Connect平台(Connect.unity.com)。
下载Unity Connect APP,请点击此处。 观看部分Unity官方视频,请关注B站帐户:Unity官方。
推荐阅读
喜欢本文,请点击“在看”