List <IdentifierNameSyntax> FilterCandidates(GeneratorExecutionContext context, IEnumerable <IdentifierNameSyntax> candidates)
        {
            var filteredCandidates = new List <IdentifierNameSyntax>();

            foreach (var candidate in candidates)
            {
                var model = context.Compilation.GetSemanticModel(candidate.SyntaxTree);
                var containingClassDeclaration = candidate.Ancestors().OfType <ClassDeclarationSyntax>().FirstOrDefault();
                if (containingClassDeclaration != null)
                {
                    var candidateContainingClassSymbol = model.GetDeclaredSymbol(containingClassDeclaration);
                    if (candidateContainingClassSymbol != null &&
                        (candidateContainingClassSymbol.Is("Unity.Entities.SystemBase") || candidateContainingClassSymbol.Is("Unity.Entities.JobComponentSystem")))
                    {
                        if (!containingClassDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
                        {
                            var lambdaJobName = "Entities.ForEach";
                            if (candidate.Identifier.ToString() == "Job")
                            {
                                lambdaJobName = "Job.WithCode";
                            }
                            context.LogError("SG0001", "LambdaJobs",
                                             $"{lambdaJobName} is in system {candidateContainingClassSymbol.Name}, but {candidateContainingClassSymbol.Name} is not defined with partial.  Source generated lambda jobs must exist in a partial system class.  Please add the `partial` keyword as part of the class definition.",
                                             candidate.GetLocation());
                        }
                        else
                        {
                            filteredCandidates.Add(candidate);
                        }
                    }
                }
            }

            return(filteredCandidates);
        }
Пример #2
0
        public void Execute(GeneratorExecutionContext context)
        {
            bool IsGeneratedAuthoringComponentAttribute(AttributeData data)
            {
                return(data.AttributeClass.ContainingAssembly.Name == "Unity.Entities" &&
                       data.AttributeClass.Name == "GenerateAuthoringComponentAttribute");
            }

            try
            {
                if (context.Compilation.Assembly.Name.Contains("CodeGen.Tests"))
                {
                    return;
                }

                var authoringComponentReceiver = (AuthoringComponentReceiver)context.SyntaxReceiver;

                if (!authoringComponentReceiver.CandidateSyntaxes.Any() || !context.Compilation.ReferencedAssemblyNames.Any(n => n.Name == "Unity.Entities.Hybrid"))
                {
                    // TODO: I believe it is valid to have GenerateAuthoringComponent but not reference entities (DocCodeSamples.Tests currently does this).
                    // We should probably throw a warning here though.
                    //throw new ArgumentException("Using the [GenerateAuthoringComponent] attribute requires a reference to the Unity.Entities.Hybrid assembly.");
                    return;
                }

                SourceGenHelpers.LogInfo($"Source generating assembly {context.Compilation.Assembly.Name} for authoring components...");
                var stopwatch = Stopwatch.StartNew();;

                foreach (var candidateSyntax in authoringComponentReceiver.CandidateSyntaxes)
                {
                    var semanticModel   = context.Compilation.GetSemanticModel(candidateSyntax.SyntaxTree);
                    var candidateSymbol = semanticModel.GetDeclaredSymbol(candidateSyntax);
                    LogInfo($"Parsing authoring component {candidateSymbol.Name}");

                    if (!candidateSymbol.GetAttributes().Any(IsGeneratedAuthoringComponentAttribute))
                    {
                        continue;
                    }

                    var authoringComponent = new AuthoringComponent(candidateSyntax, candidateSymbol, context);

                    CheckValidity(authoringComponent);

                    var compilationUnit =
                        CompilationUnit().AddMembers(GenerateAuthoringTypeFrom(authoringComponent))
                        .NormalizeWhitespace();

                    var generatedSourceHint     = candidateSyntax.SyntaxTree.GetGeneratedSourceFileName(context.Compilation.Assembly);
                    var generatedSourceFullPath = candidateSyntax.SyntaxTree.GetGeneratedSourceFilePath(context.Compilation.Assembly);
                    var sourceTextForNewClass   = compilationUnit.GetText(Encoding.UTF8);
                    sourceTextForNewClass = sourceTextForNewClass.WithInitialLineDirectiveToGeneratedSource(generatedSourceFullPath);
                    context.AddSource(generatedSourceHint, sourceTextForNewClass);

                    File.WriteAllLines(
                        generatedSourceFullPath,
                        sourceTextForNewClass.ToString().Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None));
                }

                stopwatch.Stop();
                SourceGenHelpers.LogInfo($"TIME : AuthoringComponent : {context.Compilation.Assembly.Name} : {stopwatch.ElapsedMilliseconds}ms");
            }
            catch (Exception exception)
            {
                //context.WaitForDebuggerInAssembly();
                context.LogError("SGICE002", "Authoring Component", exception.ToString(), context.Compilation.SyntaxTrees.First().GetRoot().GetLocation());
            }
        }
        public void Execute(GeneratorExecutionContext sourceGeneratorContext)
        {
            try
            {
                var iJobEntityReceiver = (IJobEntityReceiver)sourceGeneratorContext.SyntaxReceiver;

                if (!iJobEntityReceiver.JobEntityTypeCandidates.Any())
                {
                    return;
                }

                var syntaxTreeToCandidates =
                    iJobEntityReceiver
                    .JobEntityTypeCandidates
                    .GroupBy(j => j.TypeNode.SyntaxTree)
                    .ToDictionary(group => group.Key, group => group.ToArray());

                foreach (var kvp in syntaxTreeToCandidates)
                {
                    var syntaxTree = kvp.Key;
                    var candidates = kvp.Value;

                    try
                    {
                        foreach (var(typeNode, onUpdateMethodNode) in candidates)
                        {
                            SemanticModel semanticModel       = sourceGeneratorContext.Compilation.GetSemanticModel(typeNode.SyntaxTree);
                            ITypeSymbol   candidateTypeSymbol = (ITypeSymbol)semanticModel.GetDeclaredSymbol(typeNode);

                            if (!candidateTypeSymbol.InheritsFromInterface("Unity.Entities.IJobEntity"))
                            {
                                continue;
                            }

                            var jobEntityDescription =
                                new JobEntityDescription(typeNode, onUpdateMethodNode, candidateTypeSymbol, sourceGeneratorContext);

                            if (jobEntityDescription.FieldDescriptions.Any(f => !f.IsValueType))
                            {
                                throw new ArgumentException("IJobEntity types may only contain value-type fields.");
                            }

                            var jobEntityData = new JobEntityData
                            {
                                UserWrittenJobEntity         = jobEntityDescription,
                                GeneratedIJobEntityBatchType = JobEntityBatchTypeGenerator.GenerateFrom(jobEntityDescription),
                            };

                            AllJobEntityTypes[jobEntityDescription.DeclaringTypeFullyQualifiedName] = jobEntityData;

                            AddJobEntityBatchSourceToContext(jobEntityData, sourceGeneratorContext);
                        }
                    }
                    catch (Exception exception)
                    {
                        sourceGeneratorContext.LogError("SGICE001", "JobEntity", exception.ToString(), syntaxTree.GetRoot().GetLocation());
                    }
                }

                SyntaxNode[] entitiesInSystemBaseDerivedTypes =
                    GetEntitiesInSystemBaseDerivedTypes(
                        sourceGeneratorContext, iJobEntityReceiver.EntitiesGetterCandidates).ToArray();

                if (!entitiesInSystemBaseDerivedTypes.Any())
                {
                    return;
                }

                var syntaxTreesToEntities =
                    entitiesInSystemBaseDerivedTypes
                    .GroupBy(e => e.SyntaxTree)
                    .ToDictionary(group => group.Key, group => group.ToArray());

                foreach (var kvp in syntaxTreesToEntities)
                {
                    var syntaxTree  = kvp.Key;
                    var entityNodes = kvp.Value;

                    try
                    {
                        foreach (SyntaxNode entityNode in entityNodes)
                        {
                            var systemBaseDescription        = SystemBaseDescription.From(entityNode, sourceGeneratorContext);
                            var updatedSystemBaseDescription = UpdatedSystemBaseTypeGenerator.GenerateFrom(systemBaseDescription);

                            AddUpdatedSystemBaseSourceToContext(
                                systemBaseDescription,
                                updatedSystemBaseDescription,
                                sourceGeneratorContext);
                        }
                    }
                    catch (Exception exception)
                    {
                        sourceGeneratorContext.LogError("SGICE001", "JobEntity", exception.ToString(), syntaxTree.GetRoot().GetLocation());
                    }
                }
            }
            catch (Exception exception)
            {
                sourceGeneratorContext.LogError(
                    "SG0002",
                    "Unknown Exception",
                    exception.ToString(),
                    sourceGeneratorContext.Compilation.SyntaxTrees.First().GetRoot().GetLocation());
            }
        }
        public void Execute(GeneratorExecutionContext context)
        {
            try
            {
                if (context.Compilation.ReferencedAssemblyNames.All(n => n.Name != "Unity.Entities") ||
                    context.Compilation.Assembly.Name.Contains("CodeGen.Tests"))
                {
                    return;
                }

                SourceGenHelpers.LogInfo($"Source generating assembly {context.Compilation.Assembly.Name} for lambda jobs...");
                var stopwatch = Stopwatch.StartNew();;
                //context.WaitForDebugger("HelloCube");

                // Setup project path for logging and emitting debug source
                // This isn't fantastic but I haven't come up with a better way to get the project path since we might be running out of process
                if (context.AdditionalFiles.Any())
                {
                    SourceGenHelpers.SetProjectPath(context.AdditionalFiles[0].Path);
                }

                // Do initial filter for early out
                var entitiesSyntaxReceiver = (EntitiesSyntaxReceiver)context.SyntaxReceiver;
                var lambdaJobCandidates    = FilterCandidates(context, entitiesSyntaxReceiver.EntitiesGetterCandidates.Concat(entitiesSyntaxReceiver.JobGetterCandidates));
                if (!lambdaJobCandidates.Any())
                {
                    return;
                }

                // Create map from SyntaxTrees to candidates
                var syntaxTreeToCandidates = lambdaJobCandidates.GroupBy(c => c.SyntaxTree).ToDictionary(group => group.Key, group => group.ToList());

                // Outer loop - iterate over syntax tree
                foreach (var treeKVP in syntaxTreeToCandidates)
                {
                    var syntaxTree     = treeKVP.Key;
                    var treeCandidates = treeKVP.Value;

                    try
                    {
                        // Build up list of job descriptions inside of containing class declarations
                        var classDeclarationToDescriptions = new Dictionary <ClassDeclarationSyntax, List <LambdaJobDescription> >();
                        var jobIndex = 0;
                        foreach (var candidate in treeCandidates)
                        {
                            var declaringType    = candidate.Ancestors().OfType <ClassDeclarationSyntax>().FirstOrDefault();
                            var containingMethod = candidate.Ancestors().OfType <MethodDeclarationSyntax>().FirstOrDefault();
                            if (declaringType == null || containingMethod == null)
                            {
                                continue;
                            }

                            SourceGenHelpers.LogInfo($"Parsing LambdaJobDescription in {declaringType.Identifier}.{containingMethod.Identifier}");
                            var jobDescription = LambdaJobDescription.From(candidate, context, jobIndex++);
                            if (jobDescription == null)
                            {
                                continue;
                            }

                            if (classDeclarationToDescriptions.ContainsKey(declaringType))
                            {
                                classDeclarationToDescriptions[declaringType].Add(jobDescription);
                            }
                            else
                            {
                                classDeclarationToDescriptions[declaringType] = new List <LambdaJobDescription>()
                                {
                                    jobDescription
                                }
                            };
                        }

                        // Inner loop - iterate through class descriptions with lambda jobs and generate new class declaration nodes
                        var originalToGeneratedNode = new Dictionary <SyntaxNode, MemberDeclarationSyntax>();
                        foreach (var kvp in classDeclarationToDescriptions)
                        {
                            var classDeclaration    = kvp.Key;
                            var descriptionsInClass = kvp.Value;
                            SourceGenHelpers.LogInfo($"Generating code for system type: {classDeclaration.Identifier}");
                            var newClassDeclaration = GenerateNewClassDeclarationForDescriptions(classDeclaration, descriptionsInClass);
                            //SourceGenHelpers.LogInfo(newClassDeclaration.ToString());
                            originalToGeneratedNode[classDeclaration] = newClassDeclaration;
                        }

                        // recurse and create nodes down to our created system nodes
                        var rootNodesWithGenerated = new List <MemberDeclarationSyntax>();
                        foreach (var child in syntaxTree.GetRoot().ChildNodes())
                        {
                            if (child is NamespaceDeclarationSyntax || child is ClassDeclarationSyntax)
                            {
                                var generatedRootNode = ConstructTreeWithGeneratedNodes(child, originalToGeneratedNode);
                                if (generatedRootNode != null)
                                {
                                    rootNodesWithGenerated.Add(generatedRootNode);
                                }
                            }
                        }

                        if (rootNodesWithGenerated.Any())
                        {
                            OutputGeneratedSyntaxTreeNodes(context, syntaxTree, rootNodesWithGenerated);
                        }
                    }
                    catch (Exception exception)
                    {
                        context.LogError("SGICE001", "LambdaJobs", exception.ToString(), syntaxTree.GetRoot().GetLocation());
                    }
                }

                stopwatch.Stop();
                SourceGenHelpers.LogInfo($"TIME : LambdaJobs : {context.Compilation.Assembly.Name} : {stopwatch.ElapsedMilliseconds}ms");
            }
            catch (Exception exception)
            {
                context.LogError("SGICE001", "LambdaJobs", exception.ToString(), context.Compilation.SyntaxTrees.First().GetRoot().GetLocation());
            }
        }