This is not equivalent at all Your initial code wouldn’t look like this at all, it will look absolutely different (see below).
#define ENABLE_PROFILER
using System.Runtime.CompilerServices;
using Unity.Burst;
using Unity.Entities;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Profiling;
using UnityEngine;
namespace Systems
{
internal class ReproSystem : SystemBase
{
[BurstCompile]
[NoAlias]
[Unity.Entities.DOTSCompilerGenerated]
private struct <>c__DisplayClass_OnUpdate_LambdaJob0 : IJob
{
public float bar;
public float foo;
private static InternalCompilerInterface.JobRunWithoutJobSystemDelegate s_RunWithoutJobSystemDelegateFieldNoBurst;
private static InternalCompilerInterface.JobRunWithoutJobSystemDelegate s_RunWithoutJobSystemDelegateFieldBurst;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void OriginalLambdaBody()
{
foo = bar;
}
public void ReadFromDisplayClass(ref <>c__DisplayClass0_1 displayClass)
{
bar = displayClass.bar;
foo = displayClass.CS$<>8__locals1.foo;
}
public void WriteToDisplayClass(ref <>c__DisplayClass0_1 displayClass)
{
displayClass.bar = bar;
displayClass.CS$<>8__locals1.foo = foo;
}
public void Execute()
{
OriginalLambdaBody();
}
public void ScheduleTimeInitialize(ReproSystem componentSystem, ref <>c__DisplayClass0_1 displayClass)
{
ReadFromDisplayClass(ref displayClass);
}
[BurstCompile]
[Unity.Entities.MonoPInvokeCallback(typeof(InternalCompilerInterface.JobRunWithoutJobSystemDelegate))]
public unsafe static void RunWithoutJobSystem(void* jobData)
{
((<>c__DisplayClass_OnUpdate_LambdaJob0*)jobData)->Execute();
}
}
[Unity.Entities.DOTSCompilerGenerated]
[BurstCompile]
[NoAlias]
private struct <>c__DisplayClass_OnUpdate_LambdaJob1 : IJob
{
public float foo;
private static InternalCompilerInterface.JobRunWithoutJobSystemDelegate s_RunWithoutJobSystemDelegateFieldNoBurst;
private static InternalCompilerInterface.JobRunWithoutJobSystemDelegate s_RunWithoutJobSystemDelegateFieldBurst;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void OriginalLambdaBody()
{
foo = 5f;
}
public void ReadFromDisplayClass(ref <>c__DisplayClass0_0 displayClass)
{
foo = displayClass.foo;
}
public void WriteToDisplayClass(ref <>c__DisplayClass0_0 displayClass)
{
displayClass.foo = foo;
}
public void Execute()
{
OriginalLambdaBody();
}
public void ScheduleTimeInitialize(ReproSystem componentSystem, ref <>c__DisplayClass0_0 displayClass)
{
ReadFromDisplayClass(ref displayClass);
}
[BurstCompile]
[Unity.Entities.MonoPInvokeCallback(typeof(InternalCompilerInterface.JobRunWithoutJobSystemDelegate))]
public unsafe static void RunWithoutJobSystem(void* jobData)
{
((<>c__DisplayClass_OnUpdate_LambdaJob1*)jobData)->Execute();
}
}
private ProfilerMarker <>OnUpdate_LambdaJob0_profilerMarker;
private ProfilerMarker <>OnUpdate_LambdaJob1_profilerMarker;
protected unsafe override void OnUpdate()
{
<>c__DisplayClass0_0 displayClass = default(<>c__DisplayClass0_0);
displayClass.foo = 0f;
<>c__DisplayClass0_1 displayClass2 = default(<>c__DisplayClass0_1);
displayClass2.CS$<>8__locals1 = displayClass;
displayClass2.bar = 1f;
_ = base.Job;
<>c__DisplayClass_OnUpdate_LambdaJob0 jobData = default(<>c__DisplayClass_OnUpdate_LambdaJob0);
jobData.ScheduleTimeInitialize(this, ref displayClass2);
CompleteDependency();
InternalCompilerInterface.JobRunWithoutJobSystemDelegate functionPointer = JobsUtility.JobCompilerEnabled ? <>c__DisplayClass_OnUpdate_LambdaJob0.s_RunWithoutJobSystemDelegateFieldBurst : <>c__DisplayClass_OnUpdate_LambdaJob0.s_RunWithoutJobSystemDelegateFieldNoBurst;
<>OnUpdate_LambdaJob0_profilerMarker.Begin();
try
{
InternalCompilerInterface.RunIJob(ref jobData, functionPointer);
}
finally
{
<>OnUpdate_LambdaJob0_profilerMarker.End();
}
jobData.WriteToDisplayClass(ref displayClass2);
Debug.Log(displayClass.foo);
_ = base.Job;
<>c__DisplayClass_OnUpdate_LambdaJob1 jobData2 = default(<>c__DisplayClass_OnUpdate_LambdaJob1);
jobData2.ScheduleTimeInitialize(this, ref displayClass);
CompleteDependency();
InternalCompilerInterface.JobRunWithoutJobSystemDelegate functionPointer2 = JobsUtility.JobCompilerEnabled ? <>c__DisplayClass_OnUpdate_LambdaJob1.s_RunWithoutJobSystemDelegateFieldBurst : <>c__DisplayClass_OnUpdate_LambdaJob1.s_RunWithoutJobSystemDelegateFieldNoBurst;
<>OnUpdate_LambdaJob1_profilerMarker.Begin();
try
{
InternalCompilerInterface.RunIJob(ref jobData2, functionPointer2);
}
finally
{
<>OnUpdate_LambdaJob1_profilerMarker.End();
}
jobData2.WriteToDisplayClass(ref displayClass);
}
protected internal unsafe override void OnCreateForCompiler()
{
base.OnCreateForCompiler();
<>c__DisplayClass_OnUpdate_LambdaJob0.s_RunWithoutJobSystemDelegateFieldNoBurst = <>c__DisplayClass_OnUpdate_LambdaJob0.RunWithoutJobSystem;
<>c__DisplayClass_OnUpdate_LambdaJob0.s_RunWithoutJobSystemDelegateFieldBurst = InternalCompilerInterface.BurstCompile(<>c__DisplayClass_OnUpdate_LambdaJob0.s_RunWithoutJobSystemDelegateFieldNoBurst);
<>OnUpdate_LambdaJob0_profilerMarker = new ProfilerMarker("OnUpdate_LambdaJob0");
<>c__DisplayClass_OnUpdate_LambdaJob1.s_RunWithoutJobSystemDelegateFieldNoBurst = <>c__DisplayClass_OnUpdate_LambdaJob1.RunWithoutJobSystem;
<>c__DisplayClass_OnUpdate_LambdaJob1.s_RunWithoutJobSystemDelegateFieldBurst = InternalCompilerInterface.BurstCompile(<>c__DisplayClass_OnUpdate_LambdaJob1.s_RunWithoutJobSystemDelegateFieldNoBurst);
<>OnUpdate_LambdaJob1_profilerMarker = new ProfilerMarker("OnUpdate_LambdaJob1");
}
}
}
Unity using Roslyn’s compiler DisplayClass approach and has issues with scopes etc in Unity’s modification. Unity improving this and moving forward from display classes (as far as I know from chat with them about this topic last time in 2020). Issue here is that Roslyn generates two display classes (0_0 and 0_1) for this foo and bar and they both in a different one and both Job.WithCode writes to different DisplayClass and in case of second display class it writes foo into own DisplayClass.CS$<>8__locals1 and you might think “Hey why it’s not by reference its called Class” but it’s not. Both DisplayClass0_0 and DisplayClass0_1 are structs, and now you see that foo in DisplayClass.CS$<>8__locals1 it’s a copy, not reference and despite DisplayClasss-es itself passed with ref, CS$<>8__locals1 field not reference to DisplayClass0_0 but copy and Job changing this value of the foo to bar in DisplayClass0_1, but it wouldn’t affect original DisplayClass0_0 from which you getting foo for Debug.Log.
And the exact equivalent of compiled by Roslyn code in C# will work exactly the same:
public struct TestDisplay1 //Value type
{
public float foo;
}
public struct TestDisplay2 //Value type
{
public float bar;
public TestDisplay1 locals; //Value type field
}
public class ReproSystem : SystemBase
{
private static void RunLambda(LambdaSingleJobDescriptionConstructionMethods.WithCodeAction lambda)
{
lambda();
}
protected override void OnUpdate()
{
var displayClass1 = default(TestDisplay1);
displayClass1.foo = 0f;
var displayClass2 = default(TestDisplay2);
displayClass2.locals = displayClass1; //Take a close look at this especially on declaration
displayClass2.bar = 1f;
RunLambda(() =>
{
displayClass2.locals.foo = displayClass2.bar; //And this
});
Debug.Log(displayClass1.foo);
}
}
Compiled code itself works as expected, issue here is that Roslyn generates bad code, but as I already told, Unity should be aware of these issues with DisplayClass. As we discussed these locals issues 9 month’s ago with Joel.