using System; using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; #if !UNITY_DOTSPLAYER && !NET_DOTS using UnityEngine.Scripting; using System.Linq; #endif using System.Text; namespace Unity.Burst { /// /// The burst compiler runtime frontend. /// /// public static class BurstCompiler { /// /// Check if the LoadAdditionalLibrary API is supported by the current version of Unity /// /// True if the LoadAdditionalLibrary API can be used by the current version of Unity public static bool IsLoadAdditionalLibrarySupported() { return IsApiAvailable("LoadBurstLibrary"); } #if !UNITY_DOTSPLAYER && !NET_DOTS #if UNITY_EDITOR static unsafe BurstCompiler() { // Store pointers to Log and Compile callback methods. // For more info about why we need to do this, see comments in CallbackStubManager. string GetFunctionPointer(TDelegate callback) { GCHandle.Alloc(callback); // Ensure delegate is never garbage-collected. var callbackFunctionPointer = Marshal.GetFunctionPointerForDelegate(callback); return "0x" + callbackFunctionPointer.ToInt64().ToString("X16"); } EagerCompileLogCallbackFunctionPointer = GetFunctionPointer(EagerCompileLogCallback); ManagedResolverFunctionPointer = GetFunctionPointer(ManagedResolverFunction); #if UNITY_2020_1_OR_NEWER ProgressCallbackFunctionPointer = GetFunctionPointer(ProgressCallback); ProfileBeginCallbackFunctionPointer = GetFunctionPointer(ProfileBeginCallback); ProfileEndCallbackFunctionPointer = GetFunctionPointer(ProfileEndCallback); #else ProgressCallbackFunctionPointer = ""; #endif } #endif private class CommandBuilder { private StringBuilder _builder; private bool _hasArgs; public CommandBuilder() { _builder = new StringBuilder(); _hasArgs = false; } public CommandBuilder Begin(string cmd) { _builder.Clear(); _hasArgs = false; _builder.Append(cmd); return this; } public CommandBuilder With(string arg) { if (!_hasArgs) _builder.Append(' '); _hasArgs = true; _builder.Append(arg); return this; } public CommandBuilder With(IntPtr arg) { if (!_hasArgs) _builder.Append(' '); _hasArgs = true; _builder.AppendFormat("0x{0:X16}", arg.ToInt64()); return this; } public CommandBuilder And(char sep = '|') { _builder.Append(sep); return this; } public string SendToCompiler() { return SendRawCommandToCompiler(_builder.ToString()); } } [ThreadStatic] private static CommandBuilder _cmdBuilder; private static CommandBuilder BeginCompilerCommand(string cmd) { if (_cmdBuilder == null) { _cmdBuilder = new CommandBuilder(); } return _cmdBuilder.Begin(cmd); } #if BURST_INTERNAL [ThreadStatic] public static Func InternalCompiler; #endif /// /// Internal variable setup by BurstCompilerOptions. /// #if BURST_INTERNAL [ThreadStatic] // As we are changing this boolean via BurstCompilerOptions in btests and we are running multithread tests // we would change a global and it would generate random errors, so specifically for btests, we are using a TLS. public #else internal #endif static bool _IsEnabled; /// /// Gets a value indicating whether Burst is enabled. /// #if UNITY_EDITOR || BURST_INTERNAL public static bool IsEnabled => _IsEnabled; #else public static bool IsEnabled => _IsEnabled && BurstCompilerHelper.IsBurstGenerated; #endif /// /// Gets the global options for the burst compiler. /// public static readonly BurstCompilerOptions Options = new BurstCompilerOptions(true); #if UNITY_2019_3_OR_NEWER /// /// Sets the execution mode for all jobs spawned from now on. /// /// Specifiy the required execution mode public static void SetExecutionMode(BurstExecutionEnvironment mode) { Burst.LowLevel.BurstCompilerService.SetCurrentExecutionMode((uint)mode); } /// /// Retrieve the current execution mode that is configured. /// /// Currently configured execution mode public static BurstExecutionEnvironment GetExecutionMode() { return (BurstExecutionEnvironment)Burst.LowLevel.BurstCompilerService.GetCurrentExecutionMode(); } #endif /// /// Compile the following delegate with burst and return a new delegate. /// /// /// /// /// NOT AVAILABLE, unsafe to use internal static unsafe T CompileDelegate(T delegateMethod) where T : class { // We have added support for runtime CompileDelegate in 2018.2+ void* function = Compile(delegateMethod, false); object res = System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer((IntPtr)function, delegateMethod.GetType()); return (T)res; } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] private static void VerifyDelegateIsNotMulticast(T delegateMethod) where T : class { var delegateKind = delegateMethod as Delegate; if (delegateKind.GetInvocationList().Length > 1) { throw new InvalidOperationException($"Burst does not support multicast delegates, please use a regular delegate for `{delegateMethod}'"); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] private static void VerifyDelegateHasCorrectUnmanagedFunctionPointerAttribute(T delegateMethod) where T : class { var attrib = delegateMethod.GetType().GetCustomAttribute(); if (attrib == null || attrib.CallingConvention != CallingConvention.Cdecl) { #if !BURST_INTERNAL UnityEngine.Debug.LogWarning($"The delegate type {delegateMethod.GetType().FullName} should be decorated with [UnmanagedFunctionPointer(CallingConvention.Cdecl)] to ensure runtime interoperabilty between managed code and Burst-compiled code."); #endif } } /// /// DO NOT USE - deprecated. /// /// The Burst method to compile. /// The fallback managed method to use. /// The type of the delegate used to execute these methods. /// Nothing [Obsolete("This method will be removed in a future version of Burst")] public static unsafe IntPtr CompileILPPMethod(RuntimeMethodHandle burstMethodHandle, RuntimeMethodHandle managedMethodHandle, RuntimeTypeHandle delegateTypeHandle) { throw new NotImplementedException(); } /// /// Compile an IL Post-Processed method. /// /// The Burst method to compile. /// A token that must be passed to to get an actual executable function pointer. public static unsafe IntPtr CompileILPPMethod2(RuntimeMethodHandle burstMethodHandle) { if (burstMethodHandle.Value == IntPtr.Zero) { throw new ArgumentNullException(nameof(burstMethodHandle)); } OnCompileILPPMethod2?.Invoke(); var burstMethod = (MethodInfo)MethodBase.GetMethodFromHandle(burstMethodHandle); return (IntPtr)Compile(new FakeDelegate(burstMethod), burstMethod, isFunctionPointer: true, isILPostProcessing: true); } internal static Action OnCompileILPPMethod2; /// /// DO NOT USE - deprecated. /// /// The result of a previous call to . /// Nothing. [Obsolete("This method will be removed in a future version of Burst")] public static unsafe void* GetILPPMethodFunctionPointer(IntPtr ilppMethod) { throw new NotImplementedException(); } /// /// For a previous call to , get the actual executable function pointer. /// /// The result of a previous call to . /// The fallback managed method to use. /// The type of the delegate used to execute these methods. /// A pointer into an executable region, for running the function pointer. public static unsafe void* GetILPPMethodFunctionPointer2(IntPtr ilppMethod, RuntimeMethodHandle managedMethodHandle, RuntimeTypeHandle delegateTypeHandle) { if (ilppMethod == IntPtr.Zero) { throw new ArgumentNullException(nameof(ilppMethod)); } if (managedMethodHandle.Value == IntPtr.Zero) { throw new ArgumentNullException(nameof(managedMethodHandle)); } if (delegateTypeHandle.Value == IntPtr.Zero) { throw new ArgumentNullException(nameof(delegateTypeHandle)); } // If we are in the editor, we need to route a command to the compiler to start compiling the deferred ILPP compilation. // Otherwise if we're in Burst's internal testing, or in a player build, we already actually have the actual executable // pointer address, and we just return that. #if UNITY_EDITOR var managedMethod = (MethodInfo)MethodBase.GetMethodFromHandle(managedMethodHandle); var delegateType = Type.GetTypeFromHandle(delegateTypeHandle); var managedFallbackDelegate = Delegate.CreateDelegate(delegateType, managedMethod); var handle = GCHandle.Alloc(managedFallbackDelegate); var result = BeginCompilerCommand(BurstCompilerOptions.CompilerCommandILPPCompilation) .With(ilppMethod).And() .With(ManagedResolverFunctionPointer).And() .With(GCHandle.ToIntPtr(handle)) .SendToCompiler(); return new IntPtr(Convert.ToInt64(result, 16)).ToPointer(); #else return ilppMethod.ToPointer(); #endif } /// /// DO NOT USE - deprecated. /// /// A runtime method handle. /// Nothing. [Obsolete("This method will be removed in a future version of Burst")] public static unsafe void* CompileUnsafeStaticMethod(RuntimeMethodHandle handle) { throw new NotImplementedException(); } /// /// Compile the following delegate into a function pointer with burst, invokable from a Burst Job or from regular C#. /// /// Type of the delegate of the function pointer /// The delegate to compile /// A function pointer invokable from a Burst Job or from regular C# public static unsafe FunctionPointer CompileFunctionPointer(T delegateMethod) where T : class { VerifyDelegateIsNotMulticast(delegateMethod); VerifyDelegateHasCorrectUnmanagedFunctionPointerAttribute(delegateMethod); // We have added support for runtime CompileDelegate in 2018.2+ void* function = Compile(delegateMethod, true); return new FunctionPointer(new IntPtr(function)); } [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal class StaticTypeReinitAttribute : Attribute { public readonly Type reinitType; public StaticTypeReinitAttribute(Type toReinit) { reinitType = toReinit; } } private static unsafe void* Compile(object delegateObj, bool isFunctionPointer) { if (!(delegateObj is Delegate)) throw new ArgumentException("object instance must be a System.Delegate", nameof(delegateObj)); var delegateMethod = (Delegate)delegateObj; return Compile(delegateMethod, delegateMethod.Method, isFunctionPointer, false); } private static unsafe void* Compile(object delegateObj, MethodInfo methodInfo, bool isFunctionPointer, bool isILPostProcessing) { if (delegateObj == null) throw new ArgumentNullException(nameof(delegateObj)); if (delegateObj.GetType().IsGenericType) { throw new InvalidOperationException($"The delegate type `{delegateObj.GetType()}` must be a non-generic type"); } if (!methodInfo.IsStatic) { throw new InvalidOperationException($"The method `{methodInfo}` must be static. Instance methods are not supported"); } if (methodInfo.IsGenericMethod) { throw new InvalidOperationException($"The method `{methodInfo}` must be a non-generic method"); } #if ENABLE_IL2CPP if (isFunctionPointer && !isILPostProcessing && methodInfo.GetCustomAttributes().All(s => s.GetType().Name != "MonoPInvokeCallbackAttribute")) { UnityEngine.Debug.Log($"The method `{methodInfo}` must have `MonoPInvokeCallback` attribute to be compatible with IL2CPP!"); } #endif void* function; #if BURST_INTERNAL // Internally in Burst tests, we callback the C# method instead function = (void*)InternalCompiler(delegateObj); #else Delegate managedFallbackDelegateMethod = null; if (!isILPostProcessing) { managedFallbackDelegateMethod = delegateObj as Delegate; } var delegateMethod = delegateObj as Delegate; #if UNITY_EDITOR string defaultOptions; // In case Burst is disabled entirely from the command line if (BurstCompilerOptions.ForceDisableBurstCompilation) { if (isILPostProcessing) { return null; } else { GCHandle.Alloc(managedFallbackDelegateMethod); function = (void*)Marshal.GetFunctionPointerForDelegate(managedFallbackDelegateMethod); return function; } } if (isILPostProcessing) { defaultOptions = "--" + BurstCompilerOptions.OptionJitIsForFunctionPointer + "\n"; } else if (isFunctionPointer) { defaultOptions = "--" + BurstCompilerOptions.OptionJitIsForFunctionPointer + "\n"; // Make sure that the delegate will never be collected var delHandle = GCHandle.Alloc(managedFallbackDelegateMethod); defaultOptions += "--" + BurstCompilerOptions.OptionJitManagedDelegateHandle + "0x" + ManagedResolverFunctionPointer + "|" + "0x" + GCHandle.ToIntPtr(delHandle).ToInt64().ToString("X16"); } else { defaultOptions = "--" + BurstCompilerOptions.OptionJitEnableSynchronousCompilation; } string extraOptions; // The attribute is directly on the method, so we recover the underlying method here if (Options.TryGetOptions(methodInfo, true, out extraOptions, isForILPostProcessing: isILPostProcessing)) { if (!string.IsNullOrWhiteSpace(extraOptions)) { defaultOptions += "\n" + extraOptions; } var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(delegateObj, defaultOptions); function = Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId); } #else // The attribute is directly on the method, so we recover the underlying method here if (BurstCompilerOptions.HasBurstCompileAttribute(methodInfo)) { if (Options.EnableBurstCompilation && BurstCompilerHelper.IsBurstGenerated) { var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(delegateObj, string.Empty); function = Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId); } else { // If this is for direct-call, and we're in a player, with Burst disabled, then we should return null, // since we don't actually have a managedFallbackDelegateMethod at this point. if (isILPostProcessing) { return null; } // Make sure that the delegate will never be collected GCHandle.Alloc(managedFallbackDelegateMethod); // If we are in a standalone player, and burst is disabled and we are actually // trying to load a function pointer, in that case we need to support it // so we are then going to use the managed function directly // NOTE: When running under IL2CPP, this could lead to a `System.NotSupportedException : To marshal a managed method, please add an attribute named 'MonoPInvokeCallback' to the method definition.` // so in that case, the method needs to have `MonoPInvokeCallback` // but that's a requirement for IL2CPP, not an issue with burst function = (void*)Marshal.GetFunctionPointerForDelegate(managedFallbackDelegateMethod); } } #endif else { throw new InvalidOperationException($"Burst cannot compile the function pointer `{methodInfo}` because the `[BurstCompile]` attribute is missing"); } #endif // Should not happen but in that case, we are still trying to generated an error // It can be null if we are trying to compile a function in a standalone player // and the function was not compiled. In that case, we need to output an error if (function == null) { throw new InvalidOperationException($"Burst failed to compile the function pointer `{methodInfo}`"); } // When burst compilation is disabled, we are still returning a valid stub function pointer (the a pointer to the managed function) // so that CompileFunctionPointer actually returns a delegate in all cases return function; } /// /// Lets the compiler service know we are shutting down, called by the event on OnDomainUnload, if EditorApplication.quitting was called /// internal static void Shutdown() { #if UNITY_EDITOR SendCommandToCompiler(BurstCompilerOptions.CompilerCommandShutdown); #endif } #if UNITY_EDITOR internal static void SetDefaultOptions() { SendCommandToCompiler(BurstCompilerOptions.CompilerCommandSetDefaultOptions, Options.GetOptions(true)); } #endif #if UNITY_EDITOR internal static void DomainReload() { const string parameterSeparator = "***"; const string assemblySeparator = "```"; var cmdBuilder = BeginCompilerCommand(BurstCompilerOptions.CompilerCommandDomainReload) .With(ProgressCallbackFunctionPointer) .With(parameterSeparator) .With(EagerCompileLogCallbackFunctionPointer) .With(parameterSeparator); // Gather list of assemblies to compile (only actually used at Editor startup) var assemblyNames = UnityEditor.Compilation.CompilationPipeline .GetAssemblies(UnityEditor.Compilation.AssembliesType.Editor) .Select(x => x.name); foreach (var assemblyName in assemblyNames) { cmdBuilder.With(assemblyName) .With(assemblySeparator); } cmdBuilder.SendToCompiler(); } internal static string VersionNotify(string version) { return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandVersionNotification, version); } #endif /// /// Cancel any compilation being processed by the JIT Compiler in the background. /// internal static void Cancel() { #if UNITY_EDITOR SendCommandToCompiler(BurstCompilerOptions.CompilerCommandCancel); #endif } internal static void Enable() { #if UNITY_EDITOR SendCommandToCompiler(BurstCompilerOptions.CompilerCommandEnableCompiler); #endif } internal static void Disable() { #if UNITY_EDITOR SendCommandToCompiler(BurstCompilerOptions.CompilerCommandDisableCompiler); #endif } internal static bool IsHostEditorArm() { #if UNITY_EDITOR return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsArmTestEnv)=="true"; #else return false; #endif } internal static void TriggerUnsafeStaticMethodRecompilation() { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { var reinitAttributes = asm.GetCustomAttributes().Where( x => x.GetType().FullName == "Unity.Burst.BurstCompiler+StaticTypeReinitAttribute" ); foreach (var attribute in reinitAttributes) { var ourAttribute = attribute as StaticTypeReinitAttribute; var type = ourAttribute.reinitType; var method = type.GetMethod("Constructor",BindingFlags.Static|BindingFlags.Public); method.Invoke(null, new object[] { }); } } } internal static void TriggerRecompilation() { #if UNITY_EDITOR SetDefaultOptions(); // This is done separately from CompilerCommandTriggerRecompilation below, // because CompilerCommandTriggerRecompilation will cause all jobs to re-request // their function pointers from Burst, and we need to have actually triggered // compilation by that point. SendCommandToCompiler(BurstCompilerOptions.CompilerCommandTriggerSetupRecompilation); SendCommandToCompiler(BurstCompilerOptions.CompilerCommandTriggerRecompilation, Options.GetOptions(true)); #endif } internal static void UnloadAdditionalLibraries() { SendCommandToCompiler(BurstCompilerOptions.CompilerCommandUnloadBurstNatives); } internal static bool IsApiAvailable(string apiName) { return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsNativeApiAvailable, apiName) == "True"; } #if UNITY_EDITOR private unsafe delegate void LogCallbackDelegate(void* userData, int logType, byte* message, byte* fileName, int lineNumber); private static unsafe void EagerCompileLogCallback(void* userData, int logType, byte* message, byte* fileName, int lineNumber) { if (EagerCompilationLoggingEnabled) { BurstRuntime.Log(message, logType, fileName, lineNumber); } } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr ManagedFnPtrResolverDelegate(IntPtr handleVal); private static IntPtr ManagedResolverFunction(IntPtr handleVal) { var delegateObj = GCHandle.FromIntPtr(handleVal).Target; var fnptr = Marshal.GetFunctionPointerForDelegate(delegateObj); return fnptr; } internal static bool EagerCompilationLoggingEnabled = false; private static readonly string EagerCompileLogCallbackFunctionPointer; private static readonly string ManagedResolverFunctionPointer; #endif #if !UNITY_2021_1_OR_NEWER private static readonly System.Collections.Generic.List AssembliesToCompile = new System.Collections.Generic.List(); #endif internal static void NotifyCompilationStarted() { #if UNITY_EDITOR #if !UNITY_2021_1_OR_NEWER AssembliesToCompile.Clear(); #endif SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyCompilationStarted); #endif } internal static void NotifyAssemblyCompilationFinished(string assemblyName) { #if UNITY_EDITOR #if !UNITY_2021_1_OR_NEWER // On 2020.3- we need to defer adding the assemblies because of an issue with IL Post Processing ordering. AssembliesToCompile.Add(assemblyName); #else // On 2021.1+ we can just send the command straight away to the Burst client. SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyAssemblyCompilationFinished, assemblyName); #endif #endif } internal static void NotifyCompilationFinished() { #if UNITY_EDITOR #if !UNITY_2021_1_OR_NEWER foreach (var assemblyToCompile in AssembliesToCompile) { SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyAssemblyCompilationFinished, assemblyToCompile); } AssembliesToCompile.Clear(); #endif SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyCompilationFinished); #endif } #if UNITY_EDITOR private static readonly string ProgressCallbackFunctionPointer; #if UNITY_2020_1_OR_NEWER private delegate void ProgressCallbackDelegate(int current, int total); private static void ProgressCallback(int current, int total) { OnProgress?.Invoke(current, total); } internal static event Action OnProgress; #endif #endif internal static void SetProfilerCallbacks() { #if UNITY_EDITOR && UNITY_2020_1_OR_NEWER BeginCompilerCommand(BurstCompilerOptions.CompilerCommandSetProfileCallbacks) .With(ProfileBeginCallbackFunctionPointer).And(';') .With(ProfileEndCallbackFunctionPointer) .SendToCompiler(); #endif } #if UNITY_EDITOR && UNITY_2020_1_OR_NEWER internal delegate void ProfileBeginCallbackDelegate(string markerName, string metadataName, string metadataValue); internal delegate void ProfileEndCallbackDelegate(string markerName); private static readonly string ProfileBeginCallbackFunctionPointer; private static readonly string ProfileEndCallbackFunctionPointer; private static void ProfileBeginCallback(string markerName, string metadataName, string metadataValue) => OnProfileBegin?.Invoke(markerName, metadataName, metadataValue); private static void ProfileEndCallback(string markerName) => OnProfileEnd?.Invoke(markerName); internal static event ProfileBeginCallbackDelegate OnProfileBegin; internal static event ProfileEndCallbackDelegate OnProfileEnd; #endif private static string SendRawCommandToCompiler(string command) { var results = Unity.Burst.LowLevel.BurstCompilerService.GetDisassembly(DummyMethodInfo, command); if (!string.IsNullOrEmpty(results)) return results.TrimStart('\n'); return ""; } private static string SendCommandToCompiler(string commandName, string commandArgs = null) { if (commandName == null) throw new ArgumentNullException(nameof(commandName)); if (commandArgs == null) { // If there are no arguments then there's no reason to go through the builder return SendRawCommandToCompiler(commandName); } // Otherwise use the builder for building the final command return BeginCompilerCommand(commandName) .With(commandArgs) .SendToCompiler(); } private static readonly MethodInfo DummyMethodInfo = typeof(BurstCompiler).GetMethod(nameof(DummyMethod), BindingFlags.Static | BindingFlags.NonPublic); /// /// Dummy empty method for being able to send a command to the compiler /// private static void DummyMethod() { } #if !UNITY_EDITOR && !BURST_INTERNAL /// /// Internal class to detect at standalone player time if AOT settings were enabling burst. /// [BurstCompile] internal static class BurstCompilerHelper { [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool IsBurstEnabledDelegate(); private static readonly IsBurstEnabledDelegate IsBurstEnabledImpl = new IsBurstEnabledDelegate(IsBurstEnabled); [BurstCompile] [AOT.MonoPInvokeCallback(typeof(IsBurstEnabledDelegate))] private static bool IsBurstEnabled() { bool result = true; DiscardedMethod(ref result); return result; } [BurstDiscard] private static void DiscardedMethod(ref bool value) { value = false; } private static unsafe bool IsCompiledByBurst(Delegate del) { var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(del, string.Empty); // We don't try to run the method, having a pointer is already enough to tell us that burst was active for AOT settings return Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId) != (void*)0; } /// /// Gets a boolean indicating whether burst was enabled for standalone player, used only at runtime. /// public static readonly bool IsBurstGenerated = IsCompiledByBurst(IsBurstEnabledImpl); } #endif // !UNITY_EDITOR && !BURST_INTERNAL /// /// Fake delegate class to make BurstCompilerService.CompileAsyncDelegateMethod happy /// so that it can access the underlying static method via the property get_Method. /// So this class is not a delegate. /// private class FakeDelegate { public FakeDelegate(MethodInfo method) { Method = method; } [Preserve] public MethodInfo Method { get; } } #else // UNITY_DOTSPLAYER || NET_DOTS /// /// Compile the following delegate into a function pointer with burst, invokable from a Burst Job or from regular C#. /// /// Type of the delegate of the function pointer /// The delegate to compile /// A function pointer invokable from a Burst Job or from regular C# public static unsafe FunctionPointer CompileFunctionPointer(T delegateMethod) where T : System.Delegate { // Make sure that the delegate will never be collected GCHandle.Alloc(delegateMethod); return new FunctionPointer(Marshal.GetFunctionPointerForDelegate(delegateMethod)); } internal static bool IsApiAvailable(string apiName) { return false; } #endif } }