Files
Fishing2Server/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/SeparateTableGenerator.cs
2025-10-29 17:59:43 +08:00

268 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Fantasy.SourceGenerator.Common;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// ReSharper disable MemberHidesStaticFromOuterClass
namespace Fantasy.SourceGenerator.Generators
{
/// <summary>
/// SeparateTable 接口生成器
/// 自动生成 SeparateTable 所需的注册代码,替代运行时反射
/// </summary>
[Generator]
public partial class SeparateTableGenerator : IIncrementalGenerator
{
private static readonly SourceCodeBuilder SeparateTableInfo = new SourceCodeBuilder();
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 查找所有标记了 SeparateTableAttribute 的类
var attributedClasses = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => IsSeparateTableClass(node),
transform: static (ctx, _) => GetSeparateTableTypeInfo(ctx))
.Where(static info => info != null)
.Collect();
// 组合编译信息和找到的类型
var compilationAndTypes = context.CompilationProvider.Combine(attributedClasses);
// 注册源代码输出
context.RegisterSourceOutput(compilationAndTypes, static (spc, source) =>
{
// 检查1: 是否定义了 FANTASY_NET 预编译符号
if (!CompilationHelper.HasFantasyNETDefine(source.Left))
{
return;
}
// 检查2: 是否引用了 Fantasy 框架的核心类型
if (source.Left.GetTypeByMetadataName("Fantasy.Entitas.Interface.ISupportedSeparateTable") == null)
{
return;
}
GenerateRegistrationCode(spc, source.Left, source.Right!);
});
}
private static SeparateTableTypeInfo? GetSeparateTableTypeInfo(GeneratorSyntaxContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
if (context.SemanticModel.GetDeclaredSymbol(classDecl) is not INamedTypeSymbol symbol)
{
return null;
}
if (!symbol.InheritsFrom("Fantasy.Entitas.Entity"))
{
// 不是 Entity不处理
return null;
}
// 检查是否标记了 SeparateTableAttribute
var separateTableAttr = symbol.GetAttributes()
.Where(attr => attr.AttributeClass?.ToDisplayString() == "Fantasy.SeparateTable.SeparateTableAttribute").ToList();
if (!separateTableAttr.Any())
{
return null;
}
var separateTableInfo = new Dictionary<string, string>();
foreach (var attributeData in separateTableAttr)
{
if (attributeData.ConstructorArguments.Length < 2)
{
return null;
}
var rootTypeSymbol = attributeData.ConstructorArguments[0].Value as INamedTypeSymbol;
var collectionName = attributeData.ConstructorArguments[1].Value?.ToString();
if (rootTypeSymbol == null || collectionName == null)
{
return null;
}
separateTableInfo[rootTypeSymbol.GetFullName()] = collectionName;
}
return separateTableInfo.Any() ? SeparateTableTypeInfo.Create(symbol, separateTableInfo) : null;
}
private static void GenerateRegistrationCode(
SourceProductionContext context,
Compilation compilation,
IEnumerable<SeparateTableTypeInfo> separateTableTypeInfos)
{
SeparateTableInfo.Clear();
var separateTableTypeInfoList = separateTableTypeInfos.ToList();
// 获取当前程序集名称(仅用于注释)
var assemblyName = compilation.AssemblyName ?? "Unknown";
// 生成代码文件
var builder = new SourceCodeBuilder();
// 添加 using
builder.AddUsings(
"System",
"System.Collections.Generic",
"Fantasy.Assembly",
"Fantasy.Entitas",
"Fantasy.Entitas.Interface",
"Fantasy.Async"
);
builder.AppendLine();
// 开始命名空间(固定使用 Fantasy.Generated
builder.BeginNamespace("Fantasy.Generated");
// 开始类定义(实现 IEntitySystemRegistrar 接口)
builder.AddXmlComment($"Auto-generated Entity System registration class for {assemblyName}");
builder.BeginClass("SeparateTableRegistrar", "internal sealed", "ISeparateTableRegistrar");
// 生成字段用于存储已注册(用于 UnRegister
GenerateFields(builder, separateTableTypeInfoList);
// 生成注册方法
GenerateRegisterMethod(builder, separateTableTypeInfoList);
// 生成反注册方法
GenerateUnRegisterMethod(builder, separateTableTypeInfoList);
// 结束类
builder.EndClass();
// 生成数据库帮助类
builder.Append(GenerateGenerateSeparateTableGeneratedExtensions().ToString());
// 结束命名空间
builder.EndNamespace();
// 输出源代码
context.AddSource("SeparateTableRegistrar.g.cs", builder.ToString());
}
private static SourceCodeBuilder GenerateGenerateSeparateTableGeneratedExtensions()
{
var builder = new SourceCodeBuilder();
builder.AppendLine();
builder.Indent(1);
builder.AddXmlComment("分表组件扩展方法。");
builder.AppendLine("public static class SeparateTableGeneratedExtensions");
builder.AppendLine("{");
builder.Indent(1);
builder.AddXmlComment("从数据库加载指定实体的所有分表数据,并自动建立父子关系。");
builder.BeginMethod("public static FTask LoadWithSeparateTables<T>(this T entity) where T : Entity, new()");
builder.AppendLine("return entity.Scene.SeparateTableComponent.LoadWithSeparateTables(entity);");
builder.EndMethod();
builder.AddXmlComment("将实体及其所有分表组件保存到数据库中。");
builder.BeginMethod("public static FTask PersistAggregate<T>(this T entity) where T : Entity, new()");
builder.AppendLine("return entity.Scene.SeparateTableComponent.PersistAggregate(entity);");
builder.EndMethod();
builder.Unindent();
builder.AppendLine("}");
return builder;
}
private static void GenerateFields(SourceCodeBuilder builder, List<SeparateTableTypeInfo> separateTableTypeInfoList)
{
SeparateTableInfo.AppendLine("private readonly List<ISeparateTableRegistrar.SeparateTableInfo> _separateTableInfos = new List<ISeparateTableRegistrar.SeparateTableInfo>()");
SeparateTableInfo.Indent(2);
SeparateTableInfo.AppendLine("{");
SeparateTableInfo.Indent(1);
if (separateTableTypeInfoList.Any())
{
foreach (var separateTableTypeInfo in separateTableTypeInfoList)
{
foreach (var separateTableInfo in separateTableTypeInfo.SeparateTableInfo)
{
SeparateTableInfo.AppendLine(
$"new ISeparateTableRegistrar.SeparateTableInfo(typeof({separateTableInfo.Key}) ,typeof({separateTableTypeInfo.TypeFullName}) ,\"{separateTableInfo.Value}\"),");
}
}
}
SeparateTableInfo.Unindent();
SeparateTableInfo.AppendLine("};");
builder.AppendLine(SeparateTableInfo.ToString());
}
private static void GenerateRegisterMethod(SourceCodeBuilder builder, List<SeparateTableTypeInfo> separateTableTypeInfos)
{
builder.BeginMethod("public List<ISeparateTableRegistrar.SeparateTableInfo> Register()");
builder.AppendLine("return _separateTableInfos;");
builder.EndMethod();
builder.AppendLine();
}
private static void GenerateUnRegisterMethod(SourceCodeBuilder builder, List<SeparateTableTypeInfo> separateTableTypeInfos)
{
builder.BeginMethod("public List<ISeparateTableRegistrar.SeparateTableInfo> UnRegister()");
builder.AppendLine("return _separateTableInfos;");
builder.EndMethod();
builder.AppendLine();
}
private static bool IsSeparateTableClass(SyntaxNode node)
{
if (node is not ClassDeclarationSyntax classDecl)
{
return false;
}
// 快速检查是否有任何 Attribute
if (classDecl.AttributeLists.Count == 0)
{
return false;
}
// 检查是否标记了 SeparateTableAttribute
// 这里只做简单的语法级别检查,精确的语义检查在 GetSeparateTableTypeInfo 中进行
foreach (var attributeList in classDecl.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
var attributeName = attribute.Name.ToString();
// 匹配以下情况:
// 1. [SeparateTable(...)]
// 2. [SeparateTableAttribute(...)]
// 3. [Fantasy.Entitas.Interface.SeparateTable(...)]
// 4. [Fantasy.Entitas.Interface.SeparateTableAttribute(...)]
// 5. [global::Fantasy.Entitas.Interface.SeparateTable(...)]
if (attributeName == "SeparateTable" ||
attributeName == "SeparateTableAttribute" ||
attributeName == "Fantasy.Entitas.Interface.SeparateTable" ||
attributeName == "Fantasy.Entitas.Interface.SeparateTableAttribute" ||
attributeName == "global::Fantasy.Entitas.Interface.SeparateTable" ||
attributeName == "global::Fantasy.Entitas.Interface.SeparateTableAttribute")
{
return true;
}
}
}
return false;
}
private sealed class SeparateTableTypeInfo
{
public readonly Dictionary<string, string> SeparateTableInfo;
public readonly string TypeFullName;
public readonly string TypeName;
private SeparateTableTypeInfo(string typeFullName, string typeName,
Dictionary<string, string> separateTableInfo)
{
TypeFullName = typeFullName;
TypeName = typeName;
SeparateTableInfo = separateTableInfo;
}
public static SeparateTableTypeInfo Create(INamedTypeSymbol symbol,
Dictionary<string, string> separateTableInfo)
{
return new SeparateTableTypeInfo(
symbol.GetFullName(),
symbol.Name,
separateTableInfo);
}
}
}
}