public static FindExecuteMethodsResult FindExecuteMethods(IEnumerable <System.Reflection.Assembly> assemblyList, BurstReflectionAssemblyOptions options) { var methodsToCompile = new List <BurstCompileTarget>(); var methodsToCompileSet = new HashSet <MethodInfo>(); var valueTypes = new List <TypeToVisit>(); var interfaceToProducer = new Dictionary <Type, Type>(); // This method can be called on a background thread, so we can't call Debug.Log etc. // Instead collect all the log messages and return them. var logMessages = new List <LogMessage>(); //Debug.Log("Filtered Assembly List: " + string.Join(", ", assemblyList.Select(assembly => assembly.GetName().Name))); // Find all ways to execute job types (via producer attributes) var typesVisited = new HashSet <string>(); var typesToVisit = new HashSet <string>(); var allTypesAssembliesCollected = new HashSet <Type>(); foreach (var assembly in assemblyList) { var types = new List <Type>(); try { var typesFromAssembly = assembly.GetTypes(); types.AddRange(typesFromAssembly); foreach (var typeInAssembly in typesFromAssembly) { allTypesAssembliesCollected.Add(typeInAssembly); } // Collect all generic type instances (excluding indirect instances) CollectGenericTypeInstances(assembly, x => true, types, allTypesAssembliesCollected); } catch (Exception ex) { logMessages.Add(new LogMessage(LogType.Warning, "Unexpected exception while collecting types in assembly `" + assembly.FullName + "` Exception: " + ex)); } for (var i = 0; i < types.Count; i++) { var t = types[i]; if (typesToVisit.Add(t.FullName)) { // Because the list of types returned by CollectGenericTypeInstances does not detect nested generic classes that are not // used explicitly, we need to create them if a declaring type is actually used // so for example if we have: // class MyClass<T> { class MyNestedClass { } } // class MyDerived : MyClass<int> { } // The CollectGenericTypeInstances will return typically the type MyClass<int>, but will not list MyClass<int>.MyNestedClass // So the following code is correcting this in order to fully query the full graph of generic instance types, including indirect types var nestedTypes = t.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); foreach (var nestedType in nestedTypes) { if (t.IsGenericType && !t.IsGenericTypeDefinition) { var parentGenericTypeArguments = t.GetGenericArguments(); // Only create nested types that are closed generic types (full generic instance types) // It happens if for example the parent class is `class MClass<T> { class MyNestedGeneric<T1> {} }` // In that case, MyNestedGeneric<T1> is opened in the context of MClass<int>, so we don't process them if (nestedType.GetGenericArguments().Length == parentGenericTypeArguments.Length) { try { var instanceNestedType = nestedType.MakeGenericType(parentGenericTypeArguments); types.Add(instanceNestedType); } catch (Exception ex) { var error = $"Unexpected Burst Inspector error. Invalid generic type instance. Trying to instantiate the generic type {nestedType.FullName} with the generic arguments <{string.Join(", ", parentGenericTypeArguments.Select(x => x.FullName))}> is not supported: {ex}"; logMessages.Add(new LogMessage(LogType.Warning, error)); } } } else { types.Add(nestedType); } } } } foreach (var t in types) { // If the type has been already visited, don't try to visit it if (!typesVisited.Add(t.FullName) || (t.IsGenericTypeDefinition && !t.IsInterface)) { continue; } try { // collect methods with types having a [BurstCompile] attribute bool visitStaticMethods = HasBurstCompileAttribute(t); bool isValueType = false; if (t.IsInterface) { object[] attrs = t.GetCustomAttributes(typeof(JobProducerTypeAttribute), false); if (attrs.Length == 0) { continue; } JobProducerTypeAttribute attr = (JobProducerTypeAttribute)attrs[0]; interfaceToProducer.Add(t, attr.ProducerType); //Debug.Log($"{t} has producer {attr.ProducerType}"); } else if (t.IsValueType) { // NOTE: Make sure that we don't use a value type generic definition (e.g `class Outer<T> { struct Inner { } }`) // We are only working on plain type or generic type instance! if (!t.IsGenericTypeDefinition) { isValueType = true; } } if (isValueType || visitStaticMethods) { valueTypes.Add(new TypeToVisit(t, visitStaticMethods)); } } catch (Exception ex) { logMessages.Add(new LogMessage(LogType.Warning, "Unexpected exception while inspecting type `" + t + "` IsConstructedGenericType: " + t.IsConstructedGenericType + " IsGenericTypeDef: " + t.IsGenericTypeDefinition + " IsGenericParam: " + t.IsGenericParameter + " Exception: " + ex)); } } } //Debug.Log($"Mapped {interfaceToProducer.Count} producers; {valueTypes.Count} value types"); // Revisit all types to find things that are compilable using the above producers. foreach (var typePair in valueTypes) { var type = typePair.Type; // collect static [BurstCompile] methods if (typePair.CollectStaticMethods) { try { var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (var method in methods) { if (!method.ContainsGenericParameters && HasBurstCompileAttribute(method)) { var target = new BurstCompileTarget(method, type, null, true); methodsToCompile.Add(target); } } } catch (Exception ex) { logMessages.Add(new LogMessage(ex)); } } // If the type is not a value type, we don't need to proceed with struct Jobs if (!type.IsValueType) { continue; } // Otherwise try to find if we have an interface producer setup on the class foreach (var interfaceType in type.GetInterfaces()) { var genericLessInterface = interfaceType; if (interfaceType.IsGenericType) { genericLessInterface = interfaceType.GetGenericTypeDefinition(); } Type foundProducer; if (interfaceToProducer.TryGetValue(genericLessInterface, out foundProducer)) { var genericParams = new List <Type> { type }; if (interfaceType.IsGenericType) { genericParams.AddRange(interfaceType.GenericTypeArguments); } try { var executeType = foundProducer.MakeGenericType(genericParams.ToArray()); var executeMethod = executeType.GetMethod("Execute", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); if (executeMethod == null) { throw new InvalidOperationException($"Burst reflection error. The type `{executeType}` does not contain an `Execute` method"); } // We will not try to record more than once a method in the methods to compile // This can happen if a job interface is inheriting from another job interface which are using in the end the same // job producer type if (methodsToCompileSet.Add(executeMethod)) { var target = new BurstCompileTarget(executeMethod, type, interfaceType, false); methodsToCompile.Add(target); } } catch (Exception ex) { logMessages.Add(new LogMessage(ex)); } } } } return(new FindExecuteMethodsResult(methodsToCompile, logMessages)); }
// The TypeCache API was added in 2019.2. So there are two versions of FindExecuteMethods, // one that uses TypeCache and one that doesn't. #if UNITY_2019_2_OR_NEWER public static FindExecuteMethodsResult FindExecuteMethods(List <System.Reflection.Assembly> assemblyList, BurstReflectionAssemblyOptions options) { var methodsToCompile = new List <BurstCompileTarget>(); var methodsToCompileSet = new HashSet <MethodInfo>(); var logMessages = new List <LogMessage>(); var interfaceToProducer = new Dictionary <Type, Type>(); var assemblySet = new HashSet <System.Reflection.Assembly>(assemblyList); void AddTarget(BurstCompileTarget target) { // We will not try to record more than once a method in the methods to compile // This can happen if a job interface is inheriting from another job interface which are using in the end the same // job producer type if (!target.IsStaticMethod && !methodsToCompileSet.Add(target.Method)) { return; } if (options.HasFlag(BurstReflectionAssemblyOptions.ExcludeTestAssemblies) && target.JobType.Assembly.GetReferencedAssemblies().Any(x => IsNUnitDll(x.Name))) { return; } methodsToCompile.Add(target); } var staticMethodTypes = new HashSet <Type>(); // ------------------------------------------- // Find job structs using TypeCache. // ------------------------------------------- var jobProducerImplementations = TypeCache.GetTypesWithAttribute <JobProducerTypeAttribute>(); foreach (var jobProducerImplementation in jobProducerImplementations) { var attrs = jobProducerImplementation.GetCustomAttributes(typeof(JobProducerTypeAttribute), false); if (attrs.Length == 0) { continue; } staticMethodTypes.Add(jobProducerImplementation); var attr = (JobProducerTypeAttribute)attrs[0]; interfaceToProducer.Add(jobProducerImplementation, attr.ProducerType); } foreach (var jobProducerImplementation in jobProducerImplementations) { if (!jobProducerImplementation.IsInterface) { continue; } var jobTypes = TypeCache.GetTypesDerivedFrom(jobProducerImplementation); foreach (var jobType in jobTypes) { if (jobType.IsGenericType || !jobType.IsValueType) { continue; } ScanJobType(jobType, interfaceToProducer, logMessages, AddTarget); } } // ------------------------------------------- // Find static methods using TypeCache. // ------------------------------------------- void AddStaticMethods(TypeCache.MethodCollection methods) { foreach (var method in methods) { if (HasBurstCompileAttribute(method.DeclaringType)) { staticMethodTypes.Add(method.DeclaringType); // NOTE: Make sure that we don't use a value type generic definition (e.g `class Outer<T> { struct Inner { } }`) // We are only working on plain type or generic type instance! if (!method.DeclaringType.IsGenericTypeDefinition && method.IsStatic && !method.ContainsGenericParameters) { AddTarget(new BurstCompileTarget(method, method.DeclaringType, null, true)); } } } } // Add [BurstCompile] static methods. AddStaticMethods(TypeCache.GetMethodsWithAttribute <BurstCompileAttribute>()); // Add [TestCompiler] static methods. if (!options.HasFlag(BurstReflectionAssemblyOptions.ExcludeTestAssemblies)) { var testCompilerAttributeType = Type.GetType("Burst.Compiler.IL.Tests.TestCompilerAttribute, Unity.Burst.Tests.UnitTests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); if (testCompilerAttributeType != null) { AddStaticMethods(TypeCache.GetMethodsWithAttribute(testCompilerAttributeType)); } } // ------------------------------------------- // Find job types and static methods based on // generic instances types. These will not be // found by the TypeCache scanning above. // ------------------------------------------- FindExecuteMethodsForGenericInstances( assemblySet, staticMethodTypes, interfaceToProducer, AddTarget, logMessages); return(new FindExecuteMethodsResult(methodsToCompile, logMessages)); }