Files
EpinelPS/EpinelPS.Analyzers/LoadRecordInitializerGenerator.cs
2025-07-17 13:51:49 -04:00

108 lines
3.8 KiB
C#

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Text;
namespace EpinelPS.Analyzers;
[Generator]
public class LoadRecordInitializerGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Step 1: Filter for field declarations with attributes
var fieldDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => node is FieldDeclarationSyntax fds && fds.AttributeLists.Count > 0,
transform: static (ctx, _) => GetTargetFieldInfo(ctx)
)
.Where(static m => m is not null)
.Collect();
// Step 2: Generate the code
context.RegisterSourceOutput(fieldDeclarations, (spc, fieldInfos) =>
{
var source = GenerateInitializerCode(fieldInfos!);
spc.AddSource("GameDataInitializer.g.cs", SourceText.From(source, Encoding.UTF8));
});
}
private static LoadFieldInfo? GetTargetFieldInfo(GeneratorSyntaxContext context)
{
if (context.Node is not FieldDeclarationSyntax fieldDecl)
return null;
var variable = fieldDecl.Declaration.Variables.FirstOrDefault();
if (variable == null)
return null;
if (context.SemanticModel.GetDeclaredSymbol(variable) is not IFieldSymbol symbol)
return null;
if (symbol.Type is not INamedTypeSymbol namedSymbol)
return null;
foreach (var attr in symbol.GetAttributes())
{
if (attr.ConstructorArguments.Length == 2)
{
if (attr.ConstructorArguments[0].Value is not string fileName || attr.ConstructorArguments[1].Value is not string key)
return null;
return new LoadFieldInfo
{
ContainingClass = symbol.ContainingType.ToDisplayString(),
FieldName = symbol.Name,
FileName = fileName,
Key = key,
RecordTypeName = namedSymbol.TypeArguments[1].Name
};
}
}
return null;
}
private static string GenerateInitializerCode(ImmutableArray<LoadFieldInfo> fieldInfos)
{
var sb = new StringBuilder();
sb.AppendLine("using System.Collections.Generic;");
sb.AppendLine("using System.Threading.Tasks;");
sb.AppendLine();
sb.AppendLine("namespace EpinelPS.Data;");
sb.AppendLine();
sb.AppendLine("public static class GameDataInitializer");
sb.AppendLine("{");
sb.AppendLine($"\tpublic static int TotalFiles = {fieldInfos.Length};");
sb.AppendLine("\tpublic static async Task InitializeGameData(IProgress<double> progress = null)");
sb.AppendLine("\t{");
foreach (var info in fieldInfos)
{
var tempVar = $"data_{info.FieldName}";
sb.AppendLine($"\t\tvar {tempVar} = await {info.ContainingClass}.Instance.LoadZip<{info.RecordTypeName}>(\"{info.FileName}\", progress);");
sb.AppendLine($"\t\tforeach (var obj in {tempVar})");
sb.AppendLine("\t\t{");
sb.AppendLine($"\t\t\t{info.ContainingClass}.Instance.{info.FieldName}.Add(obj.{info.Key}, obj);");
sb.AppendLine("\t\t}");
}
sb.AppendLine("\t}");
sb.AppendLine("}");
return sb.ToString();
}
private class LoadFieldInfo
{
public string ContainingClass = "";
public string FieldName = "";
public string FileName = "";
public string Key = "";
public string RecordTypeName = "";
}
}