first commit

This commit is contained in:
lethanhsonvsp
2025-11-17 15:16:36 +07:00
commit a40d0921eb
17012 changed files with 2652386 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
#if UNITY_EDITOR
using System;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace Unity.Burst.Editor
{
/// <summary>
/// Allow disabling of the Burst compiler for specific assemblies.
/// ProjectSettings/Burst_DisableAssembliesForEditorCompilation.json
/// ProjectSettings/Burst_DisableAssembliesForPlayerCompilation.json
/// ProjectSettings/Burst_DisableAssembliesForPlayerCompilation_{platform}.json // if exists taken in preference to the one immediately above
/// </summary>
internal static class BurstAssemblyDisable
{
public enum DisableType
{
Editor,
Player
}
private static string GetPath(DisableType type, string platformIdentifier)
{
if (DisableType.Editor == type)
{
return "ProjectSettings/Burst_DisableAssembliesForEditorCompilation.json";
}
var platformSpecicPath = $"ProjectSettings/Burst_DisableAssembliesForPlayerCompilation_{platformIdentifier}.json";
if (File.Exists(platformSpecicPath))
{
return platformSpecicPath;
}
return "ProjectSettings/Burst_DisableAssembliesForPlayerCompilation.json";
}
public static string[] GetDisabledAssemblies(DisableType type, string platformIdentifier)
{
var pathForSettings = GetPath(type, platformIdentifier);
if (!File.Exists(pathForSettings))
{
return Array.Empty<string>();
}
var settings = new BackwardsCompatWrapper();
JsonUtility.FromJsonOverwrite(File.ReadAllText(pathForSettings),settings);
if (settings == null || settings.MonoBehaviour == null || settings.MonoBehaviour.DisabledAssemblies == null)
{
return Array.Empty<string>();
}
return settings.MonoBehaviour.DisabledAssemblies;
}
}
/// <summary>
/// Settings file -
///
///{
/// "MonoBehaviour": {
/// "DisabledAssemblies":
/// [
/// "Example.Assembly"
/// ]
/// }
///}
/// </summary>
[Serializable]
class BackwardsCompatWrapper
{
public BurstDisableSettings MonoBehaviour;
}
[Serializable]
class BurstDisableSettings
{
public string[] DisabledAssemblies;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 24e5242d7f213ce49a52eefb5d544d24
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,183 @@
#if UNITY_EDITOR
using System;
using System.Diagnostics;
using System.Reflection;
using System.Text;
namespace Unity.Burst.Editor
{
[DebuggerDisplay("{GetDisplayName(),nq}")]
internal class BurstCompileTarget
{
public BurstCompileTarget(MethodInfo method, Type jobType, Type interfaceType, bool isStaticMethod)
{
Method = method ?? throw new ArgumentNullException(nameof(method));
JobType = jobType ?? throw new ArgumentNullException(nameof(jobType));
JobInterfaceType = interfaceType; // can be null
// This is important to clone the options as we don't want to modify the global instance
Options = BurstCompiler.Options.Clone();
Options.EnableBurstCompilation = true;
// Enable safety checks by default to match inspector default behavior
Options.EnableBurstSafetyChecks = true;
TargetCpu = BurstTargetCpu.Auto;
// The BurstCompilerAttribute can be either on the type or on the method
IsStaticMethod = isStaticMethod;
}
/// <summary>
/// <c>true</c> if the <see cref="Method"/> is directly tagged with a [BurstCompile] attribute
/// </summary>
public readonly bool IsStaticMethod;
/// <summary>
/// The Execute method of the target's producer type.
/// </summary>
public readonly MethodInfo Method;
/// <summary>
/// The type of the actual job (i.e. BoidsSimulationJob).
/// </summary>
public readonly Type JobType;
/// <summary>
/// The interface of the job (IJob, IJobParallelFor...)
/// </summary>
public readonly Type JobInterfaceType;
/// <summary>
/// The default compiler options
/// </summary>
public readonly BurstCompilerOptions Options;
public BurstTargetCpu TargetCpu { get; set; }
/// <summary>
/// Set to true if burst compilation is actually requested via proper `[BurstCompile]` attribute:
/// - On the job if it is a job only
/// - On the method and parent class it if is a static method
/// </summary>
public bool HasRequiredBurstCompileAttributes => BurstCompilerOptions.HasBurstCompileAttribute(JobType) && (!IsStaticMethod || BurstCompilerOptions.HasBurstCompileAttribute(Method));
/// <summary>
/// Generated raw disassembly (IR, IL, ASM...), or null if disassembly failed (only valid for the current TargetCpu)
/// </summary>
public string RawDisassembly;
/// <summary>
/// Formatted disassembly for the associated <see cref="RawDisassembly"/>, currently only valid for <see cref="Unity.Burst.Editor.DisassemblyKind.Asm"/>
/// </summary>
public string FormattedDisassembly;
public DisassemblyKind DisassemblyKind;
public bool IsDarkMode { get; set; }
public bool IsBurstError { get; set; }
public bool IsLoading = false;
public bool JustLoaded = false;
public string GetDisplayName()
{
var displayName = IsStaticMethod ? Pretty(Method) : $"{Pretty(JobType)} - ({Pretty(JobInterfaceType)})";
// Remove the '<>c__DisplayClass_' part of the name - this is only added for C# Entities.ForEach jobs to trick the C# debugging tools into
// treating them like lambdas. This is removed wherever possible from user facing tools (like the Unity profiler), so we should do the same.
return displayName.Replace("<>c__DisplayClass_", "");
}
private static string Pretty(MethodInfo method)
{
var builder = new StringBuilder();
builder.Append(Pretty(method.DeclaringType));
builder.Append(".");
builder.Append(method.Name);
builder.Append("(");
var parameters = method.GetParameters();
for (var i = 0; i < parameters.Length; i++)
{
var param = parameters[i];
if (i > 0) builder.Append(", ");
builder.Append(Pretty(param.ParameterType));
}
builder.Append(")");
return builder.ToString();
}
internal static string Pretty(Type type)
{
if (type == typeof(bool))
{
return "bool";
}
if (type == typeof(int))
{
return "int";
}
if (type == typeof(long))
{
return "long";
}
if (type == typeof(uint))
{
return "uint";
}
if (type == typeof(ulong))
{
return "ulong";
}
if (type == typeof(short))
{
return "short";
}
if (type == typeof(ushort))
{
return "ushort";
}
if (type == typeof(byte))
{
return "byte";
}
if (type == typeof(sbyte))
{
return "sbyte";
}
if (type == typeof(float))
{
return "float";
}
if (type == typeof(double))
{
return "double";
}
if (type == typeof(string))
{
return "string";
}
if (type == typeof(object))
{
return "object";
}
if (type == typeof(char))
{
return "char";
}
// When displaying job interface type, display the interface name of Unity.Jobs namespace
var typeName = type.IsInterface && type.Name.StartsWith("IJob") ? type.Name : type.ToString();
return typeName.Replace("+", ".");
}
}
internal enum DisassemblyKind
{
Asm = 0,
IL = 1,
UnoptimizedIR = 2,
OptimizedIR = 3,
IRPassAnalysis = 4
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8e0e4e7742393f00808df3e4508a7327
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,124 @@
#if UNITY_EDITOR
using System.Diagnostics;
using UnityEditor;
namespace Unity.Burst.Editor
{
/// <summary>
/// Responsible to synchronize <see cref="BurstCompiler.Options"/> with the menu
/// </summary>
internal static class BurstEditorOptions
{
// Properties stored in SessionState (survive between domain reloads, but stays alive only during the life of the editor)
private const string EnableBurstSafetyChecksText = "BurstSafetyChecks";
// Properties stored in EditorPrefs (survive between editor restart)
private const string EnableBurstCompilationText = "BurstCompilation";
private const string EnableBurstTimingsText = "BurstShowTimings";
private const string EnableBurstCompileSynchronouslyText = "BurstCompileSynchronously";
private const string EnableBurstDebugText = "BurstDebug";
private const string ForceEnableBurstSafetyChecksText = "BurstForceSafetyChecks";
/// <summary>
/// <c>true</c> if the menu options are synchronized with <see cref="BurstCompiler.Options"/>
/// </summary>
private static bool _isSynchronized;
public static void EnsureSynchronized()
{
GetGlobalOptions();
}
public static bool EnableBurstCompilation
{
get => GetGlobalOptions().EnableBurstCompilation;
set
{
var enabled = GetGlobalOptions().EnableBurstCompilation;
GetGlobalOptions().EnableBurstCompilation = value;
if (!enabled && value)
{
// Must be called AFTER we actually enable compilation
BurstCompiler.TriggerUnsafeStaticMethodRecompilation();
}
}
}
public static bool EnableBurstSafetyChecks
{
get => GetGlobalOptions().EnableBurstSafetyChecks;
set => GetGlobalOptions().EnableBurstSafetyChecks = value;
}
public static bool EnableBurstCompileSynchronously
{
get => GetGlobalOptions().EnableBurstCompileSynchronously;
set => GetGlobalOptions().EnableBurstCompileSynchronously = value;
}
public static bool EnableBurstTimings
{
get => GetGlobalOptions().EnableBurstTimings;
set => GetGlobalOptions().EnableBurstTimings = value;
}
public static bool EnableBurstDebug
{
get => GetGlobalOptions().EnableBurstDebug;
set => GetGlobalOptions().EnableBurstDebug = value;
}
public static bool ForceEnableBurstSafetyChecks
{
get => GetGlobalOptions().ForceEnableBurstSafetyChecks;
set => GetGlobalOptions().ForceEnableBurstSafetyChecks = value;
}
private static BurstCompilerOptions GetGlobalOptions()
{
var global = BurstCompiler.Options;
// If options are not synchronize with our global instance, setup the sync
if (!_isSynchronized)
{
global.IsInitializing = true;
try
{
// Setup the synchronization
global.EnableBurstCompilation = EditorPrefs.GetBool(EnableBurstCompilationText, true);
global.EnableBurstCompileSynchronously = EditorPrefs.GetBool(EnableBurstCompileSynchronouslyText, false);
global.EnableBurstTimings = EditorPrefs.GetBool(EnableBurstTimingsText, false);
global.EnableBurstDebug = EditorPrefs.GetBool(EnableBurstDebugText, false);
global.ForceEnableBurstSafetyChecks = EditorPrefs.GetBool(ForceEnableBurstSafetyChecksText, false);
// Session only properties
global.EnableBurstSafetyChecks = SessionState.GetBool(EnableBurstSafetyChecksText, true);
}
finally
{
global.IsInitializing = false;
}
global.OptionsChanged += GlobalOnOptionsChanged;
_isSynchronized = true;
}
return global;
}
private static void GlobalOnOptionsChanged()
{
var global = BurstCompiler.Options;
// We are not optimizing anything here, so whenever one option is set, we reset all of them
EditorPrefs.SetBool(EnableBurstCompilationText, global.EnableBurstCompilation);
EditorPrefs.SetBool(EnableBurstCompileSynchronouslyText, global.EnableBurstCompileSynchronously);
EditorPrefs.SetBool(EnableBurstTimingsText, global.EnableBurstTimings);
EditorPrefs.SetBool(EnableBurstDebugText, global.EnableBurstDebug);
EditorPrefs.SetBool(ForceEnableBurstSafetyChecksText, global.ForceEnableBurstSafetyChecks);
// Session only properties
SessionState.SetBool(EnableBurstSafetyChecksText, global.EnableBurstSafetyChecks);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d81d96b8ea1936359196f069c6e225d8
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,770 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Unity.Burst.LowLevel;
using Unity.Profiling;
using Unity.Profiling.LowLevel;
using Unity.Profiling.LowLevel.Unsafe;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEditor.Scripting.ScriptCompilation;
using UnityEngine;
namespace Unity.Burst.Editor
{
/// <summary>
/// Main entry point for initializing the burst compiler service for both JIT and AOT
/// </summary>
[InitializeOnLoad]
internal class BurstLoader
{
private const int BURST_PROTOCOL_VERSION = 1;
// Cache the delegate to make sure it doesn't get collected.
private static readonly BurstCompilerService.ExtractCompilerFlags TryGetOptionsFromMemberDelegate = TryGetOptionsFromMember;
/// <summary>
/// Gets the location to the runtime path of burst.
/// </summary>
public static string RuntimePath { get; private set; }
public static BclConfiguration BclConfiguration { get; private set; }
public static bool IsDebugging { get; private set; }
public static bool SafeShutdown { get; private set; }
public static int ProtocolVersion { get; private set; }
private static void VersionUpdateCheck()
{
var seek = "com.unity.burst@";
var first = RuntimePath.LastIndexOf(seek);
var last = RuntimePath.LastIndexOf(".Runtime");
string version;
if (first == -1 || last == -1 || last <= first)
{
version = "Unknown";
}
else
{
first += seek.Length;
last -= 1;
version = RuntimePath.Substring(first, last - first);
}
var result = BurstCompiler.VersionNotify(version);
// result will be empty if we are shutting down, and thus we shouldn't popup a dialog
if (!String.IsNullOrEmpty(result) && result != version)
{
if (IsDebugging)
{
UnityEngine.Debug.LogWarning($"[com.unity.burst] - '{result}' != '{version}'");
}
OnVersionChangeDetected();
}
}
private static bool UnityBurstRuntimePathOverwritten(out string path)
{
path = Environment.GetEnvironmentVariable("UNITY_BURST_RUNTIME_PATH");
return Directory.Exists(path);
}
private static void OnVersionChangeDetected()
{
// Write marker file to tell Burst to delete the cache at next startup.
try
{
File.Create(Path.Combine(BurstCompilerOptions.DefaultCacheFolder, BurstCompilerOptions.DeleteCacheMarkerFileName)).Dispose();
}
catch (IOException)
{
// In the unlikely scenario that two processes are creating this marker file at the same time,
// and one of them fails, do nothing because the other one has hopefully succeeded.
}
// Skip checking if we are using an explicit runtime path.
if (!UnityBurstRuntimePathOverwritten(out var _))
{
EditorUtility.DisplayDialog("Burst Package Update Detected", "The version of Burst used by your project has changed. Please restart the Editor to continue.", "OK");
BurstCompiler.Shutdown();
}
}
private static CompilationTaskReason _currentBuildKind;
static BurstLoader()
{
if (BurstCompilerOptions.ForceDisableBurstCompilation)
{
if (!BurstCompilerOptions.IsSecondaryUnityProcess)
{
UnityEngine.Debug.LogWarning("[com.unity.burst] Burst is disabled entirely from the command line or environment variable");
}
return;
}
// This can be setup to get more diagnostics
var debuggingStr = Environment.GetEnvironmentVariable("UNITY_BURST_DEBUG");
IsDebugging = debuggingStr != null && int.TryParse(debuggingStr, out var debugLevel) && debugLevel > 0;
if (IsDebugging)
{
UnityEngine.Debug.LogWarning("[com.unity.burst] Extra debugging is turned on.");
}
// Try to load the runtime through an environment variable
var isRuntimePathOverwritten = UnityBurstRuntimePathOverwritten(out var path);
if (!isRuntimePathOverwritten)
{
// Otherwise try to load it from the package itself
#if UNITY_2021_3_OR_NEWER
path = FileUtil.GetPhysicalPath("Packages/com.unity.burst/.Runtime");
#else
path = Path.GetFullPath("Packages/com.unity.burst/.Runtime");
#endif
}
RuntimePath = path;
BclConfiguration = GetBclConfiguration(path, isRuntimePathOverwritten);
if (IsDebugging)
{
UnityEngine.Debug.LogWarning($"[com.unity.burst] Runtime directory set to {RuntimePath}");
}
BurstCompilerService.Initialize(RuntimePath, TryGetOptionsFromMemberDelegate);
ProtocolVersion = BurstCompiler.RequestSetProtocolVersion(BURST_PROTOCOL_VERSION);
BurstCompiler.Initialize(GetAssemblyFolders(),BurstAssemblyDisable.GetDisabledAssemblies(BurstAssemblyDisable.DisableType.Editor, ""));
// It's important that this call comes *after* BurstCompilerService.Initialize,
// otherwise any calls from within EnsureSynchronized to BurstCompilerService,
// such as BurstCompiler.Disable(), will silently fail.
BurstEditorOptions.EnsureSynchronized();
EditorApplication.quitting += OnEditorApplicationQuitting;
CompilationPipeline.compilationStarted += OnCompilationStarted;
CompilationPipeline.compilationFinished += OnCompilationFinished;
// We use this internal event because it's the only way to get access to the ScriptAssembly.HasCompileErrors,
// which tells us whether C# compilation succeeded or failed for this assembly.
EditorCompilationInterface.Instance.assemblyCompilationFinished += OnAssemblyCompilationFinished;
#if UNITY_2022_2_OR_NEWER
CompilationPipeline.assemblyCompilationNotRequired += OnAssemblyCompilationNotRequired;
#endif
EditorApplication.playModeStateChanged += EditorApplicationOnPlayModeStateChanged;
AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
SafeShutdown = false;
UnityEditor.PackageManager.Events.registeringPackages += PackageRegistrationEvent;
SafeShutdown = BurstCompiler.IsApiAvailable("SafeShutdown");
if (!SafeShutdown)
{
VersionUpdateCheck();
}
// Notify the compiler about a domain reload
if (IsDebugging)
{
UnityEngine.Debug.Log("Burst - Domain Reload");
}
BurstCompiler.OnProgress += OnProgress;
BurstCompiler.EagerCompilationLoggingEnabled = true;
// Make sure BurstRuntime is initialized. This needs to happen before BurstCompiler.DomainReload,
// because that can cause calls to BurstRuntime.Log.
BurstRuntime.Initialize();
// Notify the JitCompilerService about a domain reload
BurstCompiler.SetDefaultOptions();
BurstCompiler.DomainReload();
BurstCompiler.OnProfileBegin += OnProfileBegin;
BurstCompiler.OnProfileEnd += OnProfileEnd;
BurstCompiler.SetProfilerCallbacks();
BurstCompiler.InitialiseDebuggerHooks();
}
private static bool _isQuitting;
private static void OnEditorApplicationQuitting()
{
_isQuitting = true;
}
public static Action OnBurstShutdown;
private static void PackageRegistrationEvent(UnityEditor.PackageManager.PackageRegistrationEventArgs obj)
{
bool requireCleanup = false;
if (SafeShutdown)
{
foreach (var changed in obj.changedFrom)
{
if (changed.name.Contains("com.unity.burst"))
{
requireCleanup = true;
break;
}
}
}
foreach (var removed in obj.removed)
{
if (removed.name.Contains("com.unity.burst"))
{
requireCleanup = true;
}
}
if (requireCleanup)
{
OnBurstShutdown?.Invoke();
if (!SafeShutdown)
{
EditorUtility.DisplayDialog("Burst Package Has Been Removed", "Please restart the Editor to continue.", "OK");
}
BurstCompiler.Shutdown();
}
}
private static BclConfiguration GetBclConfiguration(string runtimePath, bool isRuntimePathOverwritten)
{
string bclFolderPath;
if (isRuntimePathOverwritten)
{
return new BclConfiguration
{
FolderPath = runtimePath,
ExecutablePath = Path.Combine(runtimePath, "bcl.exe"),
IsExecutableNative = false,
};
}
else
{
bclFolderPath = Path.Combine(runtimePath, "bcl", GetBclPlatformFolderName());
if (Directory.Exists(bclFolderPath))
{
var bclFileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "bcl.exe"
: "bcl";
return new BclConfiguration
{
FolderPath = bclFolderPath,
ExecutablePath = Path.Combine(bclFolderPath, bclFileName),
IsExecutableNative = true,
};
}
return new BclConfiguration
{
FolderPath = runtimePath,
ExecutablePath = Path.Combine(runtimePath, "bcl.exe"),
IsExecutableNative = false,
};
}
}
private static string GetBclPlatformFolderName()
{
var hostPlatform = Application.platform;
var hostArchitecture = RuntimeInformation.OSArchitecture;
switch (hostPlatform)
{
case RuntimePlatform.WindowsEditor:
return "win-x64";
case RuntimePlatform.OSXEditor when hostArchitecture == Architecture.X64:
return "osx-x64";
case RuntimePlatform.OSXEditor when hostArchitecture == Architecture.Arm64:
return "osx-arm64";
case RuntimePlatform.LinuxEditor:
return "linux-x64";
default:
throw new InvalidOperationException($"Current OS platform {hostPlatform} and architecture {hostArchitecture} combination is not supported");
}
}
// Don't initialize to 0 because that could be a valid progress ID.
private static int BurstProgressId = -1;
// If this enum changes, update the benchmarks tool accordingly as we rely on integer value related to this enum
internal enum BurstEagerCompilationStatus
{
NotScheduled,
Scheduled,
Completed
}
// For the time being, this field is only read through reflection
internal static BurstEagerCompilationStatus EagerCompilationStatus;
private static void OnProgress(int current, int total)
{
if (current == total)
{
EagerCompilationStatus = BurstEagerCompilationStatus.Completed;
}
// OnProgress is called from a background thread,
// but we need to update the progress UI on the main thread.
EditorApplication.CallDelayed(() =>
{
if (current == total)
{
// We've finished - remove progress bar.
if (Progress.Exists(BurstProgressId))
{
Progress.Remove(BurstProgressId);
BurstProgressId = -1;
}
}
else
{
// Do we need to create the progress bar?
if (!Progress.Exists(BurstProgressId))
{
BurstProgressId = Progress.Start(
"Burst",
"Compiling...",
Progress.Options.Unmanaged);
}
Progress.Report(
BurstProgressId,
current / (float)total,
$"Compiled {current} / {total} libraries");
}
});
}
[ThreadStatic]
private static Dictionary<string, IntPtr> ProfilerMarkers;
private static unsafe void OnProfileBegin(string markerName, string metadataName, string metadataValue)
{
if (ProfilerMarkers == null)
{
// Initialize thread-static dictionary.
ProfilerMarkers = new Dictionary<string, IntPtr>();
}
if (!ProfilerMarkers.TryGetValue(markerName, out var markerPtr))
{
ProfilerMarkers.Add(markerName, markerPtr = ProfilerUnsafeUtility.CreateMarker(
markerName,
ProfilerUnsafeUtility.CategoryScripts,
MarkerFlags.Script,
metadataName != null ? 1 : 0));
// metadataName is assumed to be consistent for a given markerName.
if (metadataName != null)
{
ProfilerUnsafeUtility.SetMarkerMetadata(
markerPtr,
0,
metadataName,
(byte)ProfilerMarkerDataType.String16,
(byte)ProfilerMarkerDataUnit.Undefined);
}
}
if (metadataName != null && metadataValue != null)
{
fixed (char* methodNamePtr = metadataValue)
{
var metadata = new ProfilerMarkerData
{
Type = (byte)ProfilerMarkerDataType.String16,
Size = ((uint)metadataValue.Length + 1) * 2,
Ptr = methodNamePtr
};
ProfilerUnsafeUtility.BeginSampleWithMetadata(markerPtr, 1, &metadata);
}
}
else
{
ProfilerUnsafeUtility.BeginSample(markerPtr);
}
}
private static void OnProfileEnd(string markerName)
{
if (ProfilerMarkers == null)
{
// If we got here it means we had a domain reload between when we called profile begin and
// now profile end, and so we need to bail out.
return;
}
if (!ProfilerMarkers.TryGetValue(markerName, out var markerPtr))
{
return;
}
ProfilerUnsafeUtility.EndSample(markerPtr);
}
private static void EditorApplicationOnPlayModeStateChanged(PlayModeStateChange state)
{
if (IsDebugging)
{
UnityEngine.Debug.Log($"Burst - Change of Editor State: {state}");
}
switch (state)
{
case PlayModeStateChange.ExitingPlayMode:
// Cleanup any loaded burst natives so users have a clean point to update the libraries.
BurstCompiler.UnloadAdditionalLibraries();
break;
}
}
enum CompilationTaskReason
{
IsForEditor, // Compilation should proceed as its for an editor build
IsForPlayer, // Skip this compilation
IsForPreviousScriptingMode, // We are about to enter a domain reload, don't start any new compilations
IsForAssemblyBuilder, // Request is coming from an 'AssemblyBuilder' and should be skipped as not supported
}
static CompilationTaskReason CurrentCompilationTaskShouldStart()
{
try
{
if (BurstCompiler.WasScriptDebugInfoEnabledAtDomainReload != UnityEditor.Compilation.CompilationPipeline.IsScriptDebugInfoEnabled())
{
// If the scripting compilation mode has changed since we last had our domain reloaded, then we ignore all requests, and act as if
//loading for the first time. This is to avoid having compilations kick off right before a Shutdown triggered by domain reload, that
//would cause the a significant stall as we had to wait for those compilations to finish, thus blocking the main thread.
return CompilationTaskReason.IsForPreviousScriptingMode;
}
var inst = EditorCompilationInterface.Instance;
#if UNITY_2021_1_OR_NEWER
var editorCompilationType = inst.GetType();
var activeBeeBuildField = editorCompilationType.GetField("_currentBeeScriptCompilationState", BindingFlags.Instance | BindingFlags.NonPublic);
if (activeBeeBuildField == null)
{
activeBeeBuildField = editorCompilationType.GetField("activeBeeBuild", BindingFlags.Instance | BindingFlags.NonPublic);
}
var activeBeeBuild = activeBeeBuildField.GetValue(inst);
// If a user is doing an `AssemblyBuilder` compilation, we do not support that in Burst.
// This seems to manifest as a null `activeBeeBuild`, so we bail here if that happens.
if (activeBeeBuild == null)
{
return CompilationTaskReason.IsForAssemblyBuilder;
}
var settings = activeBeeBuild.GetType().GetProperty("settings", BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(activeBeeBuild);
var opt = (EditorScriptCompilationOptions)settings.GetType().GetProperty("CompilationOptions").GetValue(settings);
#else
var task = inst.GetType()
.GetField("compilationTask", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(inst);
// If a user is doing an `AssemblyBuilder` compilation, we do not support that in Burst.
// This seems to manifest as a null `task`, so we bail here if that happens.
if (task == null)
{
return CompilationTaskReason.IsForAssemblyBuilder;
}
var opt = (EditorScriptCompilationOptions)task.GetType()
.GetField("options", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(task);
#endif
#if UNITY_2022_2_OR_NEWER
if ((opt & EditorScriptCompilationOptions.BuildingSkipCompile) != 0)
{
return CompilationTaskReason.IsForPlayer;
}
#endif
if ((opt & EditorScriptCompilationOptions.BuildingForEditor) != 0)
{
return CompilationTaskReason.IsForEditor;
}
return CompilationTaskReason.IsForPlayer;
}
catch (Exception ex)
{
UnityEngine.Debug.LogWarning("Burst - Unknown private compilation pipeline API\nAssuming editor build\n" + ex.ToString());
return CompilationTaskReason.IsForEditor;
}
}
private static void OnCompilationStarted(object value)
{
_currentBuildKind = CurrentCompilationTaskShouldStart();
if (_currentBuildKind != CompilationTaskReason.IsForEditor)
{
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - not handling '{value}' because '{_currentBuildKind}'");
}
return;
}
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - compilation started for '{value}'");
}
BurstCompiler.NotifyCompilationStarted(GetAssemblyFolders(),
BurstAssemblyDisable.GetDisabledAssemblies(BurstAssemblyDisable.DisableType.Editor,"") );
}
private static string[] GetAssemblyFolders()
{
var assemblyFolders = new HashSet<string>();
// First, we get the path to Mono system libraries. This will be something like
// <EditorPath>/Data/MonoBleedingEdge/lib/mono/unityjit-win32
//
// You might think we could use MonoLibraryHelpers.GetSystemReferenceDirectories
// here, but we can't, because that returns the _reference assembly_ directories,
// not the actual implementation assembly directory.
var systemLibraryDirectory = Path.GetDirectoryName(typeof(object).Assembly.Location);
assemblyFolders.Add(systemLibraryDirectory);
// Also add the Facades directory, since that contains netstandard. Without this,
// we'll potentially resolve the "wrong" netstandard from a dotnet compiler host.
assemblyFolders.Add(Path.Combine(systemLibraryDirectory, "Facades"));
// Now add the default assembly search paths.
// This will include
// - Unity dlls in <EditorPath>/Data/Managed and <EditorPath>/Data/Managed/UnityEngine
// - Platform support dlls e.g. <EditorPath>/Data/PlaybackEngines/WindowsStandaloneSupport
// - Package paths. These are interesting because they are "virtual" paths, of the form
// Packages/<MyPackageName>. They need to be resolved to physical paths.
// - Library/ScriptAssemblies. This needs to be resolved to the full path.
var defaultAssemblySearchPaths = AssemblyHelper.GetDefaultAssemblySearchPaths();
#if UNITY_2021_3_OR_NEWER
foreach (var searchPath in defaultAssemblySearchPaths)
{
var resolvedPath = FileUtil.PathToAbsolutePath(searchPath);
if (!string.IsNullOrEmpty(resolvedPath))
{
assemblyFolders.Add(resolvedPath);
}
}
#else
var packagesLookup = GetPackagesLookup();
foreach (var searchPath in defaultAssemblySearchPaths)
{
if (TryResolvePath(searchPath, packagesLookup, out var resolvedPath))
{
assemblyFolders.Add(resolvedPath);
}
}
#endif
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - AssemblyFolders : \n{string.Join("\n", assemblyFolders)}");
}
return assemblyFolders.ToArray();
}
#if !UNITY_2021_3_OR_NEWER
private static Dictionary<string, string> GetPackagesLookup()
{
var packages = new Dictionary<string, string>();
// Fetch list of packages
#if UNITY_2021_1_OR_NEWER
var allPackages = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages();
#else
var allPackages = UnityEditor.PackageManager.PackageInfo.GetAll();
#endif
foreach (var p in allPackages)
{
packages.Add(p.name, p.resolvedPath);
}
return packages;
}
private const string PackagesPath = "Packages/";
private static readonly int PackagesPathLength = PackagesPath.Length;
private static bool TryResolvePath(string path, Dictionary<string, string> packagesLookup, out string resolvedPath)
{
if (string.IsNullOrEmpty(path))
{
resolvedPath = null;
return false;
}
else if (path.StartsWith("Packages/", StringComparison.InvariantCulture))
{
var secondSlashIndex = path.IndexOf('/', PackagesPathLength);
var packageName = secondSlashIndex > -1
? path.Substring(PackagesPathLength, secondSlashIndex - PackagesPathLength)
: path.Substring(PackagesPathLength);
if (packagesLookup.TryGetValue(packageName, out var resolvedPathTemp))
{
path = secondSlashIndex > -1
? Path.Combine(resolvedPathTemp, path.Substring(secondSlashIndex + 1))
: resolvedPathTemp;
}
else
{
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - unknown package path '{path}'");
}
resolvedPath = null;
return false;
}
}
resolvedPath = Path.GetFullPath(path);
return true;
}
#endif
private static void OnCompilationFinished(object value)
{
if (_currentBuildKind!=CompilationTaskReason.IsForEditor)
{
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - ignoring finished compilation '{value}' because it's '{_currentBuildKind}'");
}
_currentBuildKind = CompilationTaskReason.IsForEditor;
return;
}
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - compilation finished for '{value}'");
}
BurstCompiler.NotifyCompilationFinished();
}
#if UNITY_2021_1_OR_NEWER
private static void OnAssemblyCompilationFinished(ScriptAssembly assembly, CompilerMessage[] messages)
#else
private static void OnAssemblyCompilationFinished(ScriptAssembly assembly, CompilerMessage[] messages, EditorScriptCompilationOptions options)
#endif
{
if (_currentBuildKind!=CompilationTaskReason.IsForEditor)
{
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - ignoring '{assembly.Filename}' because it's '{_currentBuildKind}'");
}
return;
}
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - Assembly compilation finished for '{assembly.Filename}'");
}
if (assembly.HasCompileErrors)
{
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - ignoring '{assembly.Filename}' because it failed C# compilation");
}
return;
}
BurstCompiler.NotifyAssemblyCompilationFinished(Path.GetFileNameWithoutExtension(assembly.Filename), assembly.Defines);
}
private static void OnAssemblyCompilationNotRequired(string arg1)
{
if (_currentBuildKind!=CompilationTaskReason.IsForEditor)
{
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - ignoring '{arg1}' because it's '{_currentBuildKind}'");
}
return;
}
if (IsDebugging)
{
UnityEngine.Debug.Log($"{DateTime.UtcNow} Burst - Assembly compilation not required for '{arg1}'");
}
BurstCompiler.NotifyAssemblyCompilationNotRequired(Path.GetFileNameWithoutExtension(arg1));
}
private static bool TryGetOptionsFromMember(MemberInfo member, out string flagsOut)
{
return BurstCompiler.Options.TryGetOptions(member, out flagsOut);
}
private static void OnDomainUnload(object sender, EventArgs e)
{
if (IsDebugging)
{
UnityEngine.Debug.Log($"Burst - OnDomainUnload");
}
BurstCompiler.Cancel();
// This check here is to execute shutdown after all OnDisable's. EditorApplication.quitting event is called before OnDisable's, so we need to shutdown in here.
if (_isQuitting)
{
BurstCompiler.Shutdown();
}
// Because of a check in Unity (specifically SCRIPTINGAPI_THREAD_AND_SERIALIZATION_CHECK),
// we are not allowed to call thread-unsafe methods (like Progress.Exists) after the
// kApplicationTerminating bit has been set. And because the domain is unloaded
// (thus triggering AppDomain.DomainUnload) *after* that bit is set, we can't call Progress.Exists
// during shutdown. So we check _isQuitting here. When quitting, it's fine for the progress item
// not to be removed since it's all being torn down anyway.
if (!_isQuitting && Progress.Exists(BurstProgressId))
{
Progress.Remove(BurstProgressId);
BurstProgressId = -1;
}
}
}
internal class BclConfiguration
{
public string FolderPath { get; set; }
public string ExecutablePath { get; set; }
public bool IsExecutableNative { get; set; }
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aeb3ace3d48330a3b182da7ace6b5f65
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,644 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Unity.Jobs.LowLevel.Unsafe;
using UnityEditor;
using UnityEditor.Compilation;
using Debug = UnityEngine.Debug;
[assembly: InternalsVisibleTo("Unity.Burst.Editor.Tests")]
namespace Unity.Burst.Editor
{
using static BurstCompilerOptions;
internal static class BurstReflection
{
// The TypeCache API was added in 2019.2. So there are two versions of FindExecuteMethods,
// one that uses TypeCache and one that doesn't.
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)
{
if (target.Method.Name.EndsWith("$BurstManaged")) return;
// 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);
}
private static void ScanJobType(
Type jobType,
Dictionary<Type, Type> interfaceToProducer,
List<LogMessage> logMessages,
Action<BurstCompileTarget> addTarget)
{
foreach (var interfaceType in jobType.GetInterfaces())
{
var genericLessInterface = interfaceType;
if (interfaceType.IsGenericType)
{
genericLessInterface = interfaceType.GetGenericTypeDefinition();
}
if (interfaceToProducer.TryGetValue(genericLessInterface, out var foundProducer))
{
var genericParams = new List<Type> { jobType };
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");
}
addTarget(new BurstCompileTarget(executeMethod, jobType, interfaceType, false));
}
catch (Exception ex)
{
logMessages.Add(new LogMessage(ex));
}
}
}
}
private static void FindExecuteMethodsForGenericInstances(
HashSet<System.Reflection.Assembly> assemblyList,
HashSet<Type> staticMethodTypes,
Dictionary<Type, Type> interfaceToProducer,
Action<BurstCompileTarget> addTarget,
List<LogMessage> logMessages)
{
var valueTypes = new List<TypeToVisit>();
//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
{
// Collect all generic type instances (excluding indirect instances)
CollectGenericTypeInstances(
assembly,
x => assemblyList.Contains(x.Assembly),
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.AssemblyQualifiedName))
{
// 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.AssemblyQualifiedName) || (t.IsGenericTypeDefinition && !t.IsInterface))
{
continue;
}
try
{
// collect methods with types having a [BurstCompile] attribute
var staticMethodDeclaringType = t;
if (t.IsGenericType)
{
staticMethodDeclaringType = t.GetGenericTypeDefinition();
}
bool visitStaticMethods = staticMethodTypes.Contains(staticMethodDeclaringType);
bool isValueType = false;
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));
}
}
}
// 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 (HasBurstCompileAttribute(method))
{
addTarget(new BurstCompileTarget(method, type, null, true));
}
}
}
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;
}
ScanJobType(type, interfaceToProducer, logMessages, addTarget);
}
}
public sealed class FindExecuteMethodsResult
{
public readonly List<BurstCompileTarget> CompileTargets;
public readonly List<LogMessage> LogMessages;
public FindExecuteMethodsResult(List<BurstCompileTarget> compileTargets, List<LogMessage> logMessages)
{
CompileTargets = compileTargets;
LogMessages = logMessages;
}
}
public sealed class LogMessage
{
public readonly LogType LogType;
public readonly string Message;
public readonly Exception Exception;
public LogMessage(LogType logType, string message)
{
LogType = logType;
Message = message;
}
public LogMessage(Exception exception)
{
LogType = LogType.Exception;
Exception = exception;
}
}
public enum LogType
{
Warning,
Exception,
}
/// <summary>
/// This method exists solely to ensure that the static constructor has been called.
/// </summary>
public static void EnsureInitialized() { }
public static readonly List<System.Reflection.Assembly> EditorAssembliesThatCanPossiblyContainJobs;
public static readonly List<System.Reflection.Assembly> EditorAssembliesThatCanPossiblyContainJobsExcludingTestAssemblies;
/// <summary>
/// Collects (and caches) all editor assemblies - transitively.
/// </summary>
static BurstReflection()
{
EditorAssembliesThatCanPossiblyContainJobs = new List<System.Reflection.Assembly>();
EditorAssembliesThatCanPossiblyContainJobsExcludingTestAssemblies = new List<System.Reflection.Assembly>();
// TODO: Not sure there is a better way to match assemblies returned by CompilationPipeline.GetAssemblies
// with runtime assemblies contained in the AppDomain.CurrentDomain.GetAssemblies()
// Filter the assemblies
var assemblyList = CompilationPipeline.GetAssemblies(AssembliesType.Editor);
var assemblyNames = new HashSet<string>();
foreach (var assembly in assemblyList)
{
CollectAssemblyNames(assembly, assemblyNames);
}
var allAssemblies = new HashSet<System.Reflection.Assembly>();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (!assemblyNames.Contains(assembly.GetName().Name))
{
continue;
}
CollectAssembly(assembly, allAssemblies);
}
}
// For an assembly to contain something "interesting" when we're scanning for things to compile,
// it needs to either:
// (a) be one of these assemblies, or
// (b) reference one of these assemblies
private static readonly string[] ScanMarkerAssemblies = new[]
{
// Contains [BurstCompile] attribute
"Unity.Burst",
// Contains [JobProducerType] attribute
"UnityEngine.CoreModule"
};
private static void CollectAssembly(System.Reflection.Assembly assembly, HashSet<System.Reflection.Assembly> collect)
{
if (!collect.Add(assembly))
{
return;
}
var referencedAssemblies = assembly.GetReferencedAssemblies();
var shouldCollectReferences = false;
var name = assembly.GetName().Name;
if (ScanMarkerAssemblies.Contains(name) || referencedAssemblies.Any(x => ScanMarkerAssemblies.Contains(x.Name)))
{
EditorAssembliesThatCanPossiblyContainJobs.Add(assembly);
shouldCollectReferences = true;
if (!assembly.GetReferencedAssemblies().Any(x => IsNUnitDll(x.Name)))
{
EditorAssembliesThatCanPossiblyContainJobsExcludingTestAssemblies.Add(assembly);
}
}
if (!shouldCollectReferences)
{
return;
}
foreach (var assemblyName in referencedAssemblies)
{
try
{
CollectAssembly(System.Reflection.Assembly.Load(assemblyName), collect);
}
catch (Exception)
{
if (BurstLoader.IsDebugging)
{
Debug.LogWarning("Could not load assembly " + assemblyName);
}
}
}
}
private static bool IsNUnitDll(string value)
{
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(value, "nunit.framework") >= 0;
}
private static void CollectAssemblyNames(UnityEditor.Compilation.Assembly assembly, HashSet<string> collect)
{
if (assembly == null || assembly.name == null) return;
if (!collect.Add(assembly.name))
{
return;
}
foreach (var assemblyRef in assembly.assemblyReferences)
{
CollectAssemblyNames(assemblyRef, collect);
}
}
/// <summary>
/// Gets the list of concrete generic type instances used in an assembly.
/// See remarks
/// </summary>
/// <param name="assembly">The assembly</param>
/// <param name="types"></param>
/// <returns>The list of generic type instances</returns>
/// <remarks>
/// Note that this method fetchs only direct type instances but
/// cannot fetch transitive generic type instances.
/// </remarks>
private static void CollectGenericTypeInstances(
System.Reflection.Assembly assembly,
Func<Type, bool> typeFilter,
List<Type> types,
HashSet<Type> visited)
{
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// WARNING: THIS CODE HAS TO BE MAINTAINED IN SYNC WITH BclApp.cs
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// From: https://gist.github.com/xoofx/710aaf86e0e8c81649d1261b1ef9590e
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
const int mdMaxCount = 1 << 24;
foreach (var module in assembly.Modules)
{
for (int i = 1; i < mdMaxCount; i++)
{
try
{
// Token base id for TypeSpec
const int mdTypeSpec = 0x1B000000;
var type = module.ResolveType(mdTypeSpec | i);
CollectGenericTypeInstances(type, types, visited, typeFilter);
}
catch (ArgumentOutOfRangeException)
{
break;
}
catch (ArgumentException)
{
// Can happen on ResolveType on certain generic types, so we continue
}
}
for (int i = 1; i < mdMaxCount; i++)
{
try
{
// Token base id for MethodSpec
const int mdMethodSpec = 0x2B000000;
var method = module.ResolveMethod(mdMethodSpec | i);
var genericArgs = method.GetGenericArguments();
foreach (var genArgType in genericArgs)
{
CollectGenericTypeInstances(genArgType, types, visited, typeFilter);
}
}
catch (ArgumentOutOfRangeException)
{
break;
}
catch (ArgumentException)
{
// Can happen on ResolveType on certain generic types, so we continue
}
}
for (int i = 1; i < mdMaxCount; i++)
{
try
{
// Token base id for Field
const int mdField = 0x04000000;
var field = module.ResolveField(mdField | i);
CollectGenericTypeInstances(field.FieldType, types, visited, typeFilter);
}
catch (ArgumentOutOfRangeException)
{
break;
}
catch (ArgumentException)
{
// Can happen on ResolveType on certain generic types, so we continue
}
}
}
// Scan for types used in constructor arguments to assembly-level attributes,
// such as [RegisterGenericJobType(typeof(...))].
foreach (var customAttribute in assembly.CustomAttributes)
{
foreach (var argument in customAttribute.ConstructorArguments)
{
if (argument.ArgumentType == typeof(Type))
{
CollectGenericTypeInstances((Type)argument.Value, types, visited, typeFilter);
}
}
}
}
private static void CollectGenericTypeInstances(
Type type,
List<Type> types,
HashSet<Type> visited,
Func<Type, bool> typeFilter)
{
if (type.IsPrimitive) return;
if (!visited.Add(type)) return;
// Add only concrete types
if (type.IsConstructedGenericType && !type.ContainsGenericParameters && typeFilter(type))
{
types.Add(type);
}
// Collect recursively generic type arguments
var genericTypeArguments = type.GenericTypeArguments;
foreach (var genericTypeArgument in genericTypeArguments)
{
if (!genericTypeArgument.IsPrimitive)
{
CollectGenericTypeInstances(genericTypeArgument, types, visited, typeFilter);
}
}
}
[DebuggerDisplay("{Type} (static methods: {CollectStaticMethods})")]
private struct TypeToVisit
{
public TypeToVisit(Type type, bool collectStaticMethods)
{
Type = type;
CollectStaticMethods = collectStaticMethods;
}
public readonly Type Type;
public readonly bool CollectStaticMethods;
}
}
[Flags]
internal enum BurstReflectionAssemblyOptions
{
None = 0,
ExcludeTestAssemblies = 1,
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 661ba2a1bdac339cb55c6a43b9eaa0ee
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
{
"reference": "Unity.Burst"
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 268a64be2367b3248a24f317d458861a
AssemblyDefinitionReferenceImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: