private MethodDefinition MakeExecuteMethod_Entities(LambdaParameterValueInformations providerInformations)
        {
            var executeMethod = CecilHelpers.AddMethodImplementingInterfaceMethod(TypeDefinition.Module,
                                                                                  TypeDefinition, typeof(IJobChunk).GetMethod(nameof(IJobChunk.Execute)));

            providerInformations.EmitInvocationToPrepareToRunOnEntitiesInIntoJobChunkExecute(executeMethod);

            var ilProcessor = executeMethod.Body.GetILProcessor();

            var iterateOnEntitiesMethod = CreateIterateEntitiesMethod(providerInformations);

            ilProcessor.Emit(OpCodes.Ldarg_0);
            ilProcessor.Emit(OpCodes.Ldarga, 1);

            ilProcessor.Emit(OpCodes.Ldarg_0);
            ilProcessor.Emit(OpCodes.Ldfld, providerInformations._runtimesField);

            ilProcessor.Emit(OpCodes.Call, iterateOnEntitiesMethod);

            EmitCallToDeallocateOnCompletion(ilProcessor);

            ilProcessor.Emit(OpCodes.Ret);

            return(executeMethod);
        }
        LambdaParameterValueInformations MakeLambdaParameterValueProviderInformations()
        {
            switch (LambdaJobDescriptionConstruction.Kind)
            {
            case LambdaJobDescriptionKind.Entities:
                return(LambdaParameterValueInformations.For(this));

            case LambdaJobDescriptionKind.Job:
                return(null);

            case LambdaJobDescriptionKind.Chunk:
                var allUsedParametersOfEntitiesForEachInvocations = ClonedMethods.SelectMany(
                    m =>
                    m.Body.Instructions.Where(IsChunkEntitiesForEachInvocation).Select(i =>
                                                                                       (m,
                                                                                        LambdaJobDescriptionConstruction.AnalyzeForEachInvocationInstruction(m, i)
                                                                                        .MethodLambdaWasEmittedAs)))
                                                                    .SelectMany(m_and_dem => m_and_dem.MethodLambdaWasEmittedAs.Parameters.Select(p => (m_and_dem.m, p)))
                                                                    .ToArray();

                return(LambdaParameterValueInformations.For(this));

            default:
                throw new ArgumentOutOfRangeException();
            }
        }
        private MethodDefinition CreateIterateEntitiesMethod(LambdaParameterValueInformations lambdaParameterValueInformations)
        {
            var iterateEntitiesMethod = new MethodDefinition("IterateEntities", MethodAttributes.Public, TypeSystem.Void)
            {
                Parameters =
                {
                    new ParameterDefinition("chunk",    ParameterAttributes.None, new ByReferenceType(ImportReference(typeof(ArchetypeChunk)))),
                    new ParameterDefinition("runtimes", ParameterAttributes.None, new ByReferenceType(lambdaParameterValueInformations.RuntimesType)),
                }
            };

            TypeDefinition.Methods.Add(iterateEntitiesMethod);

            var ilProcessor = iterateEntitiesMethod.Body.GetILProcessor();

            var loopTerminator = new VariableDefinition(TypeSystem.Int32);

            iterateEntitiesMethod.Body.Variables.Add(loopTerminator);
            ilProcessor.Emit(OpCodes.Ldarg_1);
            ilProcessor.Emit(OpCodes.Call, ImportReference(typeof(ArchetypeChunk).GetMethod("get_" + nameof(ArchetypeChunk.Count))));
            ilProcessor.Emit(OpCodes.Stloc, loopTerminator);

            var loopCounter = new VariableDefinition(TypeSystem.Int32);

            iterateEntitiesMethod.Body.Variables.Add(loopCounter);
            ilProcessor.Emit(OpCodes.Ldc_I4_0);
            ilProcessor.Emit(OpCodes.Stloc, loopCounter);

            var beginLoopInstruction = Instruction.Create(OpCodes.Ldloc, loopCounter);

            ilProcessor.Append(beginLoopInstruction);
            ilProcessor.Emit(OpCodes.Ldloc, loopTerminator);
            ilProcessor.Emit(OpCodes.Ceq);

            var exitDestination = Instruction.Create(OpCodes.Nop);

            ilProcessor.Emit(OpCodes.Brtrue, exitDestination);

            ilProcessor.Emit(OpCodes.Ldarg_0);
            foreach (var parameterDefinition in ClonedLambdaBody.Parameters)
            {
                lambdaParameterValueInformations.EmitILToLoadValueForParameterOnStack(parameterDefinition, ilProcessor, loopCounter);
            }

            ilProcessor.Emit(OpCodes.Call, ClonedLambdaBody);

            ilProcessor.Emit(OpCodes.Ldloc, loopCounter);
            ilProcessor.Emit(OpCodes.Ldc_I4_1);
            ilProcessor.Emit(OpCodes.Add);
            ilProcessor.Emit(OpCodes.Stloc, loopCounter);

            ilProcessor.Emit(OpCodes.Br, beginLoopInstruction);
            ilProcessor.Append(exitDestination);
            ilProcessor.Emit(OpCodes.Ret);
            return(iterateEntitiesMethod);
        }
        private MethodDefinition MakeExecuteMethod(LambdaParameterValueInformations lambdaParameterValueInformations)
        {
            switch (LambdaJobDescriptionConstruction.Kind)
            {
            case LambdaJobDescriptionKind.Entities:
                return(MakeExecuteMethod_Entities(lambdaParameterValueInformations));

            case LambdaJobDescriptionKind.Job:
                return(MakeExecuteMethod_Job());

            case LambdaJobDescriptionKind.Chunk:
                return(MakeExecuteMethod_Chunk(lambdaParameterValueInformations));

            default:
                throw new ArgumentOutOfRangeException();
            }
        }
        private MethodDefinition MakeExecuteMethod_Chunk(LambdaParameterValueInformations lambdaParameterValueInformations)
        {
            var executeMethod = CecilHelpers.AddMethodImplementingInterfaceMethod(TypeDefinition.Module,
                                                                                  TypeDefinition, typeof(IJobChunk).GetMethod(nameof(IJobChunk.Execute)));

            lambdaParameterValueInformations.EmitInvocationToPrepareToRunOnEntitiesInIntoJobChunkExecute(executeMethod);

            var executeIL = executeMethod.Body.GetILProcessor();

            executeIL.Emit(OpCodes.Ldarg_0);
            executeIL.Emit(OpCodes.Ldarg_1);
            executeIL.Emit(OpCodes.Ldarg_2);
            executeIL.Emit(OpCodes.Ldarg_3);
            executeIL.Emit(OpCodes.Call, ClonedLambdaBody);
            EmitCallToDeallocateOnCompletion(executeIL);
            executeIL.Emit(OpCodes.Ret);
            return(executeMethod);
        }
        private MethodDefinition MakeScheduleTimeInitializeMethod(LambdaParameterValueInformations lambdaParameterValueInformations)
        {
            var scheduleTimeInitializeMethod =
                new MethodDefinition("ScheduleTimeInitialize", MethodAttributes.Public, TypeDefinition.Module.TypeSystem.Void)
            {
                HasThis    = true,
                Parameters =
                {
                    new ParameterDefinition("componentSystem", ParameterAttributes.None, LambdaJobDescriptionConstruction.ContainingMethod.DeclaringType),
                },
            };

            if (ReadFromDisplayClassMethod != null)
            {
                scheduleTimeInitializeMethod.Parameters.Add(ReadFromDisplayClassMethod.Parameters.Last());
            }

            lambdaParameterValueInformations?.EmitInvocationToScheduleTimeInitializeIntoJobChunkScheduleTimeInitialize(scheduleTimeInitializeMethod);

            var scheduleIL = scheduleTimeInitializeMethod.Body.GetILProcessor();

            if (ReadFromDisplayClassMethod != null)
            {
                scheduleIL.Emit(OpCodes.Ldarg_0);
                scheduleIL.Emit(OpCodes.Ldarg_2);
                scheduleIL.Emit(OpCodes.Call, ReadFromDisplayClassMethod);
            }

            if (SystemInstanceField != null)
            {
                scheduleIL.Emit(OpCodes.Ldarg_0);
                scheduleIL.Emit(OpCodes.Ldarg_1);
                scheduleIL.Emit(OpCodes.Stfld, SystemInstanceField);
            }

            scheduleIL.Emit(OpCodes.Ret);
            return(scheduleTimeInitializeMethod);
        }
        private void ApplyPostProcessingOnJobCode(MethodDefinition[] methodUsedByLambdaJobs,
                                                  LambdaParameterValueInformations lambdaParameterValueInformations)
        {
            var forEachInvocations = new List <(MethodDefinition, Instruction)>();
            var methodDefinition   = methodUsedByLambdaJobs.First();

            forEachInvocations.AddRange(methodDefinition.Body.Instructions.Where(IsChunkEntitiesForEachInvocation).Select(i => (methodDefinition, i)));

            foreach (var methodUsedByLambdaJob in methodUsedByLambdaJobs)
            {
                var methodBody = methodUsedByLambdaJob.Body;

                var displayClassVariable = methodBody.Variables.SingleOrDefault(v => v.VariableType.Name.Contains("DisplayClass"));
                if (displayClassVariable != null)
                {
                    TypeDefinition displayClass = displayClassVariable.VariableType.Resolve();
                    bool           allDelegatesAreGuaranteedNotToOutliveMethod =
                        displayClass.IsValueType ||
                        CecilHelpers.AllDelegatesAreGuaranteedNotToOutliveMethodFor(methodUsedByLambdaJob);

                    if (!displayClass.IsValueType && allDelegatesAreGuaranteedNotToOutliveMethod)
                    {
                        CecilHelpers.PatchMethodThatUsedDisplayClassToTreatItAsAStruct(methodBody,
                                                                                       displayClassVariable, displayClass);
                        CecilHelpers.PatchDisplayClassToBeAStruct(displayClass);
                    }
                }
            }

            int counter = 1;

            foreach (var(methodUsedByLambdaJob, instruction) in forEachInvocations)
            {
                var methodBody = methodUsedByLambdaJob.Body;
                var(ldFtn, newObj) = FindClosureCreatingInstructions(methodBody, instruction);

                var newType = new TypeDefinition("", "InlineEntitiesForEachInvocation" + counter++, TypeAttributes.NestedPublic | TypeAttributes.SequentialLayout, methodUsedByLambdaJob.Module.ImportReference(typeof(ValueType)))
                {
                    DeclaringType = methodUsedByLambdaJob.DeclaringType
                };
                methodUsedByLambdaJob.DeclaringType.NestedTypes.Add(newType);

                CloneLambdaMethodAndItsLocalMethods();

                var iterateEntitiesMethod = CreateIterateEntitiesMethod(lambdaParameterValueInformations);

                var variable = new VariableDefinition(newType);
                methodBody.Variables.Add(variable);

                InstructionExtensions.MakeNOP(ldFtn.Previous);
                InstructionExtensions.MakeNOP(ldFtn);
                newObj.OpCode  = OpCodes.Ldnull;
                newObj.Operand = null;

                var displayClassVariable = methodBody.Variables.SingleOrDefault(v => v.VariableType.Name.Contains("DisplayClass"));
                if (displayClassVariable == null)
                {
                    continue;
                }
                var ilProcessor = methodBody.GetILProcessor();

                ilProcessor.InsertAfter(instruction, new List <Instruction>
                {
                    //no need to drop the delegate from the stack, because we just removed the function that placed it on the stack in the first place.
                    //do not drop the description from the stack, as the original method returns it, and we want to maintain stack behaviour.

                    //call our new method
                    Instruction.Create(OpCodes.Ldloca, variable),
                    Instruction.Create(OpCodes.Initobj, newType),

                    Instruction.Create(OpCodes.Ldloca, variable),
                    Instruction.Create(OpCodes.Ldloca, displayClassVariable),
                    Instruction.Create(OpCodes.Call, ReadFromDisplayClassMethod),


                    Instruction.Create(OpCodes.Ldloca, variable),
                    Instruction.Create(OpCodes.Ldarga, methodBody.Method.Parameters.First(p => p.ParameterType.Name == nameof(ArchetypeChunk))),
                    Instruction.Create(OpCodes.Ldarg_0),
                    Instruction.Create(OpCodes.Ldfld, lambdaParameterValueInformations._runtimesField),
                    Instruction.Create(OpCodes.Call, (MethodReference)iterateEntitiesMethod),
                });

#if ENABLE_DOTS_COMPILER_CHUNKS
                var chunkEntitiesInvocation = LambdaJobDescriptionConstruction.FindInstructionThatPushedArg(methodBody.Method, 0, instruction);
                if (chunkEntitiesInvocation.Operand is MethodReference mr && mr.Name == "get_" + nameof(ArchetypeChunk.Entities) && mr.DeclaringType.Name == nameof(ArchetypeChunk))
                {
                    CecilHelpers.EraseMethodInvocationFromInstructions(ilProcessor, chunkEntitiesInvocation);
                }
#endif

                CecilHelpers.EraseMethodInvocationFromInstructions(ilProcessor, instruction);
            }
        }