No AOT code generated for a monobehaviour

I’m running into a Bolt issue when running on mobile.
After running the AOT pre-build step I’m still getting this error on scene start.
Could this be a bug with how the AOT pre-build generates its AOT code? Or am I doing something wrong?
I also have a linker file making sure the QuestSceneReferences isn’t being stripped

ΐ— Time: 4/12/2022 7:19:31 PM Type: Exception —
ExecutionEngineException: Attempting to call method 'Ludiq.InstanceFunctionInvoker3[[AOFL.Multiplayer.Overworld.QuestSceneReferences, OverworldClient, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[UnityEngine.GameObject[ ], UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]::.ctor' for which no ahead of time (AOT) code was generated. System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[ ] parameters) (at <00000000000000000000000000000000>:0) System.Reflection.MonoCMethod.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[ ] parameters, System.Globalization.CultureInfo culture) (at <00000000000000000000000000000000>:0) System.Reflection.MonoCMethod.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[ ] parameters, System.Globalization.CultureInfo culture) (at <00000000000000000000000000000000>:0) System.RuntimeType.CreateInstanceImpl (System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[ ] args, System.Globalization.CultureInfo culture, System.Object[ ] activationAttributes, System.Threading.StackCrawlMark& stackMark) (at <00000000000000000000000000000000>:0) System.Activator.CreateInstance (System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[ ] args, System.Globalization.CultureInfo culture, System.Object[ ] activationAttributes) (at <00000000000000000000000000000000>:0) System.Activator.CreateInstance (System.Type type, System.Object[ ] args) (at <00000000000000000000000000000000>:0) Ludiq.OptimizedReflection.GetMethodInvoker (System.Reflection.MethodInfo methodInfo) (at <00000000000000000000000000000000>:0) Ludiq.OptimizedReflection.Prewarm (System.Reflection.MethodInfo methodInfo) (at <00000000000000000000000000000000>:0) Ludiq.Member.Prewarm () (at <00000000000000000000000000000000>:0) Bolt.MemberUnit.Prewarm () (at <00000000000000000000000000000000>:0) Ludiq.Graph.Prewarm () (at <00000000000000000000000000000000>:0) Ludiq.Machine2[TGraph,TMacro].Awake () (at <00000000000000000000000000000000>:0)
Bolt.EventMachine2[TGraph,TMacro].Awake () (at <00000000000000000000000000000000>:0) Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation. System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[ ] parameters) (at <00000000000000000000000000000000>:0) System.Reflection.MonoCMethod.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[ ] parameters, System.Globalization.CultureInfo culture) (at <00000000000000000000000000000000>:0) System.Reflection.MonoCMethod.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[ ] parameters, System.Globalization.CultureInfo culture) (at <00000000000000000000000000000000>:0) System.RuntimeType.CreateInstanceImpl (System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[ ] args, System.Globalization.CultureInfo culture, System.Object[ ] activationAttributes, System.Threading.StackCrawlMark& stackMark) (at <00000000000000000000000000000000>:0) System.Activator.CreateInstance (System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[ ] args, System.Globalization.CultureInfo culture, System.Object[ ] activationAttributes) (at <00000000000000000000000000000000>:0) System.Activator.CreateInstance (System.Type type, System.Object[ ] args) (at <00000000000000000000000000000000>:0) Ludiq.OptimizedReflection.GetMethodInvoker (System.Reflection.MethodInfo methodInfo) (at <00000000000000000000000000000000>:0) Ludiq.OptimizedReflection.Prewarm (System.Reflection.MethodInfo methodInfo) (at <00000000000000000000000000000000>:0) Ludiq.Member.Prewarm () (at <00000000000000000000000000000000>:0) Bolt.MemberUnit.Prewarm () (at <00000000000000000000000000000000>:0) Ludiq.Graph.Prewarm () (at <00000000000000000000000000000000>:0) Ludiq.Machine2[TGraph,TMacro].Awake () (at <00000000000000000000000000000000>:0)
Bolt.EventMachine`2[TGraph,TMacro].Awake () (at <00000000000000000000000000000000>:0)

OMG I have something. I’ve just been able to reproduce it and submit a Bug Report. Basically, what I’m saying in the report is:

If your code is in an Assembly Definition and is referencing some assembly (let’s say TextMeshPro) and is using that assembly, like so:

using TMPro;
using UnityEngine;

public class NotIncluded : MonoBehaviour
{

    public TextMeshProUGUI TextMeshProUGUI;
  
    public void IncludeMe()
    {
        Debug.Log("Please");
    }
}

Your whole assembly is considered EditorOnly, which means no AOTStub will be generated for any Members in that assembly (in your case, some function with 2 parameters in QuestSceneReferences). Why TextMeshPro? Well, TextMeshPro is dependant of UnityEngine.UI, which is dependant of UnityEditor.CoreModule assembly. Therefore, UVS thinks your type is using Editor code, which would mean it is an Editor Type.

TextMeshPro is far from being the only one that triggers this bug, having a reference to Unity.VisualScripting.Core will do the same thing, because Unity.VisualScripting.Core.FrameDelayedCallback is using UnityEditor (even if it’s surrounded by #if UNITY_EDITOR, because when this is checked, UNITY_EDITOR is technically defined. Therefore, it thinks the assembly needs UnityEditor).

7 Likes

I’ve made this simple tool when I was testing. It can tell you if your type/assembly is considered an Editor Assembly. The code is the same as the one found in Codebase.cs, but stripped down to the bare minimum. If your type/assembly considered as Editor, it will also show you the dependencies.

Line 13, you can change the type of myType and run Bug/Check If Type Is Editor.

The tool also provides a way to generate the AOTStubs.cs without having to do a build. When it’s generated, check the file and check if it contains your type (a quick search in VSCode will do).

#if UNITY_EDITOR
namespace Scripts.Editor
{
    using Unity.VisualScripting;
    using UnityEditor;
    using UnityEngine;
    using System;
    using System.Reflection;
    using System.Collections.Generic;

    public static class AOTBuilder
    {
        public static readonly Type MyType = typeof(NotIncluded);
        // Generates the AOTStubs using reflection, but this is the actual process when building.
        [MenuItem("Bug/AOTStubs Builder")]
        public static void Build()
        {
            typeof(AotPreBuilder)
                .GetMethod("GenerateFromInternalMenu", (BindingFlags) ~0)
                !.Invoke(null, null);
            var message = @"AOTStubs.cs Generated in Assets\Unity.VisualScripting.Generated\VisualScripting.Core";
            Log(message, Color.cyan);
        }

       
        [MenuItem("Bug/Check If Type Is Editor")]
        public static void CheckIfTypeIsEditor()
        {
            var isEditorType = IsEditorType(MyType);
          
            Log($"Is {MyType.Name} an EditorType: {isEditorType}", Color.cyan);
        }

        private static bool IsUserAssembly(AssemblyName assemblyName)
        {
            var name = assemblyName.Name;

            return
                name == "Assembly-CSharp" ||
                name == "Assembly-CSharp-firstpass";
        }

        private static bool IsEditorAssembly(Assembly assembly, HashSet<string> visited)
        {
            if (visited.Contains(assembly.GetName().Name))
            {
                return false;
            }

            visited.Add(assembly.GetName().Name);

            if (Attribute.IsDefined(assembly, typeof(AssemblyIsEditorAssembly)))
            {
                Debug.Log(assembly + " Attribute.IsDefined");
                // _editorAssemblyCache.Add(assembly, true);
                return true;
            }

            if (IsUserAssembly(assembly.GetName()))
            {
                Debug.Log("IsUserAssembly");
                // _editorAssemblyCache.Add(assembly, false);
                return false;
            }

            AssemblyName[] listOfAssemblyNames = assembly.GetReferencedAssemblies();
            foreach (var dependencyName in listOfAssemblyNames)
            {
                try
                {
                    Assembly dependency = Assembly.Load(dependencyName);

                    if (IsEditorAssembly(dependency, visited))
                    {
                        Debug.Log($"{assembly}: {dependency}");
                        // _editorAssemblyCache.Add(assembly, true);
                        return true;
                    }
                }
                catch (Exception e)
                {
                    Debug.LogWarning(e.Message);
                }
            }

            // _editorAssemblyCache.Add(assembly, false);
            return false;
        }

        public static bool IsEditorType(Type type)
        {
            return IsEditorAssembly(type.Assembly, new HashSet<string>());
        }

        public static void Log(object message, Color color)
        {
            var coloredMessage =
                $"<color=#{(byte) (color.r * 255f):X2}{(byte) (color.g * 255f):X2}{(byte) (color.b * 255f):X2}>{message}</color>";
            Debug.Log(coloredMessage);
        }
    }
}
#endif
9 Likes

I’m having this same issue… it’s been driving me mad! My game has worked perfectly for awhile… and now I’m getting this error on iOS (haven’t tried android) but it works fine in editor and PC build.

ExecutionEngineException: Attempting to call method ‘Unity.VisualScripting.StaticPropertyAccessor`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]::.ctor’ for which no ahead of time (AOT) code was generated.

System.Reflection.RuntimeConstructorInfo.InternalInvoke (System.Object obj, System.Object[ ] parameters, System.Boolean wrapExceptions) (at <00000000000000000000000000000000>:0)
System.RuntimeType.CreateInstanceImpl (System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[ ] args, System.Globalization.CultureInfo culture, System.Object[ ] activationAttributes, System.Threading.StackCrawlMark& stackMark) (at <00000000000000000000000000000000>:0)
System.Activator.CreateInstance (System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[ ] args, System.Globalization.CultureInfo culture, System.Object[ ] activationAttributes) (at <00000000000000000000000000000000>:0)
Unity.VisualScripting.OptimizedReflection.GetPropertyAccessor (System.Reflection.PropertyInfo propertyInfo) (at <00000000000000000000000000000000>:0)
Unity.VisualScripting.Member.Prewarm () (at <00000000000000000000000000000000>:0)
Unity.VisualScripting.Graph.Prewarm () (at <00000000000000000000000000000000>:0)
Unity.VisualScripting.Machine2[TGraph,TMacro].Awake () (at <00000000000000000000000000000000>:0) Unity.VisualScripting.EventMachine2[TGraph,TMacro].Awake () (at <00000000000000000000000000000000>:0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.RuntimeConstructorInfo.InternalInvoke (System.Object obj, System.Object[ ] parameters, System.Boolean wrapExceptions) (at <00000000000000000000000000000000>:0)
System.RuntimeType.CreateInstanceImpl (System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[ ] args, System.Globalization.CultureInfo culture, System.Object[ ] activationAttributes, System.Threading.StackCrawlMark& stackMark) (at <00000000000000000000000000000000>:0)
System.Activator.CreateInstance (System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[ ] args, System.Globalization.CultureInfo culture, System.Object[ ] activationAttributes) (at <00000000000000000000000000000000>:0)
Unity.VisualScripting.OptimizedReflection.GetPropertyAccessor (System.Reflection.PropertyInfo propertyInfo) (at <00000000000000000000000000000000>:0)
Unity.VisualScripting.Member.Prewarm () (at <00000000000000000000000000000000>:0)
Unity.VisualScripting.Graph.Prewarm () (at <00000000000000000000000000000000>:0)
Unity.VisualScripting.Machine2[TGraph,TMacro].Awake () (at <00000000000000000000000000000000>:0) Unity.VisualScripting.EventMachine2[TGraph,TMacro].Awake () (at <00000000000000000000000000000000>:0)

So I found that this instance was actually caused by there being scripts being used that hadn’t been added to the Bolt Types; that said I am still seeing this issue on my main project since my asset development is done in a different project.

Has anybody found a way to force Bolt to make AOT code for all of the methods in a class including the constructor?

Decorate your classes with [IncludeInSettings(true)] attribute. This will add the class and all its public members to the node library upon regeneration. The user still would have to manually regenerate on their end though.

Unfortunately this doesn’t seem to cause the constructor to be serialized into the aot stubs class

Are you tied to Bolt? Does this reproduce with UVS as well? Bolt is pretty much an afterthought as far as support goes at this point. It’s getting deprecated in a year or so.

This issue has been fixed in 1.7.8 that was released last week.

Also, we need to provide a mechanism that prevents our users from not being able to compile their graphs. That’s why we decided to split runtime only assemblies and assemblies with Editor dependencies. We recognize that it comes with some issues and there are currently conversations about how to fix those problems.

Do you have a sample of project that reproduces this issue? We really want to get rid of every AotStub issues and we have good ideas on how to fix them. Our problem is we don’t have repro cases right now. Please help us by sending us any sample (I mean a really small project) that allows us to test our fixes.

I can’t upgrade my project easily so I’m bound to this version of Bolt, though it looks like this is an issue regardless.

As far as I can tell the issue is that I author my assets in a different project(s) but I need all of those assets in the same project if the goal is to

As far as I can tell the issue is that I author my assets in a different project(s) but I need all of those assets in the same project since the AOT Prebuild scan is expecting to find all of those instances in the project and use them to uniquely generate the aot stubs.

Unfortunately I can’t just move the assets to a package as I already tried this and it doesn’t seem to notice the bolt flows in those packages.

Additionally it appears that even if you create a bolt flow that uses all of your defined methods in a class (to ensure the proper aot stub generation) it still doens’t serialize the constructor unless you have that as an object scoped variable and have that variable filled (otherwise it drops the type and doesn’t generate the constructor stub method)

I don’t think I can share the project, but I’ll look into making a sample; in the meantime if you’re curious these should be the steps required to reproduce this:

  • Two projects both with bolt in them with Project A consuming Project B’s scripts as a package.

  • In Project B create monobehaviour class with some public methods and use it in a bolt flow.

  • Give that bolt flow an object scoped variable of the type of script you just made

  • Put the flow on an object

  • In Project A use this bolt flow in a scene

  • Add the created script to the managed types for Bolt

  • Run the aot prebuild

  • If you check the newly created aotstubs.cs you’ll find that your script didn’t have its public methods preserved

I have been struggling with the same AOT issue since upgrading to 1.7.8. I found that most of our assemblies were being excluded from the AOT stubs because of the dependency code in Codebase.IsEditorAssembly() (as per @marcuslelus post). I have embedded the Visual Scripting package and added a workaround to Codebase.cs to search the project for asmdefs and examine if they’re set to be Editor only. FindRuntimeAsmDefNames() is called from the constructor. IsAsmDefRuntimeAssembly() is called from inside IsEditorAssembly

private static readonly HashSet<string> _runtimeAsmDefs;

// The code in IsEditorAssembly() assumes any assembly which depends on an editor assembly
// must be an editor assembly itself. This means our Main assembly (and others) which use editor code
// inside #if UNITY_EDITOR will be excluded from the AOT stubs.
// FindRuntimeAsmDefNames finds all the asmdef files in the project and examines them to see if they
// really are editor assemblies or not.
private static HashSet<string> FindRuntimeAsmDefNames()
{
   // Find all the Assembly Definition files in the project and examine the platform settings
   // to see if it's editor-only. If not, assume it should be included in runtime
 
   HashSet<string> names = new();

   try
   {
       string[] guids = UnityEditor.AssetDatabase.FindAssets("t:asmdef", new[] {"Assets"});
       foreach (string guid in guids)
       {
           var asset = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEditorInternal.AssemblyDefinitionAsset>(UnityEditor.AssetDatabase.GUIDToAssetPath(guid));

           string wholeFile = asset.text;
           string[] includePlatforms = GetArrayElementsFromText(wholeFile, "includePlatforms");
           string[] excludePlatforms = GetArrayElementsFromText(wholeFile, "excludePlatforms");

           bool editorOnlyAssembly = false;
           if (includePlatforms is { Length: 1 } && includePlatforms[0] == "Editor")
           {
               editorOnlyAssembly = true;
           }
           else if (excludePlatforms is { Length: > 0 } && !excludePlatforms.Contains("Editor"))
           {
               editorOnlyAssembly = true;
           }

           if (!editorOnlyAssembly)
           {
               // Extract the name from the "name" field in the asmdef asset
               int nameIndex = wholeFile.IndexOf("\"name\":", StringComparison.OrdinalIgnoreCase) + 7;
               if (nameIndex != -1)
               {
                   int startOfNameIndex = nameIndex + wholeFile.Substring(nameIndex).IndexOf("\"", StringComparison.OrdinalIgnoreCase) + 1;
                   int endOfNameIndex = wholeFile.Substring(startOfNameIndex).IndexOf("\"", StringComparison.OrdinalIgnoreCase);
                   string name = wholeFile.Substring(startOfNameIndex, endOfNameIndex);
                   names.Add(name);
               }
           }
       }
   }
   catch (Exception e)
   {
       Debug.Log(e);
   }

   return names;
}

private static string[] GetArrayElementsFromText(string wholeFile, string arrayName)
{
   // Examine the text file looking for text in the form:
   //  "arrayName": [
   //      "Element_1",
   //      "Element_2",
   //      "Element_3"
   //  ],
 
 
   int arrayNameIndex = wholeFile.IndexOf(arrayName, StringComparison.OrdinalIgnoreCase);
   if (arrayNameIndex > 0)
   {
       int leftBracketSubIndex = wholeFile.Substring(arrayNameIndex).IndexOf("[", StringComparison.Ordinal);
       int rightBracketSubIndex = wholeFile.Substring(arrayNameIndex + leftBracketSubIndex + 1).IndexOf("]", StringComparison.Ordinal);
       if (leftBracketSubIndex > 0 && rightBracketSubIndex > 0)
       {
           string arrayString = wholeFile.Substring(arrayNameIndex + leftBracketSubIndex + 1, rightBracketSubIndex);
           string[] elements = arrayString.Split(',');
           for (int index = 0; index < elements.Length; index++)
           {
               elements[index] = elements[index].Trim().Replace("\"", "");
           }
           return elements;
       }
   }

   return null;
}

private static bool IsAsmDefRuntimeAssembly(string assemblyName)
{
    return _runtimeAsmDefs.Contains(assemblyName);
}

Put this at the top of the constructor:

_runtimeAsmDefs = FindRuntimeAsmDefNames();

Put this in IsEditorAssembly() just after IsSpecialCaseRuntimeAssembly():

if (IsAsmDefRuntimeAssembly(name))
{
    _editorAssemblyCache.Add(assembly, false);
    return false;
}
1 Like

Has anyone successfully reported the bug in proper channels? Have Unity QA confirmed this issue? This is mission critical to the many platforms that don’t support Mono scripting backend.

1 Like

I’ve not reported it officially. I hope @jeanedouard_unity is on the case though?

My understanding is that most Unity devs, especially on the UVS team, don’t visit the forums and the only valid way of making sure your issue is seen is to report it via the editor.

That’s great @SimonFireproof , would you mind giving a more basic “how to”, on implementing this fix?

There’s not much to it really. You need to embed the visual scripting package, which basically means coping the package from the cache to your project packages directory. Instructions here: https://docs.unity3d.com/Manual/upm-embed.html

Then open Codebase.cs and add the code from my previous post.

My code examines the asmdef text file to determine if an assembly is “Editor” only. If any other platform is ticked it will assume the assembly is required for runtime. It doesn’t try to compare against the current build platform.

1 Like

Currently, no AOT code is generated for any Cinemachine related nodes as well. They whitelisted TMPro assembly, but the core issue remains for everything that they have not whitelisted. Even if they whitelist everything inside Unity first party ecosystem, the issue will remain in context of 3rd party packages. The logic for automatically detecting editor only assemblies is flawed and needs to be reworked.

CASE IN-6243 for the Cinemachine issue.

1 Like

Agreed. How this ever got released is beyond me.