0
Cannot Fix

Some constants fail in AOT builds

André Ivankio Hauer Ploszaj 2 years ago updated by Lazlo Bonin (Lead Developer) 1 year ago 12

Bolt 1.4, .NET 3.x, Unity 2017.4.6f1

A project that was running fine in editor and in android when using Bolt 1.3, when I updated to 1.4, fixed the coroutines so that it played as before in editor, when built to android, some parts would stop working. I did do the AOT pre-build as always.

Android debug gives me errors like these:

07-21 21:12:52.151 4056-4074/com.animaokio.tatbolbolt E/Unity: MissingValuePortInputException: Missing input value for 'input'.
      at Bolt.Flow.GetValue (Bolt.ValueInput input) [0x00000] in <filename unknown>:0 
      at Bolt.SetVariable.Assign (Bolt.Flow flow) [0x00000] in <filename unknown>:0 
      at Bolt.Flow.InvokeDelegate (Bolt.ControlInput input) [0x00000] in <filename unknown>:0 
      at Bolt.Flow.Invoke (Bolt.ControlOutput output) [0x00000] in <filename unknown>:0 
      at Bolt.Flow.Invoke (Bolt.ControlOutput output) [0x00000] in <filename unknown>:0 
      at Bolt.ForEach.Loop (Bolt.Flow flow) [0x00000] in <filename unknown>:0 
      at Bolt.Flow.InvokeDelegate (Bolt.ControlInput input) [0x00000] in <filename unknown>:0 
      at Bolt.Flow.Invoke (Bolt.ControlOutput output) [0x00000] in <filename unknown>:0 
      at Bolt.Flow.Invoke (Bolt.ControlOutput output) [0x00000] in <filename unknown>:0 
      at Bolt.Flow.Invoke (Bolt.ControlOutput output) [0x00000] in <filename unknown>:0 
      at Bolt.Sequence.Enter (Bolt.Flow flow) [0x00000] in <filename unknown>:0 
      at Bolt.Flow.InvokeDelegate (Bolt
Bolt Version:
Unity Version:
Platform(s):
Scripting Backend:
.NET Version (API Compatibility Level):

Answer

Answer
Cannot Fix

Hi André! I finally figured this one out, it was very tricky!

It turns out that AOT Stubbing does not work on Mathf.Infinity. This is because, I believe, the C# compiler detects that:

  • Mathf.Infinity is a constant (const)
  • It is equal to float.MaxValue (another constant)
  • Therefore it only emits IL for the constant value itself

So even when I write a fake script that calls Mathf.Infinity to prevent Unity from stripping it, the C# compiler will convert Mathf.Infinity to just a very large number. Then, Unity has no idea that the code is actually calling it anywhere, strips Mathf.Infinity, and calling it via reflection through Bolt fails.

I think there's no way to fix this kind of issue in Bolt 1.4. We could fix this specific issue by adding UnityEngine.Mathf to a link.xml file manually, but it would only fix this constant, not all constants. It is, however, a good workaround if you absolutely want the clarity of the constant name in your graphs. For example, you could create file called link.xml with the following content and put it anywhere in your project (untested):

<linker>
  <assembly fullname="UnityEngine">
    <type fullname="UnityEngine.Mathf" preserve="all"></type>
  </assembly>
</linker>

In Bolt 2, this will no longer be an issue, because your builds will always be running from actual scripts, so reflection will never be involved.

In the mean time, I will add a warning in Bolt 1 when a connection fails to get created because of a failed reflection or type incompatibility like this. Hopefully, it should make debugging of those cases at least easier!

GOOD, I'M SATISFIED
Satisfaction mark by André Ivankio Hauer Ploszaj 2 years ago

Some Bolt code still works, only the pawn characters seem to be failing. They are quite long for me to know what nodes may be at fault without a hint from debug. Just reinforcing: they do work in editor after setting coroutine in events that have next frame or waits.

Reality.stop helped me find uses of the legacy community DoOnceUnit that were stopping me from updating, and I removed those safely and while still on 1.3 I confirmed it was working. So the project is the same and shouldn't have problem with 3rd party Bolt code.

Pending Review

Hi Ivankio, sorry you're still having issues with that upgrade.

I can't help much without a simple project or graph that reproduces the issue, but we can try deducting which node fails from that stack trace, maybe.

We know that unit is inside a For Each body, which is in a Sequence, and it's the unit that should be providing a value to SetVariable.

Does that help you narrow it down?

Thanks, I'll try to narrow it down.
Excuse my usual ignorance, but shouldn't the Debug.log unit write to the logcat of Android Studio? Do I need some special setting in Android Studio or Unity?

This was the narrowest and closest I could go. Your ForEach tip was really helpfull. 

I found weird that the log inside the super unit before first requesting the list doesn't show. Could it be the "Set Graph Variable" inside SU that changed from 1.3 to 1.4 or is the list indeed the real problem? I hope something rings a bell.


Hi Ivankio, thank you so much for the in-depth investigation.

From what I understand, it seems like the error is coming from Mathf: Get Infinity.

Here are a couple things I would try / check:

  • Can you find an AOT stub for Mathf.infinity in Ludiq/Ludiq.Core/Generated/AotStubs.cs?
  • Can you try replacing that node with another value (e.g. just a float literal of 99999) and see if it fixes the issue?
  • Do you get any other error log like "failed to reflect" or "failed to define" that could be linked to the Mathf.infinity node?

I will investigate on my end why that unit doesn't seem to work, but if you have another log, it could really help!

2. Good news, indeed replacing Mathf.Infinity with a float literal fixed it.

3. The log wouldn't point any other error as seen on the image above. Unless it was at some other moment, but during execution, no.

1. Insite AotStubs.cs there was this:

// UnityEngine.Mathf.Infinity
... // UnityEngine.Mathf.Infinity
[UnityEngine.Scripting.PreserveAttribute()]
public static void UnityEngine_Mathf_Infinity()
{
float accessor = UnityEngine.Mathf.Infinity;
Ludiq.StaticFieldAccessor<float> optimized = new Ludiq.StaticFieldAccessor<float>(default(System.Reflection.FieldInfo));
optimized.GetValue(null);
}

Just reinforcing, it used to work on 1.3. Can you tell from top of your head if any other node may have been affected in the same way in this update?

Happy to know the workaround worked.

What seems to have happened is that the Mathf.infinity unit failed to Define. This would be very early in your logs, around initialization / deserialization, not during runtime.

I don't have a hypothesis off the top of my head as to why it failed to define, but with logs I could surely fix it!

+1
Cannot Reproduce

I'm marking this as cannot reproduce for now because I don't see a way of figuring out which members somehow get stripped and weirdly stop the get member unit from defining.

But script generation is well on its way, and since this is the only report of that I had with a workaround, I'll just wait for script gen to fix all of this by itself.

Still no clue as to what may be causing this, but I'll be giving another shot at testing and investigating.

Answer
Cannot Fix

Hi André! I finally figured this one out, it was very tricky!

It turns out that AOT Stubbing does not work on Mathf.Infinity. This is because, I believe, the C# compiler detects that:

  • Mathf.Infinity is a constant (const)
  • It is equal to float.MaxValue (another constant)
  • Therefore it only emits IL for the constant value itself

So even when I write a fake script that calls Mathf.Infinity to prevent Unity from stripping it, the C# compiler will convert Mathf.Infinity to just a very large number. Then, Unity has no idea that the code is actually calling it anywhere, strips Mathf.Infinity, and calling it via reflection through Bolt fails.

I think there's no way to fix this kind of issue in Bolt 1.4. We could fix this specific issue by adding UnityEngine.Mathf to a link.xml file manually, but it would only fix this constant, not all constants. It is, however, a good workaround if you absolutely want the clarity of the constant name in your graphs. For example, you could create file called link.xml with the following content and put it anywhere in your project (untested):

<linker>
  <assembly fullname="UnityEngine">
    <type fullname="UnityEngine.Mathf" preserve="all"></type>
  </assembly>
</linker>

In Bolt 2, this will no longer be an issue, because your builds will always be running from actual scripts, so reflection will never be involved.

In the mean time, I will add a warning in Bolt 1 when a connection fails to get created because of a failed reflection or type incompatibility like this. Hopefully, it should make debugging of those cases at least easier!