Abnormal code execution when using a Master Build

Hello, sorry for the confusing title but I’m having a bit of a difficult time understanding what’s exactly going on.

Yesterday I decided to build using the master configuration as we are getting pretty close to release and found out some strange code execution patterns. I’ve only observed this when using the Master configuration. Release works just fine.

I’ll try to explain what’s happening and what should be happening. In our game, the object you are controlling (PC if you wish) can attach itself to a colored square. After a certain time, the PC will fall automatically.
When you attach yourself to a colored square we do 2 things: listen if PC’s parent object has changed and launch a timer to automatically fall after a certain amount of time.

...
this.NGGame.kyubBlock.OnParentChanged.AddListener(this.FallFromColor2);
...
                    this.fallFromColorCoroutine = this.DelayCall(this.colorTimeout, this.FallFromColor);
...

And here are those 2 functions:

        private void    FallFromColor()
        {
            if (this.isColorFreezed == true)
            {
                this.isColorFreezed = false;
                this.stickingColorSquare.Fall.Invoke();
                this.NGGame.kyubBlock.Unparent(this.stickingColorSquare.cacheTransform);
                this.stickingColorSquare = null;
            }
        }
        private void    FallFromColor2(Block p)
        {        
                this.NGGame.kyubBlock.OnParentChanged.RemoveListener(this.FallFromColor2);   
                this.FallFromColor();
        }

If you wait for the timer to complete and fall off the following execution would occur:

  1. FallFromColor get called by the DelayCall.
  2. The PC will be unparented which would trigger FallFromColor2
  3. The listener would be removed since we are no longer being on a color
  4. FallFromColor is called again but this time isColorFreezed is set to false so we just exit the function.

This is the normal behavior that works fine. However when switching to Master configuration, things are different. I set some breakpoints, stepped through them and observed the following.

  1. FallFromColor would still be called because of DelayCall.
  2. If you try to step through the function it would just skip this.NGGame.kyubBlock.Unparent(this.stickingColorSquare.cacheTransform); and go to the end of the function.
  3. If you put the breakpoint the said line it would trigger twice.
  4. FallFromColor2 would also get triggered twice but the line this.NGGame.kyubBlock.OnParentChanged.RemoveListener(this.FallFromColor2); doesn’t seem to be executed at all.

The call stack is exactly the same and the thread doesn’t change but the behavior is definitely not what should be happening.

Again, sorry if this is too specific, but I’ll provide more details if needed.

Lastly, I’m building for Universal 10, using the .NET backend with unity 5.3.5p7

Sounds like .NET native optimizer is causing issues. Could you try disabling .NET native for master builds in the project properties and see whether it still behaves this way?

What visual studio/Windows SDK version are you on?

That’s indeed what was causing it. I’m using VS 2015 Community 13.0.25425.01 Update 3. I think it’s the latest version as I just did an update a day or two ago.

Isn’t .NET Native required for submission? If so is there anything that can be done to isolate the problem?

Yes, it’s required. But it should be possible to workaround the problem. However, let’s first figure out if you’re on newest .NET native version (as that would be the simplest fix if you weren’t). If you go to Control Panel → Programs and Features → VS2015 → Modify, under “Universal Windows App Development Tools” what “Tools” version do you see? I believe the latest is 1.4.1.

We’re experiencing the same type of problem. Debug and Release builds execute as expected, while Master is just weird (that’s the technical term for the wrong functions being called, right?).

We’re a bit behind as changing build tools near the end is not ideal, using Windows 10586, Visual Studio 2015 with Update 1 and I see Universal Tools as 1.2. It seems rather weird that everything runs as expected until .NET Native is used, but I don’t have much choice for submission if this will in fact fix things.

Can I start with just upgrading VS to Update 3 and then the tools you mention to be 1.4.1 or am I going to have to update Windows as well?

Tools are indeed at version 1.4.1, the Win 10 SDK is at 10.0.14393.

Windows Version is irrelevant.

Ok, you’re on latest then. Now, for the workaround. You’ll need to find a method that does stuff incorrectly and mark it with “[MethodImpl(MethodImplOptions.NoOptimization)]” attribute. That attribute will make .NET Native not optimize that function, and if the bug is in its optimizer, it will make it behave identically as if .NET Native was turned off.

If you manage to find a pattern or a function which indeed starts working after you mark it with that attribute, we’d love to see a bug report with that code snippet so we could report it to Microsoft.

Ok, so I tried adding the said attribute to the methods that are involved in the process but unfortunately the bug is still there. Tried stepping trough the function again and what I’m observing is that RemoveListener isn’t doing its job as weird as it sounds.

I’ve modified the function slightly as so:

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)]
        private void    FallFromColor()
        {
            if (this.isColorFreezed == true)
            {
                sb.Append("A");
                this.isColorFreezed = false;
                this.stickingColorSquare.Fall.Invoke();
                sb.Append("B");
                this.NGGame.kyubBlock.OnParentChanged.RemoveListener(this.FallFromColor2);
                this.NGGame.kyubBlock.Unparent(this.stickingColorSquare.cacheTransform);
                this.stickingColorSquare = null;
                sb.Append("C");
            }
        }

and put 2 breakpoints: first one on sb.Append(“A”) and second on sb.Append(“C”);

While in release mode, the OnParentChanged event has 4 runtime calls at the 1st breakpoint and 3 runtime calls at the 2nd breakpoint;
However, in master mode (with the .NET Native), the same event has 9 runtime calls at the 1st breakpoint AND at the 2nd breakpoint.

I’m attaching some screenshots and I will try to reproduce this scenario on an empty project.

EDIT: The 2nd breakpoint is at sb.Append(“C”)




Ok so I was able to reproduce this error on an empty project and it’s definitely related to RemoveListener not doing its job properly. I thought that it was related to using a UnityEvent with an argument but the error also happens on basic UnityEvent that doesn’t take an argument.

I have submitted a bug report with the sample project that demonstrates the issue. The issue number is: 827748

Thanks, we’ll take a look.

Any idea what’s going on here? We’re hoping to get a submission in soon.

We’ve identified exact place where this issue occurs, seems .NET behaves differently when comparing MethodInfo Class (System.Reflection) | Microsoft Learn between each other with .NET enabled vs disabled. We’ve sent a repro case to Microsoft, hoping to hear from them soon.

I just read the official issue description and it says that it’s not reproducible on the IL2CPP back end. I will try to build a version and report back. It’s not ideal but maybe it will allow us to submit something while we wait for a fix.

We’ve received an answer from Microsoft that this is by design, so we’ll be fixing this on our side to preserve same behavior between .NET Native disabled vs enabled, that bug could have also surfaced on regular .NET, but due caching the probability for it was low.

On il2cpp, it’s not reproducible because .NET Native has no effect on your code, it’s not even enabled there, because Unity generates C++ project when building to Windows Store Apps with il2cpp enabled.

Stay tuned

Unfortunately, il2cpp didn’t work for our game. I got Payload errors with all of our dll’s. I’m assuming we would need to rebuild those dll’s to make il2cpp happy, which isn’t very feasible right now. Please share anything you can with us, as we’d be willing to test a hotfix for this issue. Thanks.