static LambdaJobDescriptionConstruction AnalyzeLambdaJobStatement(MethodDefinition method, Instruction getEntitiesOrJobInstruction, int lambdaNumber)
        {
            List <InvokedConstructionMethod> modifiers = new List <InvokedConstructionMethod>();

            Instruction cursor = getEntitiesOrJobInstruction;
            var         expectedPreviousMethodPushingDescription = getEntitiesOrJobInstruction;

            while (true)
            {
                cursor = FindNextConstructionMethod(method, cursor);

                var mr = cursor?.Operand as MethodReference;

                if (mr.Name == nameof(LambdaJobDescriptionExecutionMethods.Schedule) ||
                    mr.Name == nameof(LambdaJobDescriptionExecutionMethods.ScheduleParallel) ||
                    mr.Name == nameof(LambdaJobDescriptionExecutionMethods.Run))
                {
                    var withNameModifier = modifiers.FirstOrDefault(m => m.MethodName == nameof(LambdaJobDescriptionConstructionMethods.WithName));
                    var givenName        = withNameModifier?.Arguments.OfType <string>().Single();
                    var lambdaJobName    = givenName ?? $"{method.Name}_LambdaJob{lambdaNumber}";
                    if (givenName != null && !VerifyLambdaName(givenName))
                    {
                        UserError.DC0039(method, givenName, getEntitiesOrJobInstruction).Throw();
                    }

                    var hasWithStructuralChangesModifier
                        = modifiers.Any(m => m.MethodName == nameof(LambdaJobDescriptionConstructionMethods.WithStructuralChanges));

                    if (hasWithStructuralChangesModifier && mr.Name != nameof(LambdaJobDescriptionExecutionMethods.Run))
                    {
                        UserError.DC0028(method, getEntitiesOrJobInstruction).Throw();
                    }

                    FieldReference storeQueryInField = null;
                    foreach (var modifier in modifiers)
                    {
                        if (modifier.MethodName == nameof(LambdaJobQueryConstructionMethods.WithStoreEntityQueryInField))
                        {
                            var instructionThatPushedField = CecilHelpers.FindInstructionThatPushedArg(method, 1, modifier.InstructionInvokingMethod);
                            storeQueryInField = instructionThatPushedField.Operand as FieldReference;
                            if (instructionThatPushedField.OpCode != OpCodes.Ldflda || storeQueryInField == null ||
                                instructionThatPushedField.Previous.OpCode != OpCodes.Ldarg_0)
                            {
                                UserError.DC0031(method, getEntitiesOrJobInstruction).Throw();
                            }
                        }
                    }

                    LambdaJobDescriptionKind FindLambdaDescriptionKind()
                    {
                        switch (((MethodReference)getEntitiesOrJobInstruction.Operand).Name)
                        {
                        case EntitiesGetterName:
                            return(LambdaJobDescriptionKind.Entities);

                        case JobGetterName:
                            return(LambdaJobDescriptionKind.Job);

#if ENABLE_DOTS_COMPILER_CHUNKS
                        case "get_" + nameof(JobComponentSystem.Chunks):
                            return(LambdaJobDescriptionKind.Chunk);
#endif
                        default:
                            throw new ArgumentOutOfRangeException();
                        }
                    }

                    if (modifiers.All(m => m.MethodName != nameof(LambdaForEachDescriptionConstructionMethods.ForEach) &&
                                      m.MethodName != nameof(LambdaSingleJobDescriptionConstructionMethods.WithCode)))
                    {
                        DiagnosticMessage MakeDiagnosticMessage()
                        {
                            switch (FindLambdaDescriptionKind())
                            {
                            case LambdaJobDescriptionKind.Entities:
                                return(UserError.DC0006(method, getEntitiesOrJobInstruction));

                            case LambdaJobDescriptionKind.Job:
                                return(UserError.DC0017(method, getEntitiesOrJobInstruction));

                            case LambdaJobDescriptionKind.Chunk:
                                return(UserError.DC0018(method, getEntitiesOrJobInstruction));

                            default:
                                throw new ArgumentOutOfRangeException();
                            }
                        }

                        MakeDiagnosticMessage().Throw();
                    }

                    if (method.DeclaringType.HasGenericParameters)
                    {
                        UserError.DC0025($"Entities.ForEach cannot be used in system {method.DeclaringType.Name} as Entities.ForEach in generic system types are not supported.", method, getEntitiesOrJobInstruction).Throw();
                    }

                    var withCodeInvocationInstruction = modifiers
                                                        .Single(m => m.MethodName == nameof(LambdaForEachDescriptionConstructionMethods.ForEach) || m.MethodName == nameof(LambdaSingleJobDescriptionConstructionMethods.WithCode))
                                                        .InstructionInvokingMethod;
                    return(new LambdaJobDescriptionConstruction()
                    {
                        Kind = FindLambdaDescriptionKind(),
                        InvokedConstructionMethods = modifiers,
                        WithCodeInvocationInstruction = withCodeInvocationInstruction,
                        ScheduleOrRunInvocationInstruction = cursor,
                        LambdaJobName = lambdaJobName,
                        ChainInitiatingInstruction = getEntitiesOrJobInstruction,
                        ContainingMethod = method,
                        DelegateProducingSequence = AnalyzeForEachInvocationInstruction(method, withCodeInvocationInstruction),
                        WithStructuralChanges = hasWithStructuralChangesModifier,
                        StoreQueryInField = (storeQueryInField != null) ? storeQueryInField.Resolve() : null
                    });
                }

                var instructions = mr.Parameters.Skip(1)
                                   .Select(p => OperandObjectFor(CecilHelpers.FindInstructionThatPushedArg(method, p.Index, cursor))).ToArray();

                var invokedConstructionMethod = new InvokedConstructionMethod(mr.Name,
                                                                              (mr as GenericInstanceMethod)?.GenericArguments.ToArray() ?? Array.Empty <TypeReference>(),
                                                                              instructions, cursor);

                var allowDynamicValue = method.Module.ImportReference(typeof(AllowDynamicValueAttribute));
                for (int i = 0; i != invokedConstructionMethod.Arguments.Length; i++)
                {
                    if (invokedConstructionMethod.Arguments[i] != null)
                    {
                        continue;
                    }

                    var inbovokedForEachMethod    = mr.Resolve();
                    var methodDefinitionParameter = inbovokedForEachMethod.Parameters[i + 1];

                    if (!methodDefinitionParameter.CustomAttributes.Any(c => c.AttributeType.TypeReferenceEquals(allowDynamicValue)))
                    {
                        UserError.DC0008(method, cursor, mr).Throw();
                    }
                }

                if (modifiers.Any(m => m.MethodName == mr.Name) && !HasAllowMultipleAttribute(mr.Resolve()))
                {
                    UserError.DC0009(method, cursor, mr).Throw();
                }

                var findInstructionThatPushedArg = CecilHelpers.FindInstructionThatPushedArg(method, 0, cursor);
                if (cursor == null || findInstructionThatPushedArg != expectedPreviousMethodPushingDescription)
                {
                    UserError.DC0007(method, cursor).Throw();
                }

                expectedPreviousMethodPushingDescription = cursor;
                modifiers.Add(invokedConstructionMethod);
            }
        }
Example #2
0
        static LambdaJobDescriptionConstruction AnalyzeLambdaJobStatement(MethodDefinition method, Instruction getEntitiesOrJobInstruction, int lambdaNumber)
        {
            List <InvokedConstructionMethod> modifiers = new List <InvokedConstructionMethod>();

            Instruction cursor = getEntitiesOrJobInstruction;
            var         expectedPreviousMethodPushingDescription = getEntitiesOrJobInstruction;

            while (true)
            {
                cursor = FindNextConstructionMethod(method, cursor);

                var mr = cursor?.Operand as MethodReference;

                if (mr.Name == nameof(LambdaJobDescriptionExecutionMethods.Schedule) ||
                    mr.Name == nameof(LambdaJobDescriptionExecutionMethods.ScheduleParallel) ||
                    mr.Name == nameof(LambdaJobDescriptionExecutionMethods.Run))
                {
                    // todo: Make this a expression switch statement once 8.0 is allowed in DOTS (2020.2)
                    LambdaJobDescriptionKind jobDescriptionKind;
                    switch (((MethodReference)getEntitiesOrJobInstruction.Operand).Name)
                    {
                    case EntitiesGetterName:   jobDescriptionKind = LambdaJobDescriptionKind.Entities; break;

                    case JobGetterName:        jobDescriptionKind = LambdaJobDescriptionKind.Job;      break;

#if ENABLE_DOTS_COMPILER_CHUNKS
                    case "get_" + nameof(JobComponentSystem.Chunks): jobDescriptionKind = LambdaJobDescriptionKind.Chunk; break;
#endif
                    default:                   throw new ArgumentOutOfRangeException();
                    }

                    var withNameModifier = modifiers.FirstOrDefault(m => m.MethodName == nameof(LambdaJobDescriptionConstructionMethods.WithName));
                    var givenName        = withNameModifier?.Arguments.OfType <string>().Single();
                    var lambdaJobName    = givenName ?? $"{method.Name}_LambdaJob{lambdaNumber}";
                    if (givenName != null && !VerifyLambdaName(givenName))
                    {
                        UserError.DC0043(method, givenName, getEntitiesOrJobInstruction).Throw();
                    }

                    var hasWithStructuralChangesModifier
                        = modifiers.Any(m => m.MethodName == nameof(LambdaJobDescriptionConstructionMethods.WithStructuralChanges));
                    if (hasWithStructuralChangesModifier)
                    {
                        if (jobDescriptionKind == LambdaJobDescriptionKind.Job)
                        {
                            UserError.DC0057(method, getEntitiesOrJobInstruction).Throw();
                        }
                        if (mr.Name != nameof(LambdaJobDescriptionExecutionMethods.Run))
                        {
                            UserError.DC0028(method, getEntitiesOrJobInstruction).Throw();
                        }
                    }

                    FieldReference storeQueryInField = null;
                    foreach (var modifier in modifiers)
                    {
                        if (modifier.MethodName == nameof(LambdaJobQueryConstructionMethods.WithStoreEntityQueryInField))
                        {
                            var instructionThatPushedField = CecilHelpers.FindInstructionThatPushedArg(method, 1, modifier.InstructionInvokingMethod);
                            storeQueryInField = instructionThatPushedField.Operand as FieldReference;
                            if (instructionThatPushedField.OpCode != OpCodes.Ldflda || storeQueryInField == null ||
                                instructionThatPushedField.Previous.OpCode != OpCodes.Ldarg_0)
                            {
                                UserError.DC0031(method, getEntitiesOrJobInstruction).Throw();
                            }
                        }
                    }

                    if (modifiers.All(m => m.MethodName != nameof(LambdaForEachDescriptionConstructionMethods.ForEach) &&
                                      m.MethodName != nameof(LambdaSingleJobDescriptionConstructionMethods.WithCode)))
                    {
                        DiagnosticMessage MakeDiagnosticMessage()
                        {
                            switch (jobDescriptionKind)
                            {
                            case LambdaJobDescriptionKind.Entities:
                                return(UserError.DC0006(method, getEntitiesOrJobInstruction));

                            case LambdaJobDescriptionKind.Job:
                                return(UserError.DC0017(method, getEntitiesOrJobInstruction));

                            case LambdaJobDescriptionKind.Chunk:
                                return(UserError.DC0018(method, getEntitiesOrJobInstruction));

                            default:
                                throw new ArgumentOutOfRangeException();
                            }
                        }

                        MakeDiagnosticMessage().Throw();
                    }

                    if (method.DeclaringType.HasGenericParameters)
                    {
                        UserError.DC0053(method.DeclaringType, method, getEntitiesOrJobInstruction).Throw();
                    }

                    var withCodeInvocationInstruction = modifiers
                                                        .Single(m => m.MethodName == nameof(LambdaForEachDescriptionConstructionMethods.ForEach) || m.MethodName == nameof(LambdaSingleJobDescriptionConstructionMethods.WithCode))
                                                        .InstructionInvokingMethod;
                    return(new LambdaJobDescriptionConstruction()
                    {
                        Kind = jobDescriptionKind,
                        InvokedConstructionMethods = modifiers,
                        WithCodeInvocationInstruction = withCodeInvocationInstruction,
                        ScheduleOrRunInvocationInstruction = cursor,
                        LambdaJobName = lambdaJobName,
                        ChainInitiatingInstruction = getEntitiesOrJobInstruction,
                        ContainingMethod = method,
                        DelegateProducingSequence = AnalyzeForEachInvocationInstruction(method, withCodeInvocationInstruction),
                        WithStructuralChanges = hasWithStructuralChangesModifier,
                        StoreQueryInField = (storeQueryInField != null) ? storeQueryInField.Resolve() : null
                    });
                }

                var arguments = mr.Parameters.Skip(1).Select(p =>
                {
                    var instructionThatPushedArg = CecilHelpers.FindInstructionThatPushedArg(method, p.Index, cursor);
                    if (instructionThatPushedArg.IsLoadConstantInt(out var intValue))
                    {
                        return(intValue);
                    }
                    return(instructionThatPushedArg.Operand);
                }).ToArray();
                var invokedConstructionMethod = new InvokedConstructionMethod(mr.Name,
                                                                              (mr as GenericInstanceMethod)?.GenericArguments.ToArray() ?? Array.Empty <TypeReference>(),
                                                                              arguments, cursor);

                var allowDynamicValueAttribute = method.Module.ImportReference(typeof(AllowDynamicValueAttribute));
                for (int i = 0; i != invokedConstructionMethod.Arguments.Length; i++)
                {
                    if (invokedConstructionMethod.Arguments[i] != null)
                    {
                        continue;
                    }

                    var invokedForEachMethod      = mr.Resolve();
                    var methodDefinitionParameter = invokedForEachMethod.Parameters[i + 1];

                    bool allowDynamicValue = methodDefinitionParameter.CustomAttributes.Any(c => c.AttributeType.TypeReferenceEquals(allowDynamicValueAttribute));

                    //all arguments to ForEach are implicit allowdynamicvalue, to allow for users bring-your-own-delegate setups, without us having to make the AllowDynamicAttribute public.
                    if (invokedConstructionMethod.MethodName == nameof(LambdaForEachDescriptionConstructionMethods.ForEach))
                    {
                        allowDynamicValue = true;
                    }

                    if (!allowDynamicValue)
                    {
                        UserError.DC0008(method, cursor, mr).Throw();
                    }
                }

                if (modifiers.Any(m => m.MethodName == mr.Name) && !HasAllowMultipleAttribute(mr.Resolve()))
                {
                    UserError.DC0009(method, cursor, mr).Throw();
                }

                var findInstructionThatPushedArg = CecilHelpers.FindInstructionThatPushedArg(method, 0, cursor);
                if (cursor == null || findInstructionThatPushedArg != expectedPreviousMethodPushingDescription)
                {
                    UserError.DC0007(method, cursor).Throw();
                }

                expectedPreviousMethodPushingDescription = cursor;
                modifiers.Add(invokedConstructionMethod);
            }
        }