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); } }
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); } }