框架更新
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
namespace Fantasy.SourceGenerator.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// 标记程序集启用 Source Generator 生成注册代码
|
||||
/// 添加到 AssemblyInfo.cs 或任何文件:
|
||||
/// [assembly: Fantasy.SourceGenerator.Attributes.EnableSourceGenerator]
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Assembly)]
|
||||
public sealed class EnableSourceGeneratorAttribute : System.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否生成 Entity System 注册器
|
||||
/// </summary>
|
||||
public bool GenerateEntitySystem { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否生成 Event Handler 注册器
|
||||
/// </summary>
|
||||
public bool GenerateEventHandler { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否生成 OpCode Mapper 注册器
|
||||
/// </summary>
|
||||
public bool GenerateOpCodeMapper { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否生成 Message Handler 注册器
|
||||
/// </summary>
|
||||
public bool GenerateMessageHandler { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Fantasy.SourceGenerator.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// 编译环境检测帮助类
|
||||
/// 提供检测 Fantasy 框架、Unity 环境等公共方法
|
||||
/// </summary>
|
||||
public static class CompilationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查是否定义了 Fantasy 框架的预编译符号
|
||||
/// 只有定义了 FANTASY_NET的项目才会生成代码
|
||||
/// </summary>
|
||||
public static bool HasFantasyNETDefine(Compilation compilation)
|
||||
{
|
||||
// 遍历所有语法树的预处理符号
|
||||
foreach (var tree in compilation.SyntaxTrees)
|
||||
{
|
||||
var defines = tree.Options.PreprocessorSymbolNames;
|
||||
if (defines.Contains("FANTASY_NET"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否定义了 Fantasy 框架的预编译符号
|
||||
/// 只有定义了 FANTASY_UNITY的项目才会生成代码
|
||||
/// </summary>
|
||||
public static bool HasFantasyUNITYDefine(Compilation compilation)
|
||||
{
|
||||
// 遍历所有语法树的预处理符号
|
||||
foreach (var tree in compilation.SyntaxTrees)
|
||||
{
|
||||
var defines = tree.Options.PreprocessorSymbolNames;
|
||||
if (defines.Contains("FANTASY_UNITY"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否定义了 Fantasy 框架的预编译符号
|
||||
/// 只有定义了 FANTASY_NET 或 FANTASY_UNITY 的项目才会生成代码
|
||||
/// </summary>
|
||||
public static bool HasFantasyDefine(Compilation compilation)
|
||||
{
|
||||
// 遍历所有语法树的预处理符号
|
||||
foreach (var tree in compilation.SyntaxTrees)
|
||||
{
|
||||
var defines = tree.Options.PreprocessorSymbolNames;
|
||||
if (defines.Contains("FANTASY_NET") || defines.Contains("FANTASY_UNITY"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测是否是 Unity 编译环境
|
||||
/// 优先检查是否引用了 UnityEngine 核心程序集或类型,这是最可靠的方式
|
||||
/// </summary>
|
||||
public static bool IsUnityCompilation(Compilation compilation)
|
||||
{
|
||||
// 方法1: 检查是否引用了 UnityEngine.CoreModule 或 UnityEngine 程序集(最可靠)
|
||||
foreach (var reference in compilation.References)
|
||||
{
|
||||
if (reference is PortableExecutableReference peRef)
|
||||
{
|
||||
var display = peRef.Display ?? "";
|
||||
// Unity 2017+ 使用模块化程序集
|
||||
if (display.Contains("UnityEngine.CoreModule.dll") ||
|
||||
display.Contains("UnityEngine.dll"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 方法2: 检查是否能够找到 UnityEngine 命名空间的核心类型
|
||||
var unityMonoBehaviour = compilation.GetTypeByMetadataName("UnityEngine.MonoBehaviour");
|
||||
var unityGameObject = compilation.GetTypeByMetadataName("UnityEngine.GameObject");
|
||||
if (unityMonoBehaviour != null || unityGameObject != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace Fantasy.SourceGenerator.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Source Generator 使用的常量定义
|
||||
/// </summary>
|
||||
internal static class GeneratorConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成文件的命名空间
|
||||
/// </summary>
|
||||
public const string GeneratedNamespace = "Fantasy.Generated";
|
||||
/// <summary>
|
||||
/// 诊断 ID 前缀
|
||||
/// </summary>
|
||||
public const string DiagnosticIdPrefix = "FANTASY";
|
||||
/// <summary>
|
||||
/// 生成的代码文件注释头
|
||||
/// </summary>
|
||||
public const string AutoGeneratedHeader = @"//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by Fantasy.SourceGenerator.
|
||||
// Runtime Version: 1.0.0
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#nullable enable
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
// ReSharper disable MergeIntoPattern
|
||||
// ReSharper disable SuspiciousTypeConversion.Global
|
||||
// ReSharper disable NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
// ReSharper disable CheckNamespace
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace Fantasy.SourceGenerator.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// HashCode 算法帮助类 (Source Generator 版本)
|
||||
/// 用于在编译时计算类型哈希值
|
||||
/// 注意:此实现必须与 Fantasy.Helper.HashCodeHelper.ComputeHash64 保持完全一致
|
||||
/// </summary>
|
||||
internal static class HashCodeHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 MurmurHash3 算法生成一个 long 的值
|
||||
/// </summary>
|
||||
/// <param name="str">输入字符串</param>
|
||||
/// <returns>64位哈希值</returns>
|
||||
public static long ComputeHash64(string str)
|
||||
{
|
||||
const ulong seed = 0xc58f1a7bc58f1a7bUL; // 64-bit seed
|
||||
var hash = seed;
|
||||
var c1 = 0x87c37b91114253d5UL;
|
||||
var c2 = 0x4cf5ad432745937fUL;
|
||||
|
||||
for (var i = 0; i < str.Length; i++)
|
||||
{
|
||||
var k1 = (ulong)str[i];
|
||||
k1 *= c1;
|
||||
k1 = (k1 << 31) | (k1 >> (64 - 31));
|
||||
k1 *= c2;
|
||||
|
||||
hash ^= k1;
|
||||
hash = (hash << 27) | (hash >> (64 - 27));
|
||||
hash = hash * 5 + 0x52dce729;
|
||||
}
|
||||
|
||||
hash ^= (ulong)str.Length;
|
||||
hash ^= hash >> 33;
|
||||
hash *= 0xff51afd7ed558ccdUL;
|
||||
hash ^= hash >> 33;
|
||||
hash *= 0xc4ceb9fe1a85ec53UL;
|
||||
hash ^= hash >> 33;
|
||||
return (long)hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// ReSharper disable CheckNamespace
|
||||
//------------------------------------------------------------------------------
|
||||
// 这个 IsExternalInit 类是一个 polyfill(兼容性填充)
|
||||
// 用于在 .NET Standard 2.0 或较低版本的框架中启用 C# 9.0 的 init 访问器和 record 类型功能。
|
||||
// 为什么需要它?
|
||||
// C# 9.0 引入了 init 访问器(只在初始化时可设置的属性)
|
||||
// 编译器在编译 init 属性时,会查找 IsExternalInit 类型
|
||||
// 示例:
|
||||
// public class Person
|
||||
// {
|
||||
// public string Name { get; init; } // 需要 IsExternalInit
|
||||
// public int Age { get; init; }
|
||||
// }
|
||||
// 使用
|
||||
// var person = new Person { Name = "Alice", Age = 30 };
|
||||
// person.Name = "Bob"; // ❌ 编译错误:init 属性只能在对象初始化时设置
|
||||
// 不定义会怎样?
|
||||
// 如果目标框架是 netstandard2.0 但没定义 IsExternalInit,编译器会报错:
|
||||
// error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit'
|
||||
// is not defined or imported
|
||||
// 实际应用场景
|
||||
// 在 IncrementalGenerator 中,你可能会生成或使用带 init 的代码
|
||||
//------------------------------------------------------------------------------
|
||||
#if NETSTANDARD2_0 || NETFRAMEWORK
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Polyfill for C# 9.0 record types in netstandard2.0
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
internal static class IsExternalInit { }
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,134 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Fantasy.SourceGenerator.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Roslyn 相关的扩展方法
|
||||
/// </summary>
|
||||
internal static class RoslynExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查类型是否实现了指定的接口(通过完全限定名)
|
||||
/// </summary>
|
||||
public static bool ImplementsInterface(this INamedTypeSymbol typeSymbol, string interfaceFullName)
|
||||
{
|
||||
return typeSymbol.AllInterfaces.Any(i => i.ToDisplayString() == interfaceFullName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查类型是否继承自指定的基类(通过完全限定名)
|
||||
/// </summary>
|
||||
public static bool InheritsFrom(this INamedTypeSymbol typeSymbol, string baseTypeFullName)
|
||||
{
|
||||
var current = typeSymbol.BaseType;
|
||||
while (current != null)
|
||||
{
|
||||
if (current.ToDisplayString() == baseTypeFullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
current = current.BaseType;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型的完全限定名(包括命名空间)
|
||||
/// </summary>
|
||||
public static string GetFullName(this ITypeSymbol typeSymbol, bool includeGlobal = true)
|
||||
{
|
||||
var displayString = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
return includeGlobal ? displayString : displayString.Replace("global::", "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查类型是否可以被实例化(非抽象、非接口、非泛型定义)
|
||||
/// </summary>
|
||||
public static bool IsInstantiable(this INamedTypeSymbol typeSymbol)
|
||||
{
|
||||
// 允许:具体的类(非抽象、非静态)
|
||||
// 排除:抽象类、静态类、接口、开放泛型类型(如 MyClass<T> where T 未指定)
|
||||
return typeSymbol is { IsAbstract: false, IsStatic: false, TypeKind: TypeKind.Class };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有实现指定接口的类型
|
||||
/// </summary>
|
||||
public static IEnumerable<INamedTypeSymbol> GetTypesImplementingInterface(
|
||||
this Compilation compilation,
|
||||
string interfaceFullName)
|
||||
{
|
||||
var visitor = new InterfaceImplementationVisitor(interfaceFullName);
|
||||
visitor.Visit(compilation.GlobalNamespace);
|
||||
return visitor.ImplementingTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为驼峰命名
|
||||
/// </summary>
|
||||
public static string ToCamelCase(this string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name) || char.IsLower(name[0]))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return char.ToLowerInvariant(name[0]) + name.Substring(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 访问器:查找实现特定接口的所有类型
|
||||
/// </summary>
|
||||
private class InterfaceImplementationVisitor : SymbolVisitor
|
||||
{
|
||||
private readonly string _interfaceFullName;
|
||||
private readonly List<INamedTypeSymbol> _implementingTypes = new List<INamedTypeSymbol>();
|
||||
|
||||
public IReadOnlyList<INamedTypeSymbol> ImplementingTypes => _implementingTypes;
|
||||
|
||||
public InterfaceImplementationVisitor(string interfaceFullName)
|
||||
{
|
||||
_interfaceFullName = interfaceFullName;
|
||||
}
|
||||
|
||||
public override void VisitNamespace(INamespaceSymbol symbol)
|
||||
{
|
||||
foreach (var member in symbol.GetMembers())
|
||||
{
|
||||
member.Accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitNamedType(INamedTypeSymbol symbol)
|
||||
{
|
||||
if (symbol.IsInstantiable() && symbol.ImplementsInterface(_interfaceFullName))
|
||||
{
|
||||
_implementingTypes.Add(symbol);
|
||||
}
|
||||
|
||||
// 递归访问嵌套类型
|
||||
foreach (var nestedType in symbol.GetTypeMembers())
|
||||
{
|
||||
nestedType.Accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取泛型接口的类型参数
|
||||
/// 例如:IAwakeSystem<PlayerEntity> 返回 PlayerEntity
|
||||
/// </summary>
|
||||
public static ITypeSymbol? GetGenericInterfaceTypeArgument(
|
||||
this INamedTypeSymbol typeSymbol,
|
||||
string genericInterfaceName)
|
||||
{
|
||||
var matchingInterface = typeSymbol.AllInterfaces.FirstOrDefault(i =>
|
||||
i.IsGenericType &&
|
||||
i.ConstructedFrom.ToDisplayString() == genericInterfaceName);
|
||||
|
||||
return matchingInterface?.TypeArguments.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Fantasy.SourceGenerator.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// 辅助构建生成代码的工具类
|
||||
/// </summary>
|
||||
internal sealed class SourceCodeBuilder
|
||||
{
|
||||
private readonly StringBuilder _builder;
|
||||
private int _indentLevel;
|
||||
private const string IndentString = " "; // 4 空格缩进
|
||||
|
||||
public SourceCodeBuilder(int indentLevel = 0)
|
||||
{
|
||||
_builder = new StringBuilder();
|
||||
_indentLevel = indentLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加缩进级别
|
||||
/// </summary>
|
||||
public SourceCodeBuilder Indent(int indentLevel = 1)
|
||||
{
|
||||
_indentLevel += indentLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 减少缩进级别
|
||||
/// </summary>
|
||||
public SourceCodeBuilder Unindent()
|
||||
{
|
||||
if (_indentLevel > 0)
|
||||
{
|
||||
_indentLevel--;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Append(string code = "")
|
||||
{
|
||||
_builder.Append(code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一行代码(自动处理缩进)
|
||||
/// </summary>
|
||||
public SourceCodeBuilder AppendLine(string code = "", bool indent = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(code))
|
||||
{
|
||||
_builder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (indent)
|
||||
{
|
||||
for (int i = 0; i < _indentLevel; i++)
|
||||
{
|
||||
_builder.Append(IndentString);
|
||||
}
|
||||
}
|
||||
|
||||
_builder.AppendLine(code);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加代码块开始 {
|
||||
/// </summary>
|
||||
public SourceCodeBuilder OpenBrace()
|
||||
{
|
||||
AppendLine("{");
|
||||
Indent();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加代码块结束 }
|
||||
/// </summary>
|
||||
public SourceCodeBuilder CloseBrace(bool semicolon = false)
|
||||
{
|
||||
Unindent();
|
||||
AppendLine(semicolon ? "};" : "}");
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 using 语句
|
||||
/// </summary>
|
||||
public SourceCodeBuilder AddUsing(string @namespace)
|
||||
{
|
||||
AppendLine($"using {@namespace};");
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加多个 using 语句
|
||||
/// </summary>
|
||||
public SourceCodeBuilder AddUsings(params string[] namespaces)
|
||||
{
|
||||
foreach (var ns in namespaces)
|
||||
{
|
||||
AddUsing(ns);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始命名空间
|
||||
/// </summary>
|
||||
public SourceCodeBuilder BeginNamespace(string @namespace)
|
||||
{
|
||||
AppendLine($"namespace {@namespace}");
|
||||
OpenBrace();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束命名空间
|
||||
/// </summary>
|
||||
public SourceCodeBuilder EndNamespace()
|
||||
{
|
||||
CloseBrace();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始类定义
|
||||
/// </summary>
|
||||
public SourceCodeBuilder BeginClass(string className, string? modifiers = "internal static", string? baseTypes = null)
|
||||
{
|
||||
var classDeclaration = $"{modifiers} class {className}";
|
||||
if (!string.IsNullOrEmpty(baseTypes))
|
||||
{
|
||||
classDeclaration += $" : {baseTypes}";
|
||||
}
|
||||
AppendLine(classDeclaration);
|
||||
OpenBrace();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束类定义
|
||||
/// </summary>
|
||||
public SourceCodeBuilder EndClass()
|
||||
{
|
||||
CloseBrace();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始方法定义
|
||||
/// </summary>
|
||||
public SourceCodeBuilder BeginMethod(string signature)
|
||||
{
|
||||
AppendLine(signature);
|
||||
OpenBrace();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束方法定义
|
||||
/// </summary>
|
||||
public SourceCodeBuilder EndMethod()
|
||||
{
|
||||
CloseBrace();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加注释
|
||||
/// </summary>
|
||||
public SourceCodeBuilder AddComment(string comment)
|
||||
{
|
||||
AppendLine($"// {comment}");
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 XML 文档注释
|
||||
/// </summary>
|
||||
public SourceCodeBuilder AddXmlComment(string summary)
|
||||
{
|
||||
AppendLine("/// <summary>");
|
||||
AppendLine($"/// {summary}");
|
||||
AppendLine("/// </summary>");
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建最终代码
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return _builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空构建器
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_builder.Clear();
|
||||
_indentLevel = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
|
||||
<!-- 帮助 IDE 识别这是一个 Source Generator -->
|
||||
<DevelopmentDependency>true</DevelopmentDependency>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- 为 Unity 构建使用低版本 Roslyn (Unity 2020.2-2022.x 使用 Roslyn 4.0-4.3) -->
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Unity' OR '$(Configuration)' == 'UnityDebug'">
|
||||
<DefineConstants>$(DefineConstants);UNITY_COMPATIBLE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Unity 兼容版本 - 使用 Roslyn 4.0.1 (兼容 Unity 2020.2+) -->
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Unity' OR '$(Configuration)' == 'UnityDebug'">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- .NET 版本 - 使用最新 Roslyn (用于 .NET 8/9 项目) -->
|
||||
<ItemGroup Condition="'$(Configuration)' != 'Unity' AND '$(Configuration)' != 'UnityDebug'">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- 让生成的代码对用户可见 (调试用) -->
|
||||
<CompilerVisibleProperty Include="RootNamespace" />
|
||||
<CompilerVisibleProperty Include="ProjectDir" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,259 @@
|
||||
using Fantasy.SourceGenerator.Common;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Fantasy.SourceGenerator.Generators
|
||||
{
|
||||
/// <summary>
|
||||
/// 程序集初始化器生成器
|
||||
/// 为每个 Fantasy 项目生成 ModuleInitializer,在程序集加载时自动注册到框架
|
||||
/// 支持 Native AOT,无需运行时反射
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class AssemblyInitializerGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// 获取编译信息
|
||||
var compilationProvider = context.CompilationProvider;
|
||||
// 注册源代码输出
|
||||
context.RegisterSourceOutput(compilationProvider, (spc, compilation) =>
|
||||
{
|
||||
GenerateModuleInitializer(spc, compilation);
|
||||
});
|
||||
}
|
||||
|
||||
private static void GenerateModuleInitializer(
|
||||
SourceProductionContext context,
|
||||
Compilation compilation)
|
||||
{
|
||||
// 检查是否定义了 FANTASY_NET 或 FANTASY_UNITY 预编译符号
|
||||
if (!CompilationHelper.HasFantasyDefine(compilation))
|
||||
{
|
||||
// 不是 Fantasy 框架项目,跳过代码生成
|
||||
return;
|
||||
}
|
||||
|
||||
var assemblyName = compilation.AssemblyName ?? "Unknown";
|
||||
// 检测是否是 Unity 环境
|
||||
var isUnity = CompilationHelper.IsUnityCompilation(compilation);
|
||||
var builder = new SourceCodeBuilder();
|
||||
// 添加文件头
|
||||
builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
|
||||
// 添加 using
|
||||
if (isUnity)
|
||||
{
|
||||
builder.AddUsings(
|
||||
"System",
|
||||
"UnityEngine"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddUsings(
|
||||
"System",
|
||||
"System.Runtime.CompilerServices"
|
||||
);
|
||||
}
|
||||
builder.AppendLine();
|
||||
// 开始命名空间
|
||||
builder.BeginNamespace(GeneratorConstants.GeneratedNamespace);
|
||||
// 添加类注释
|
||||
builder.AddXmlComment($"Auto-generated assembly initializer for {assemblyName}");
|
||||
builder.AddXmlComment("This class is automatically invoked when the assembly is loaded via ModuleInitializer");
|
||||
// 开始类定义
|
||||
builder.BeginClass("AssemblyInitializer", "internal static");
|
||||
// 添加字段
|
||||
builder.AppendLine("private static bool _initialized;");
|
||||
builder.AppendLine("private static long _assemblyManifestId;");
|
||||
builder.AppendLine();
|
||||
|
||||
// 生成 ModuleInitializer 方法
|
||||
GenerateInitializeMethod(builder, assemblyName, isUnity);
|
||||
|
||||
// 生成卸载方法
|
||||
GenerateUnloadMethod(builder, isUnity);
|
||||
|
||||
// 结束 AssemblyInitializer 类
|
||||
builder.EndClass();
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
// 生成 AssemblyMarker 类(用于强制加载程序集)
|
||||
// 类名包含程序集名称以避免冲突,将特殊字符替换为下划线
|
||||
var markerClassName = assemblyName.Replace("-", "_").Replace(".", "_") + "_AssemblyMarker";
|
||||
builder.AddXmlComment($"Public marker class for forcing {assemblyName} assembly load");
|
||||
builder.AddXmlComment("Access this type to ensure the assembly is loaded and ModuleInitializer executes");
|
||||
builder.BeginClass(markerClassName, "public static");
|
||||
builder.AppendLine("/// <summary>");
|
||||
builder.AppendLine("/// Call this method to ensure the assembly is loaded");
|
||||
builder.AppendLine("/// This is useful when loading assemblies dynamically via reflection");
|
||||
builder.AppendLine("/// </summary>");
|
||||
builder.BeginMethod("public static void EnsureLoaded()");
|
||||
builder.AppendLine("// Accessing this method ensures the assembly is loaded");
|
||||
builder.AppendLine("// ModuleInitializer will execute automatically when assembly loads");
|
||||
builder.EndMethod();
|
||||
builder.EndClass();
|
||||
|
||||
// 结束命名空间
|
||||
builder.EndNamespace();
|
||||
// 输出源代码
|
||||
context.AddSource("AssemblyInitializer.g.cs", builder.ToString());
|
||||
}
|
||||
|
||||
private static void GenerateInitializeMethod(SourceCodeBuilder builder, string assemblyName, bool isUnity)
|
||||
{
|
||||
if (isUnity)
|
||||
{
|
||||
builder.AddXmlComment(
|
||||
"Unity runtime initializer - automatically called when entering play mode or on app start");
|
||||
builder.AppendLine("[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]");
|
||||
builder.BeginMethod("internal static void Initialize()");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddXmlComment("Module initializer - automatically called when assembly is loaded");
|
||||
builder.AppendLine("[ModuleInitializer]");
|
||||
builder.BeginMethod("internal static void Initialize()");
|
||||
}
|
||||
|
||||
// 防止重复初始化
|
||||
builder.AppendLine("if (_initialized)");
|
||||
builder.OpenBrace();
|
||||
builder.AppendLine("return;");
|
||||
builder.CloseBrace();
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("_initialized = true;");
|
||||
builder.AppendLine();
|
||||
|
||||
// 获取程序集并计算唯一标识
|
||||
builder.AddComment("Get assembly and calculate manifest ID");
|
||||
builder.AppendLine("var assembly = typeof(AssemblyInitializer).Assembly;");
|
||||
builder.AppendLine(
|
||||
$"_assemblyManifestId = Fantasy.Helper.HashCodeHelper.ComputeHash64(assembly.GetName().Name ?? \"{assemblyName}\");");
|
||||
builder.AppendLine();
|
||||
|
||||
// 注册卸载事件(用于热更新支持)
|
||||
builder.AddComment("Register auto-unload for collectible AssemblyLoadContext (hot-reload support)");
|
||||
if (isUnity)
|
||||
{
|
||||
builder.AppendLine(
|
||||
"#if !UNITY_EDITOR && !UNITY_STANDALONE && !UNITY_ANDROID && !UNITY_IOS && !UNITY_WEBGL");
|
||||
}
|
||||
|
||||
builder.AppendLine("var loadContext = System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(assembly);");
|
||||
builder.AppendLine(
|
||||
"if (loadContext != null && loadContext != System.Runtime.Loader.AssemblyLoadContext.Default)");
|
||||
builder.OpenBrace();
|
||||
builder.AppendLine("loadContext.Unloading += OnAssemblyUnloading;");
|
||||
builder.CloseBrace();
|
||||
if (isUnity)
|
||||
{
|
||||
builder.AppendLine("#endif");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
// 声明所有可能的注册器变量
|
||||
builder.AddComment("Declare registrar variables");
|
||||
builder.AppendLine("Fantasy.Assembly.INetworkProtocolRegistrar? networkProtocolRegistrar = null;");
|
||||
builder.AppendLine("Fantasy.Assembly.IEventSystemRegistrar? eventSystemRegistrar = null;");
|
||||
builder.AppendLine("Fantasy.Assembly.IEntitySystemRegistrar? entitySystemRegistrar = null;");
|
||||
builder.AppendLine("Fantasy.Assembly.IMessageHandlerResolver? messageHandlerResolverRegistrar = null;");
|
||||
builder.AppendLine(
|
||||
"Fantasy.Assembly.IEntityTypeCollectionRegistrar? entityTypeCollectionRegistrar = null;");
|
||||
builder.AppendLine(
|
||||
"Fantasy.Assembly.INetworkProtocolOpCodeResolver? networkProtocolOpCodeResolverRegistrar = null;");
|
||||
builder.AppendLine(
|
||||
"Fantasy.Assembly.INetworkProtocolResponseTypeResolver? networkProtocolResponseTypeResolverRegistrar = null;");
|
||||
builder.AppendLine("#if FANTASY_NET", false);
|
||||
builder.AppendLine(
|
||||
"Fantasy.Assembly.ISeparateTableRegistrar? separateTableRegistrar = null;");
|
||||
builder.AppendLine("#endif", false);
|
||||
builder.AppendLine();
|
||||
|
||||
// 尝试创建各个注册器(如果存在)
|
||||
builder.AddComment("Try to create registrars if they were generated in this assembly");
|
||||
GenerateTryCreateRegistrar(builder, "NetworkProtocol", "networkProtocolRegistrar");
|
||||
GenerateTryCreateRegistrar(builder, "EventSystem", "eventSystemRegistrar");
|
||||
GenerateTryCreateRegistrar(builder, "EntitySystem", "entitySystemRegistrar");
|
||||
GenerateTryCreateRegistrar(builder, "MessageHandlerResolver", "messageHandlerResolverRegistrar");
|
||||
GenerateTryCreateRegistrar(builder, "EntityTypeCollection", "entityTypeCollectionRegistrar");
|
||||
GenerateTryCreateRegistrar(builder, "NetworkProtocolOpCodeResolver", "networkProtocolOpCodeResolverRegistrar");
|
||||
GenerateTryCreateRegistrar(builder, "NetworkProtocolResponseTypeResolver", "networkProtocolResponseTypeResolverRegistrar");
|
||||
builder.AppendLine("#if FANTASY_NET", false);
|
||||
GenerateTryCreateRegistrar(builder, "SeparateTable", "separateTableRegistrar");
|
||||
builder.AppendLine("#endif", false);
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
// 注册到框架
|
||||
builder.AddComment("Register complete AssemblyManifest to the framework");
|
||||
builder.AppendLine("#if FANTASY_NET", false);
|
||||
builder.AppendLine("Fantasy.Assembly.AssemblyManifest.Register(");
|
||||
builder.Indent();
|
||||
builder.AppendLine("_assemblyManifestId,");
|
||||
builder.AppendLine("assembly,");
|
||||
builder.AppendLine("networkProtocolRegistrar,");
|
||||
builder.AppendLine("eventSystemRegistrar,");
|
||||
builder.AppendLine("entitySystemRegistrar,");
|
||||
builder.AppendLine("messageHandlerResolverRegistrar,");
|
||||
builder.AppendLine("entityTypeCollectionRegistrar,");
|
||||
builder.AppendLine("separateTableRegistrar,");
|
||||
builder.AppendLine("networkProtocolOpCodeResolverRegistrar,");
|
||||
builder.AppendLine("networkProtocolResponseTypeResolverRegistrar);");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("#endif", false);
|
||||
builder.AppendLine("#if FANTASY_UNITY", false);
|
||||
builder.AppendLine("Fantasy.Assembly.AssemblyManifest.Register(");
|
||||
builder.Indent();
|
||||
builder.AppendLine("_assemblyManifestId,");
|
||||
builder.AppendLine("assembly,");
|
||||
builder.AppendLine("networkProtocolRegistrar,");
|
||||
builder.AppendLine("eventSystemRegistrar,");
|
||||
builder.AppendLine("entitySystemRegistrar,");
|
||||
builder.AppendLine("messageHandlerResolverRegistrar,");
|
||||
builder.AppendLine("entityTypeCollectionRegistrar,");
|
||||
builder.AppendLine("networkProtocolOpCodeResolverRegistrar,");
|
||||
builder.AppendLine("networkProtocolResponseTypeResolverRegistrar);");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("#endif", false);
|
||||
builder.EndMethod();
|
||||
}
|
||||
|
||||
private static void GenerateUnloadMethod(SourceCodeBuilder builder, bool isUnity)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.AddXmlComment("Called when AssemblyLoadContext is unloading (for hot-reload support)");
|
||||
|
||||
// Unity 环境下,AssemblyLoadContext 仅在非编辑器/非独立平台可用
|
||||
if (isUnity)
|
||||
{
|
||||
builder.AppendLine("#if !UNITY_EDITOR && !UNITY_STANDALONE && !UNITY_ANDROID && !UNITY_IOS && !UNITY_WEBGL");
|
||||
}
|
||||
|
||||
builder.BeginMethod("private static void OnAssemblyUnloading(System.Runtime.Loader.AssemblyLoadContext context)");
|
||||
|
||||
builder.AddComment("Unregister from framework");
|
||||
builder.AppendLine("if (_assemblyManifestId != 0)");
|
||||
builder.OpenBrace();
|
||||
builder.AppendLine("Fantasy.Assembly.AssemblyManifest.Unregister(_assemblyManifestId);");
|
||||
builder.CloseBrace();
|
||||
|
||||
builder.EndMethod();
|
||||
|
||||
if (isUnity)
|
||||
{
|
||||
builder.AppendLine("#endif");
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateTryCreateRegistrar(
|
||||
SourceCodeBuilder builder,
|
||||
string registrarName,
|
||||
string variableName)
|
||||
{
|
||||
var typeName = $"Fantasy.Generated.{registrarName}Registrar";
|
||||
builder.AppendLine($"{variableName} = new {typeName}();");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
using Fantasy.SourceGenerator.Common;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
|
||||
namespace Fantasy.SourceGenerator.Generators
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity System 注册代码生成器
|
||||
/// 自动生成 EntityComponent 所需的 System 注册代码,替代运行时反射
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public partial class EntitySystemGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// 查找所有实现了 IEntitySystem 相关接口的类
|
||||
var systemTypes = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: static (node, _) => IsSystemClass(node),
|
||||
transform: static (ctx, _) => GetSystemTypeInfo(ctx))
|
||||
.Where(static info => info != null)
|
||||
.Collect();
|
||||
// 组合编译信息和找到的类型
|
||||
var compilationAndTypes = context.CompilationProvider.Combine(systemTypes);
|
||||
// 注册源代码输出
|
||||
context.RegisterSourceOutput(compilationAndTypes, static (spc, source) =>
|
||||
{
|
||||
// 检查1: 是否定义了 FANTASY_NET 或 FANTASY_UNITY 预编译符号
|
||||
if (!CompilationHelper.HasFantasyDefine(source.Left))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查2: 是否引用了 Fantasy 框架的核心类型
|
||||
if (source.Left.GetTypeByMetadataName("Fantasy.Assembly.IEntitySystemRegistrar") == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GenerateRegistrationCode(spc, source.Left, source.Right!);
|
||||
});
|
||||
}
|
||||
|
||||
private static EntitySystemTypeInfo? GetSystemTypeInfo(GeneratorSyntaxContext context)
|
||||
{
|
||||
var classDecl = (ClassDeclarationSyntax)context.Node;
|
||||
|
||||
if (context.SemanticModel.GetDeclaredSymbol(classDecl) is not INamedTypeSymbol symbol || !symbol.IsInstantiable())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var baseType = symbol.BaseType;
|
||||
|
||||
if (!baseType.IsGenericType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return baseType.Name switch
|
||||
{
|
||||
"AwakeSystem" => EntitySystemTypeInfo.Create(EntitySystemType.AwakeSystem, symbol),
|
||||
"UpdateSystem" => EntitySystemTypeInfo.Create(EntitySystemType.UpdateSystem, symbol),
|
||||
"DestroySystem" => EntitySystemTypeInfo.Create(EntitySystemType.DestroySystem, symbol),
|
||||
"DeserializeSystem" => EntitySystemTypeInfo.Create(EntitySystemType.DeserializeSystem, symbol),
|
||||
"LateUpdateSystem" => EntitySystemTypeInfo.Create(EntitySystemType.LateUpdateSystem, symbol),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private static void GenerateRegistrationCode(
|
||||
SourceProductionContext context,
|
||||
Compilation compilation,
|
||||
IEnumerable<EntitySystemTypeInfo> systemTypes)
|
||||
{
|
||||
var entitySystemTypeInfos = systemTypes.ToList();
|
||||
// 获取当前程序集名称(仅用于注释)
|
||||
var assemblyName = compilation.AssemblyName ?? "Unknown";
|
||||
// 生成代码文件
|
||||
var builder = new SourceCodeBuilder();
|
||||
// 添加文件头
|
||||
builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
|
||||
// 添加 using
|
||||
builder.AddUsings(
|
||||
"System",
|
||||
"System.Collections.Generic",
|
||||
"Fantasy.Assembly",
|
||||
"Fantasy.Entitas",
|
||||
"Fantasy.Entitas.Interface"
|
||||
);
|
||||
builder.AppendLine();
|
||||
// 开始命名空间(固定使用 Fantasy.Generated)
|
||||
builder.BeginNamespace("Fantasy.Generated");
|
||||
// 开始类定义(实现 IEntitySystemRegistrar 接口)
|
||||
builder.AddXmlComment($"Auto-generated Entity System registration class for {assemblyName}");
|
||||
builder.BeginClass("EntitySystemRegistrar", "internal sealed", "IEntitySystemRegistrar");
|
||||
// 生成字段用于存储 System 实例
|
||||
GenerateFields(builder, entitySystemTypeInfos);
|
||||
// 生成注册方法
|
||||
GenerateRegisterMethod(builder, entitySystemTypeInfos);
|
||||
// 生成反注册方法
|
||||
GenerateUnRegisterMethod(builder, entitySystemTypeInfos);
|
||||
// 结束类和命名空间
|
||||
builder.EndClass();
|
||||
builder.EndNamespace();
|
||||
// 输出源代码
|
||||
context.AddSource("EntitySystemRegistrar.g.cs", builder.ToString());
|
||||
}
|
||||
|
||||
private static void GenerateFields(SourceCodeBuilder builder, List<EntitySystemTypeInfo> entitySystemTypeInfos)
|
||||
{
|
||||
builder.AddComment("Store registered entity system for UnRegister");
|
||||
|
||||
if (!entitySystemTypeInfos.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var eventSystemTypeInfo in entitySystemTypeInfos)
|
||||
{
|
||||
var fieldName = $"_{eventSystemTypeInfo.TypeName.ToCamelCase()}";
|
||||
builder.AppendLine($"private {eventSystemTypeInfo.GlobalTypeFullName} {fieldName} = new {eventSystemTypeInfo.GlobalTypeFullName}();");
|
||||
builder.AppendLine($"private const long _typeHashCode{fieldName} = {eventSystemTypeInfo.EntityTypeHashCode};");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static void GenerateRegisterMethod(SourceCodeBuilder builder, List<EntitySystemTypeInfo> entitySystemTypeInfos)
|
||||
{
|
||||
builder.AddXmlComment("Register all Entity Systems to the dictionaries");
|
||||
builder.AppendLine("#if FANTASY_NET", false);
|
||||
builder.BeginMethod(
|
||||
"public void RegisterSystems(" +
|
||||
"Dictionary<long, Action<Entity>> awakeSystems, " +
|
||||
"Dictionary<long, Action<Entity>> updateSystems, " +
|
||||
"Dictionary<long, Action<Entity>> destroySystems, " +
|
||||
"Dictionary<long, Action<Entity>> deserializeSystems)");
|
||||
builder.AppendLine("#endif", false);
|
||||
builder.Unindent();
|
||||
builder.AppendLine("#if FANTASY_UNITY", false);
|
||||
builder.BeginMethod(
|
||||
"public void RegisterSystems(" +
|
||||
"Dictionary<long, Action<Entity>> awakeSystems, " +
|
||||
"Dictionary<long, Action<Entity>> updateSystems, " +
|
||||
"Dictionary<long, Action<Entity>> destroySystems, " +
|
||||
"Dictionary<long, Action<Entity>> deserializeSystems, " +
|
||||
"Dictionary<long, Action<Entity>> lateUpdateSystems)");
|
||||
builder.AppendLine("#endif", false);
|
||||
|
||||
if (entitySystemTypeInfos.Any())
|
||||
{
|
||||
foreach (var systemTypeInfo in entitySystemTypeInfos)
|
||||
{
|
||||
var fieldName = $"_{systemTypeInfo.TypeName.ToCamelCase()}";
|
||||
|
||||
switch (systemTypeInfo.EntitySystemType)
|
||||
{
|
||||
case EntitySystemType.AwakeSystem:
|
||||
{
|
||||
builder.AppendLine($"awakeSystems.Add(_typeHashCode{fieldName}, {fieldName}.Invoke);");
|
||||
continue;
|
||||
}
|
||||
case EntitySystemType.UpdateSystem:
|
||||
{
|
||||
builder.AppendLine($"updateSystems.Add(_typeHashCode{fieldName}, {fieldName}.Invoke);");
|
||||
continue;
|
||||
}
|
||||
case EntitySystemType.DestroySystem:
|
||||
{
|
||||
builder.AppendLine($"destroySystems.Add(_typeHashCode{fieldName}, {fieldName}.Invoke);");
|
||||
continue;
|
||||
}
|
||||
case EntitySystemType.DeserializeSystem:
|
||||
{
|
||||
builder.AppendLine($"deserializeSystems.Add(_typeHashCode{fieldName}, {fieldName}.Invoke);");
|
||||
continue;
|
||||
}
|
||||
case EntitySystemType.LateUpdateSystem:
|
||||
{
|
||||
builder.AppendLine("#if FANTASY_UNITY", false);
|
||||
builder.AppendLine($"awakeSystems.Add(_typeHashCode{fieldName}, {fieldName}.Invoke);");
|
||||
builder.AppendLine("#endif", false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.EndMethod();
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static void GenerateUnRegisterMethod(SourceCodeBuilder builder, List<EntitySystemTypeInfo> entitySystemTypeInfos)
|
||||
{
|
||||
builder.AddXmlComment("Unregister all Entity Systems from the dictionaries");
|
||||
builder.AppendLine("#if FANTASY_NET", false);
|
||||
builder.BeginMethod(
|
||||
"public void UnRegisterSystems(" +
|
||||
"Dictionary<long, Action<Entity>> awakeSystems, " +
|
||||
"Dictionary<long, Action<Entity>> updateSystems, " +
|
||||
"Dictionary<long, Action<Entity>> destroySystems, " +
|
||||
"Dictionary<long, Action<Entity>> deserializeSystems)");
|
||||
builder.AppendLine("#endif", false);
|
||||
builder.Unindent();
|
||||
builder.AppendLine("#if FANTASY_UNITY", false);
|
||||
builder.BeginMethod(
|
||||
"public void UnRegisterSystems(" +
|
||||
"Dictionary<long, Action<Entity>> awakeSystems, " +
|
||||
"Dictionary<long, Action<Entity>> updateSystems, " +
|
||||
"Dictionary<long, Action<Entity>> destroySystems, " +
|
||||
"Dictionary<long, Action<Entity>> deserializeSystems, " +
|
||||
"Dictionary<long, Action<Entity>> lateUpdateSystems)");
|
||||
builder.AppendLine("#endif", false);
|
||||
|
||||
if (entitySystemTypeInfos.Any())
|
||||
{
|
||||
foreach (var systemTypeInfo in entitySystemTypeInfos)
|
||||
{
|
||||
var fieldName = $"_{systemTypeInfo.TypeName.ToCamelCase()}";
|
||||
|
||||
switch (systemTypeInfo.EntitySystemType)
|
||||
{
|
||||
case EntitySystemType.AwakeSystem:
|
||||
{
|
||||
builder.AppendLine($"awakeSystems.Remove(_typeHashCode{fieldName});");
|
||||
continue;
|
||||
}
|
||||
case EntitySystemType.UpdateSystem:
|
||||
{
|
||||
builder.AppendLine($"updateSystems.Remove(_typeHashCode{fieldName});");
|
||||
continue;
|
||||
}
|
||||
case EntitySystemType.DestroySystem:
|
||||
{
|
||||
builder.AppendLine($"destroySystems.Remove(_typeHashCode{fieldName});");
|
||||
continue;
|
||||
}
|
||||
case EntitySystemType.DeserializeSystem:
|
||||
{
|
||||
builder.AppendLine($"deserializeSystems.Remove(_typeHashCode{fieldName});");
|
||||
continue;
|
||||
}
|
||||
case EntitySystemType.LateUpdateSystem:
|
||||
{
|
||||
builder.AppendLine("#if FANTASY_UNITY", false);
|
||||
builder.AppendLine($"lateUpdateSystem.Remove(_typeHashCode{fieldName});");
|
||||
builder.AppendLine("#endif", false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.EndMethod();
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static bool IsSystemClass(SyntaxNode node)
|
||||
{
|
||||
if (node is not ClassDeclarationSyntax classDecl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 必须有基类型列表(继承抽象类)
|
||||
if (classDecl.BaseList == null || classDecl.BaseList.Types.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 快速检查是否包含可能的 EntitySystem 基类名称
|
||||
var baseListText = classDecl.BaseList.ToString();
|
||||
return baseListText.Contains("AwakeSystem") ||
|
||||
baseListText.Contains("UpdateSystem") ||
|
||||
baseListText.Contains("DestroySystem") ||
|
||||
baseListText.Contains("DeserializeSystem") ||
|
||||
baseListText.Contains("LateUpdateSystem");
|
||||
}
|
||||
|
||||
private enum EntitySystemType
|
||||
{
|
||||
None,
|
||||
AwakeSystem,
|
||||
UpdateSystem,
|
||||
DestroySystem,
|
||||
DeserializeSystem,
|
||||
LateUpdateSystem
|
||||
}
|
||||
|
||||
private sealed class EntitySystemTypeInfo
|
||||
{
|
||||
public readonly EntitySystemType EntitySystemType;
|
||||
public readonly string GlobalTypeFullName;
|
||||
public readonly string TypeFullName;
|
||||
public readonly string TypeName;
|
||||
public readonly long EntityTypeHashCode; // 预计算的实体类型哈希值
|
||||
public readonly string EntityTypeFullName; // 实体类型的完整名称(用于注释)
|
||||
|
||||
private EntitySystemTypeInfo(
|
||||
EntitySystemType entitySystemType,
|
||||
string globalTypeFullName,
|
||||
string typeFullName,
|
||||
string typeName,
|
||||
long entityTypeHashCode,
|
||||
string entityTypeFullName)
|
||||
{
|
||||
EntitySystemType = entitySystemType;
|
||||
GlobalTypeFullName = globalTypeFullName;
|
||||
TypeFullName = typeFullName;
|
||||
TypeName = typeName;
|
||||
EntityTypeHashCode = entityTypeHashCode;
|
||||
EntityTypeFullName = entityTypeFullName;
|
||||
}
|
||||
|
||||
public static EntitySystemTypeInfo Create(EntitySystemType entitySystemType, INamedTypeSymbol symbol)
|
||||
{
|
||||
// 获取泛型参数 T (例如:AwakeSystem<T> 中的 T)
|
||||
var entityType = symbol.BaseType?.TypeArguments.FirstOrDefault();
|
||||
var entityTypeFullName = entityType?.GetFullName(false) ?? "Unknown";
|
||||
|
||||
// 使用与运行时相同的算法计算哈希值
|
||||
var entityTypeHashCode = HashCodeHelper.ComputeHash64(entityTypeFullName);
|
||||
|
||||
return new EntitySystemTypeInfo(
|
||||
entitySystemType,
|
||||
symbol.GetFullName(),
|
||||
symbol.GetFullName(false),
|
||||
symbol.Name,
|
||||
entityTypeHashCode,
|
||||
entityTypeFullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
using Fantasy.SourceGenerator.Common;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Fantasy.SourceGenerator.Generators
|
||||
{
|
||||
[Generator]
|
||||
public partial class EntityTypeCollectionGenerate : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// 查找所有实现了 EventSystem 相关抽象类的类
|
||||
var protoBufTypes = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: static (node, _) => IsEntityClass(node),
|
||||
transform: static (ctx, _) => GetEntityTypeInfo(ctx))
|
||||
.Where(static info => info != null)
|
||||
.Collect();
|
||||
// 组合编译信息和找到的类型
|
||||
var compilationAndTypes = context.CompilationProvider.Combine(protoBufTypes);
|
||||
// 注册源代码输出
|
||||
context.RegisterSourceOutput(compilationAndTypes, static (spc, source) =>
|
||||
{
|
||||
// 检查1: 是否定义了 FANTASY_NET 或 FANTASY_UNITY 预编译符号
|
||||
if (!CompilationHelper.HasFantasyDefine(source.Left))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查2: 是否引用了 Fantasy 框架的核心类型
|
||||
if (source.Left.GetTypeByMetadataName("Fantasy.Assembly.IEntityTypeCollectionRegistrar") == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GenerateRegistrationCode(spc, source.Left, source.Right!);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成注册代码
|
||||
/// </summary>
|
||||
private static void GenerateRegistrationCode(
|
||||
SourceProductionContext context,
|
||||
Compilation compilation,
|
||||
IEnumerable<EntityTypeInfo> entityTypeInfos)
|
||||
{
|
||||
var entityTypeList = entityTypeInfos.ToList();
|
||||
// 获取当前程序集名称(仅用于注释)
|
||||
var assemblyName = compilation.AssemblyName ?? "Unknown";
|
||||
var builder = new SourceCodeBuilder();
|
||||
// 添加文件头
|
||||
builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
|
||||
// 添加 using
|
||||
builder.AddUsings(
|
||||
"System",
|
||||
"System.Collections.Generic",
|
||||
"Fantasy.Assembly",
|
||||
"Fantasy.DataStructure.Collection",
|
||||
"Fantasy.Event"
|
||||
);
|
||||
builder.AppendLine();
|
||||
// 开始命名空间(固定使用 Fantasy.Generated)
|
||||
builder.BeginNamespace("Fantasy.Generated");
|
||||
// 开始类定义(实现 IEntityTypeCollectionRegistrar 接口)
|
||||
builder.AddXmlComment($"Automatically generated Entity Type collection class for {assemblyName}");
|
||||
builder.BeginClass("EntityTypeCollectionRegistrar", "internal sealed", "IEntityTypeCollectionRegistrar");
|
||||
// 生成 GetEntityTypes 方法
|
||||
GetEntityTypesMethod(builder, entityTypeList);
|
||||
builder.AppendLine();
|
||||
// 结束类和命名空间
|
||||
builder.EndClass();
|
||||
builder.EndNamespace();
|
||||
// 输出源代码
|
||||
context.AddSource("EntityTypeCollectionRegistrar.g.cs", builder.ToString());
|
||||
}
|
||||
|
||||
private static void GetEntityTypesMethod(SourceCodeBuilder builder, List<EntityTypeInfo> entityTypeInfos)
|
||||
{
|
||||
builder.AddXmlComment("All Entity Types");
|
||||
builder.BeginMethod("public List<Type> GetEntityTypes()");
|
||||
|
||||
if (entityTypeInfos.Any())
|
||||
{
|
||||
builder.AppendLine($"return new List<Type>({entityTypeInfos.Count})");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
foreach (var system in entityTypeInfos)
|
||||
{
|
||||
builder.AppendLine($"typeof({system.EntityTypeFullName}),");
|
||||
}
|
||||
builder.Unindent();
|
||||
builder.AppendLine("};");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"return new List<Type>();");
|
||||
}
|
||||
|
||||
builder.EndMethod();
|
||||
}
|
||||
|
||||
private static bool IsEntityClass(SyntaxNode node)
|
||||
{
|
||||
if (node is not ClassDeclarationSyntax classDecl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 必须有基类型列表(实现接口)
|
||||
return classDecl.BaseList != null && classDecl.BaseList.Types.Any();
|
||||
}
|
||||
|
||||
private static EntityTypeInfo? GetEntityTypeInfo(GeneratorSyntaxContext context)
|
||||
{
|
||||
var classDecl = (ClassDeclarationSyntax)context.Node;
|
||||
var semanticModel = context.SemanticModel;
|
||||
|
||||
// 检查是否继承自 Entity 或 Entity<T>
|
||||
if (!InheritsFromEntity(classDecl, semanticModel))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var symbol = context.SemanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
|
||||
|
||||
if (symbol == null || !symbol.IsInstantiable())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new EntityTypeInfo(
|
||||
symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||
symbol.Name
|
||||
);
|
||||
}
|
||||
|
||||
private static bool InheritsFromEntity(ClassDeclarationSyntax classDecl, SemanticModel semanticModel)
|
||||
{
|
||||
if (classDecl.BaseList == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var baseType in classDecl.BaseList.Types)
|
||||
{
|
||||
var typeInfo = semanticModel.GetTypeInfo(baseType.Type);
|
||||
var baseTypeSymbol = typeInfo.Type as INamedTypeSymbol;
|
||||
|
||||
if (baseTypeSymbol == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否是 Entity(非泛型)
|
||||
if (baseTypeSymbol.Name == "Entity" && baseTypeSymbol.Arity == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否是 Entity<T>(泛型)
|
||||
if (baseTypeSymbol.IsGenericType)
|
||||
{
|
||||
var originalDef = baseTypeSymbol.OriginalDefinition;
|
||||
if (originalDef.Name == "Entity" && originalDef.Arity == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private sealed record EntityTypeInfo(
|
||||
string EntityTypeFullName,
|
||||
string EntityTypeName
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
using Fantasy.SourceGenerator.Common;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
// ReSharper disable AssignNullToNotNullAttribute
|
||||
|
||||
namespace Fantasy.SourceGenerator.Generators
|
||||
{
|
||||
/// <summary>
|
||||
/// Event System 注册代码生成器
|
||||
/// 自动生成 EventComponent 所需的 Event System 注册代码,替代运行时反射
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public partial class EventSystemGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// 查找所有实现了 EventSystem 相关抽象类的类
|
||||
var eventSystemTypes = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: static (node, _) => IsEventSystemClass(node),
|
||||
transform: static (ctx, _) => GetEventSystemTypeInfo(ctx))
|
||||
.Where(static info => info != null)
|
||||
.Collect();
|
||||
// 组合编译信息和找到的类型
|
||||
var compilationAndTypes = context.CompilationProvider.Combine(eventSystemTypes);
|
||||
// 注册源代码输出
|
||||
context.RegisterSourceOutput(compilationAndTypes, static (spc, source) =>
|
||||
{
|
||||
// 检查1: 是否定义了 FANTASY_NET 或 FANTASY_UNITY 预编译符号
|
||||
if (!CompilationHelper.HasFantasyDefine(source.Left))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查2: 是否引用了 Fantasy 框架的核心类型
|
||||
if (source.Left.GetTypeByMetadataName("Fantasy.Assembly.IEventSystemRegistrar") == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GenerateRegistrationCode(spc, source.Left, source.Right!);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提取 EventSystem 类型信息
|
||||
/// </summary>
|
||||
private static EventSystemTypeInfo? GetEventSystemTypeInfo(GeneratorSyntaxContext context)
|
||||
{
|
||||
var classDecl = (ClassDeclarationSyntax)context.Node;
|
||||
|
||||
if (context.SemanticModel.GetDeclaredSymbol(classDecl) is not INamedTypeSymbol symbol || !symbol.IsInstantiable())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (symbol.BaseType == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var baseType = symbol.BaseType;
|
||||
|
||||
if (!baseType.IsGenericType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return baseType.Name switch
|
||||
{
|
||||
"EventSystem" => EventSystemTypeInfo.Create(EventSystemType.EventSystem, symbol),
|
||||
"AsyncEventSystem" => EventSystemTypeInfo.Create(EventSystemType.AsyncEventSystem, symbol),
|
||||
"SphereEventSystem" => EventSystemTypeInfo.Create(EventSystemType.SphereEventSystem, symbol),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成注册代码
|
||||
/// </summary>
|
||||
private static void GenerateRegistrationCode(
|
||||
SourceProductionContext context,
|
||||
Compilation compilation,
|
||||
IEnumerable<EventSystemTypeInfo> eventSystemTypes)
|
||||
{
|
||||
var eventSystems = eventSystemTypes.ToList();
|
||||
// 获取当前程序集名称(仅用于注释)
|
||||
var assemblyName = compilation.AssemblyName ?? "Unknown";
|
||||
// 生成代码文件
|
||||
var builder = new SourceCodeBuilder();
|
||||
// 添加文件头
|
||||
builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
|
||||
// 添加 using
|
||||
builder.AddUsings(
|
||||
"System",
|
||||
"System.Collections.Generic",
|
||||
"Fantasy.Assembly",
|
||||
"Fantasy.DataStructure.Collection",
|
||||
"Fantasy.Event"
|
||||
);
|
||||
builder.AppendLine();
|
||||
// 开始命名空间(固定使用 Fantasy.Generated)
|
||||
builder.BeginNamespace("Fantasy.Generated");
|
||||
// 开始类定义(实现 IEventSystemRegistrar 接口)
|
||||
builder.AddXmlComment($"Auto-generated Event System registration class for {assemblyName}");
|
||||
builder.BeginClass("EventSystemRegistrar", "internal sealed", "IEventSystemRegistrar");
|
||||
// 生成字段用于存储已注册的事件处理器(用于 UnRegister)
|
||||
GenerateFields(builder, eventSystems);
|
||||
// 生成 RegisterSystems 方法
|
||||
GenerateRegisterMethod(builder, eventSystems);
|
||||
// 生成 UnRegisterSystems 方法
|
||||
GenerateUnRegisterMethod(builder, eventSystems);
|
||||
// 生成 Dispose 方法
|
||||
GenerateDisposeMethod(builder, eventSystems);
|
||||
// 结束类和命名空间
|
||||
builder.EndClass();
|
||||
builder.EndNamespace();
|
||||
// 输出源代码
|
||||
context.AddSource("EventSystemRegistrar.g.cs", builder.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成字段,用于存储已注册的事件处理器实例
|
||||
/// </summary>
|
||||
private static void GenerateFields(SourceCodeBuilder builder, List<EventSystemTypeInfo> eventSystems)
|
||||
{
|
||||
builder.AddComment("Store registered event handlers for UnRegister");
|
||||
|
||||
if (!eventSystems.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var eventSystemTypeInfo in eventSystems)
|
||||
{
|
||||
var fieldName = $"_{eventSystemTypeInfo.TypeName.ToCamelCase()}";
|
||||
builder.AppendLine($"private {eventSystemTypeInfo.TypeFullName} {fieldName} = new {eventSystemTypeInfo.TypeFullName}();");
|
||||
}
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成 RegisterSystems 方法
|
||||
/// </summary>
|
||||
private static void GenerateRegisterMethod(SourceCodeBuilder builder, List<EventSystemTypeInfo> eventSystems)
|
||||
{
|
||||
builder.AddXmlComment("Register all Event Systems to the containers");
|
||||
builder.BeginMethod(
|
||||
"public void RegisterSystems(" +
|
||||
"OneToManyList<RuntimeTypeHandle, IEvent> events, " +
|
||||
"OneToManyList<RuntimeTypeHandle, IEvent> asyncEvents, " +
|
||||
"OneToManyList<RuntimeTypeHandle, IEvent> sphereEvents)");
|
||||
|
||||
if (eventSystems.Any())
|
||||
{
|
||||
foreach (var eventSystemTypeInfo in eventSystems)
|
||||
{
|
||||
var fieldName = $"_{eventSystemTypeInfo.TypeName.ToCamelCase()}";
|
||||
|
||||
switch (eventSystemTypeInfo.EventSystemType)
|
||||
{
|
||||
case EventSystemType.EventSystem:
|
||||
{
|
||||
builder.AppendLine($"events.Add({fieldName}.EventType().TypeHandle, {fieldName});");
|
||||
continue;
|
||||
}
|
||||
case EventSystemType.AsyncEventSystem:
|
||||
{
|
||||
builder.AppendLine($"asyncEvents.Add({fieldName}.EventType().TypeHandle, {fieldName});");
|
||||
continue;
|
||||
}
|
||||
case EventSystemType.SphereEventSystem:
|
||||
{
|
||||
builder.AppendLine($"sphereEvents.Add({fieldName}.EventType().TypeHandle, {fieldName});");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.EndMethod();
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成 UnRegisterSystems 方法
|
||||
/// </summary>
|
||||
private static void GenerateUnRegisterMethod(SourceCodeBuilder builder, List<EventSystemTypeInfo> eventSystems)
|
||||
{
|
||||
builder.AddXmlComment("Unregister all Event Systems from the containers (called on hot reload)");
|
||||
builder.BeginMethod(
|
||||
"public void UnRegisterSystems(" +
|
||||
"OneToManyList<RuntimeTypeHandle, IEvent> events, " +
|
||||
"OneToManyList<RuntimeTypeHandle, IEvent> asyncEvents, " +
|
||||
"OneToManyList<RuntimeTypeHandle, IEvent> sphereEvents)");
|
||||
|
||||
if (eventSystems.Any())
|
||||
{
|
||||
foreach (var eventSystemTypeInfo in eventSystems)
|
||||
{
|
||||
var fieldName = $"_{eventSystemTypeInfo.TypeName.ToCamelCase()}";
|
||||
|
||||
switch (eventSystemTypeInfo.EventSystemType)
|
||||
{
|
||||
case EventSystemType.EventSystem:
|
||||
{
|
||||
builder.AppendLine($"events.RemoveValue({fieldName}.EventType().TypeHandle, {fieldName});");
|
||||
continue;
|
||||
}
|
||||
case EventSystemType.AsyncEventSystem:
|
||||
{
|
||||
builder.AppendLine($"asyncEvents.RemoveValue({fieldName}.EventType().TypeHandle, {fieldName});");
|
||||
continue;
|
||||
}
|
||||
case EventSystemType.SphereEventSystem:
|
||||
{
|
||||
builder.AppendLine($"sphereEvents.RemoveValue({fieldName}.EventType().TypeHandle, {fieldName});");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.EndMethod();
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成 Dispose 方法
|
||||
/// </summary>
|
||||
private static void GenerateDisposeMethod(SourceCodeBuilder builder, List<EventSystemTypeInfo> eventSystems)
|
||||
{
|
||||
builder.AddXmlComment("Dispose all resources");
|
||||
builder.BeginMethod("public void Dispose()");
|
||||
builder.AddComment("Clear all references");
|
||||
|
||||
if (eventSystems.Any())
|
||||
{
|
||||
foreach (var eventSystemTypeInfo in eventSystems)
|
||||
{
|
||||
var fieldName = $"_{eventSystemTypeInfo.TypeName.ToCamelCase()}";
|
||||
builder.AppendLine($"{fieldName} = null;");
|
||||
}
|
||||
}
|
||||
|
||||
builder.EndMethod();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快速判断语法节点是否可能是 EventSystem 类
|
||||
/// </summary>
|
||||
private static bool IsEventSystemClass(SyntaxNode node)
|
||||
{
|
||||
if (node is not ClassDeclarationSyntax classDecl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 必须有基类型列表(继承抽象类)
|
||||
if (classDecl.BaseList == null || classDecl.BaseList.Types.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 快速检查是否包含可能的 EventSystem 基类名称
|
||||
var baseListText = classDecl.BaseList.ToString();
|
||||
return baseListText.Contains("AwakeSystem") ||
|
||||
baseListText.Contains("AsyncEventSystem") ||
|
||||
baseListText.Contains("SphereEventSystem");
|
||||
}
|
||||
|
||||
private enum EventSystemType
|
||||
{
|
||||
None,
|
||||
EventSystem, // EventSystem<T> 事件类
|
||||
AsyncEventSystem, // AsyncEventSystem<T> 事件类
|
||||
SphereEventSystem, // SphereEventSystem<T> 事件类
|
||||
}
|
||||
|
||||
private sealed class EventSystemTypeInfo
|
||||
{
|
||||
public readonly EventSystemType EventSystemType;
|
||||
public readonly string TypeFullName;
|
||||
public readonly string TypeName;
|
||||
|
||||
private EventSystemTypeInfo(EventSystemType eventSystemType, string typeFullName, string typeName)
|
||||
{
|
||||
EventSystemType = eventSystemType;
|
||||
TypeFullName = typeFullName;
|
||||
TypeName = typeName;
|
||||
}
|
||||
|
||||
public static EventSystemTypeInfo Create(EventSystemType eventSystemType, INamedTypeSymbol symbol)
|
||||
{
|
||||
return new EventSystemTypeInfo(
|
||||
eventSystemType,
|
||||
symbol.GetFullName(),
|
||||
symbol.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
using Fantasy.SourceGenerator.Common;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Fantasy.SourceGenerator.Generators
|
||||
{
|
||||
[Generator]
|
||||
public sealed class MessageHandlerGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// 查找所有实现了消息相关接口的类
|
||||
var messageTypes = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: static (node, _) => IsMessageHandlerClass(node),
|
||||
transform: static (ctx, _) => GetMessageTypeInfo(ctx))
|
||||
.Where(static info => info != null)
|
||||
.Collect();
|
||||
// 组合编译信息和找到的类型
|
||||
var compilationAndTypes = context.CompilationProvider.Combine(messageTypes);
|
||||
// 注册源代码输出
|
||||
context.RegisterSourceOutput(compilationAndTypes, static (spc, source) =>
|
||||
{
|
||||
// 检查1: 是否定义了 FANTASY_NET 或 FANTASY_UNITY 预编译符号
|
||||
if (!CompilationHelper.HasFantasyDefine(source.Left))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查2: 是否引用了 Fantasy 框架的核心类型
|
||||
if (source.Left.GetTypeByMetadataName("Fantasy.Assembly.INetworkProtocolRegistrar") == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GenerateRegistrationCode(spc, source.Left, source.Right!);
|
||||
});
|
||||
}
|
||||
|
||||
private static void GenerateRegistrationCode(
|
||||
SourceProductionContext context,
|
||||
Compilation compilation,
|
||||
IEnumerable<MessageHandlerInfo> messageHandlerInfos)
|
||||
{
|
||||
var messageHandlers = new List<MessageHandlerInfo>();
|
||||
var routeMessageHandlers = new List<MessageHandlerInfo>();
|
||||
|
||||
foreach (var messageHandlerInfo in messageHandlerInfos)
|
||||
{
|
||||
switch (messageHandlerInfo.HandlerType)
|
||||
{
|
||||
case HandlerType.MessageHandler:
|
||||
{
|
||||
messageHandlers.Add(messageHandlerInfo);
|
||||
break;
|
||||
}
|
||||
case HandlerType.RouteMessageHandler:
|
||||
{
|
||||
routeMessageHandlers.Add(messageHandlerInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var assemblyName = compilation.AssemblyName ?? "Unknown";
|
||||
var builder = new SourceCodeBuilder();
|
||||
builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
|
||||
builder.AddUsings(
|
||||
"System",
|
||||
"System.Collections.Generic",
|
||||
"Fantasy.Assembly",
|
||||
"Fantasy.DataStructure.Dictionary",
|
||||
"Fantasy.Network.Interface",
|
||||
"Fantasy.Network",
|
||||
"Fantasy.Entitas",
|
||||
"Fantasy.Async",
|
||||
"System.Runtime.CompilerServices"
|
||||
);
|
||||
builder.AppendLine();
|
||||
builder.BeginNamespace("Fantasy.Generated");
|
||||
builder.AddXmlComment($"Auto-generated message handler registration class for {assemblyName}");
|
||||
builder.BeginClass("MessageHandlerResolverRegistrar", "internal sealed", "IMessageHandlerResolver");
|
||||
// 生成字段用于存储已注册的实例(用于 UnRegister)
|
||||
GenerateFields(builder, messageHandlers, routeMessageHandlers);
|
||||
// 生成 Register 方法
|
||||
GenerateRegistrationCode(builder, messageHandlers, routeMessageHandlers);
|
||||
// 结束类和命名空间
|
||||
builder.EndClass();
|
||||
builder.EndNamespace();
|
||||
// 输出源代码
|
||||
context.AddSource("MessageHandlerResolverRegistrar.g.cs", builder.ToString());
|
||||
}
|
||||
|
||||
private static void GenerateFields(SourceCodeBuilder builder, List<MessageHandlerInfo> messageHandlers, List<MessageHandlerInfo> routeMessageHandlers)
|
||||
{
|
||||
foreach (var messageHandlerInfo in messageHandlers)
|
||||
{
|
||||
builder.AppendLine($"private Func<Session, uint, uint, object, FTask> message_{messageHandlerInfo.TypeName} = new {messageHandlerInfo.TypeFullName}().Handle;");
|
||||
}
|
||||
|
||||
foreach (var messageHandlerInfo in routeMessageHandlers)
|
||||
{
|
||||
builder.AppendLine($"private Func<Session, Entity, uint, object, FTask> routeMessage_{messageHandlerInfo.TypeName} = new {messageHandlerInfo.TypeFullName}().Handle;");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static void GenerateRegistrationCode(SourceCodeBuilder builder, List<MessageHandlerInfo> messageHandlers, List<MessageHandlerInfo> routeMessageHandlers)
|
||||
{
|
||||
builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
builder.BeginMethod("public int GetMessageHandlerCount()");
|
||||
builder.AppendLine($"return {messageHandlers.Count};");
|
||||
builder.EndMethod();
|
||||
builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
builder.BeginMethod("public int GetRouteMessageHandlerCount()");
|
||||
builder.AppendLine($"return {routeMessageHandlers.Count};");
|
||||
builder.EndMethod();
|
||||
builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
builder.BeginMethod("public bool MessageHandler(Session session, uint rpcId, uint protocolCode, object message)");
|
||||
if (messageHandlers.Any())
|
||||
{
|
||||
builder.AppendLine("switch (protocolCode)");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
foreach (var messageHandlerInfo in messageHandlers)
|
||||
{
|
||||
builder.AppendLine($"case {messageHandlerInfo.OpCode}:");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
builder.AppendLine($"message_{messageHandlerInfo.TypeName}(session, rpcId, protocolCode, message).Coroutine();");
|
||||
builder.AppendLine($"return true;");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
}
|
||||
builder.AppendLine("default:");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
builder.AppendLine($"return false;");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"return false;");
|
||||
}
|
||||
builder.EndMethod();
|
||||
|
||||
builder.AppendLine("#if FANTASY_NET", false);
|
||||
builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
builder.BeginMethod("public async FTask<bool> RouteMessageHandler(Session session, Entity entity, uint rpcId, uint protocolCode, object message)");
|
||||
if (routeMessageHandlers.Any())
|
||||
{
|
||||
builder.AppendLine("switch (protocolCode)");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
foreach (var routeMessageHandler in routeMessageHandlers)
|
||||
{
|
||||
builder.AppendLine($"case {routeMessageHandler.OpCode}:");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
builder.AppendLine($"await routeMessage_{routeMessageHandler.TypeName}(session, entity, rpcId, message);");
|
||||
builder.AppendLine($"return true;");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
}
|
||||
builder.AppendLine("default:");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
builder.AppendLine($"await FTask.CompletedTask;");
|
||||
builder.AppendLine($"return false;");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"await FTask.CompletedTask;");
|
||||
builder.AppendLine($"return false;");
|
||||
}
|
||||
builder.EndMethod();
|
||||
builder.AppendLine("#endif", false);
|
||||
}
|
||||
|
||||
private static bool IsMessageHandlerClass(SyntaxNode node)
|
||||
{
|
||||
if (node is not ClassDeclarationSyntax classDecl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (classDecl.BaseList == null || !classDecl.BaseList.Types.Any())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var baseType in classDecl.BaseList.Types)
|
||||
{
|
||||
var typeName = baseType.Type.ToString();
|
||||
|
||||
if (typeName.Contains("IMessageHandler") ||
|
||||
typeName.Contains("IRouteMessageHandler") ||
|
||||
typeName.Contains("Message<") ||
|
||||
typeName.Contains("MessageRPC<") ||
|
||||
typeName.Contains("Route<") ||
|
||||
typeName.Contains("RouteRPC<") ||
|
||||
typeName.Contains("Addressable<") ||
|
||||
typeName.Contains("AddressableRPC<") ||
|
||||
typeName.Contains("Roaming<") ||
|
||||
typeName.Contains("RoamingRPC<"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static MessageHandlerInfo? GetMessageTypeInfo(GeneratorSyntaxContext context)
|
||||
{
|
||||
var classDecl = (ClassDeclarationSyntax)context.Node;
|
||||
|
||||
if (context.SemanticModel.GetDeclaredSymbol(classDecl) is not INamedTypeSymbol symbol ||
|
||||
!symbol.IsInstantiable())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var baseType = symbol.BaseType;
|
||||
|
||||
if (baseType is not { IsGenericType: true } || baseType.TypeArguments.Length <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var baseTypeName = baseType.OriginalDefinition.ToDisplayString();
|
||||
|
||||
switch (baseTypeName)
|
||||
{
|
||||
case "Fantasy.Network.Interface.Message<T>":
|
||||
case "Fantasy.Network.Interface.MessageRPC<TRequest, TResponse>":
|
||||
{
|
||||
return new MessageHandlerInfo(
|
||||
HandlerType.MessageHandler,
|
||||
symbol.GetFullName(),
|
||||
symbol.Name,
|
||||
GetOpCode(context, baseType, 0));
|
||||
}
|
||||
case "Fantasy.Network.Interface.Route<TEntity, TMessage>":
|
||||
case "Fantasy.Network.Interface.RouteRPC<TEntity, TRouteRequest, TRouteResponse>":
|
||||
case "Fantasy.Network.Interface.Addressable<TEntity, TMessage>":
|
||||
case "Fantasy.Network.Interface.AddressableRPC<TEntity, TRouteRequest, TRouteResponse>":
|
||||
case "Fantasy.Network.Interface.Roaming<TEntity, TMessage>":
|
||||
case "Fantasy.Network.Interface.RoamingRPC<TEntity, TRouteRequest, TRouteResponse>":
|
||||
{
|
||||
return new MessageHandlerInfo(
|
||||
HandlerType.RouteMessageHandler,
|
||||
symbol.GetFullName(),
|
||||
symbol.Name,
|
||||
GetOpCode(context, baseType, 1));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static uint? GetOpCode(GeneratorSyntaxContext context, INamedTypeSymbol baseType, int index)
|
||||
{
|
||||
if (baseType.TypeArguments.Length <= index)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var messageType = (INamedTypeSymbol)baseType.TypeArguments[index];
|
||||
var messageName = messageType.Name;
|
||||
var compilation = context.SemanticModel.Compilation;
|
||||
|
||||
// 策略1:从消息类型所在程序集中搜索 OpCode 类
|
||||
var messageAssembly = messageType.ContainingAssembly;
|
||||
var namespaceName = messageType.ContainingNamespace.ToDisplayString();
|
||||
|
||||
// 遍历程序集中的所有类型,查找 OuterOpcode 或 InnerOpcode
|
||||
var opCodeTypeNames = new[] { "OuterOpcode", "InnerOpcode" };
|
||||
foreach (var opCodeTypeName in opCodeTypeNames)
|
||||
{
|
||||
var opCodeType = FindTypeInAssembly(messageAssembly.GlobalNamespace, namespaceName, opCodeTypeName);
|
||||
if (opCodeType != null)
|
||||
{
|
||||
var opCodeField = opCodeType.GetMembers(messageName).OfType<IFieldSymbol>().FirstOrDefault();
|
||||
if (opCodeField != null && opCodeField.IsConst && opCodeField.ConstantValue is uint constValue)
|
||||
{
|
||||
return constValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 策略2:如果策略1失败,尝试从 OpCode() 方法的语法树中解析(仅适用于同项目中的消息)
|
||||
var opCodeMethod = messageType.GetMembers("OpCode").OfType<IMethodSymbol>().FirstOrDefault();
|
||||
if (opCodeMethod != null)
|
||||
{
|
||||
var opCodeSyntax = opCodeMethod.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as MethodDeclarationSyntax;
|
||||
if (opCodeSyntax?.Body != null)
|
||||
{
|
||||
var returnStatement = opCodeSyntax.Body.DescendantNodes()
|
||||
.OfType<ReturnStatementSyntax>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (returnStatement?.Expression != null)
|
||||
{
|
||||
var syntaxTree = opCodeSyntax.SyntaxTree;
|
||||
|
||||
if (compilation.ContainsSyntaxTree(syntaxTree))
|
||||
{
|
||||
var semanticModel = compilation.GetSemanticModel(syntaxTree);
|
||||
|
||||
// 尝试符号解析
|
||||
var symbolInfo = semanticModel.GetSymbolInfo(returnStatement.Expression);
|
||||
if (symbolInfo.Symbol is IFieldSymbol fieldSymbol && fieldSymbol.IsConst && fieldSymbol.ConstantValue is uint constValue2)
|
||||
{
|
||||
return constValue2;
|
||||
}
|
||||
|
||||
// 尝试常量值解析
|
||||
var constantValue = semanticModel.GetConstantValue(returnStatement.Expression);
|
||||
if (constantValue.HasValue && constantValue.Value is uint uintValue)
|
||||
{
|
||||
return uintValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 辅助方法:在程序集的命名空间中递归查找指定类型
|
||||
private static INamedTypeSymbol? FindTypeInAssembly(INamespaceSymbol namespaceSymbol, string targetNamespace, string typeName)
|
||||
{
|
||||
// 如果当前命名空间匹配目标命名空间,查找类型
|
||||
if (namespaceSymbol.ToDisplayString() == targetNamespace)
|
||||
{
|
||||
var type = namespaceSymbol.GetTypeMembers(typeName).FirstOrDefault();
|
||||
if (type != null)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
// 递归搜索子命名空间
|
||||
foreach (var childNamespace in namespaceSymbol.GetNamespaceMembers())
|
||||
{
|
||||
var result = FindTypeInAssembly(childNamespace, targetNamespace, typeName);
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private enum HandlerType
|
||||
{
|
||||
None,
|
||||
MessageHandler,
|
||||
RouteMessageHandler
|
||||
}
|
||||
|
||||
private sealed record MessageHandlerInfo(
|
||||
HandlerType HandlerType,
|
||||
string TypeFullName,
|
||||
string TypeName,
|
||||
uint? OpCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Fantasy.SourceGenerator.Common;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
|
||||
namespace Fantasy.SourceGenerator.Generators;
|
||||
[Generator]
|
||||
internal partial class NetworkProtocolGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var networkProtocols = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: static (node, _) => IsNetworkProtocolClass(node),
|
||||
transform: static (ctx, _) => GetNetworkProtocolInfo(ctx))
|
||||
.Where(static info => info != null)
|
||||
.Collect();
|
||||
|
||||
var compilationAndTypes = context.CompilationProvider.Combine(networkProtocols);
|
||||
|
||||
context.RegisterSourceOutput(compilationAndTypes, static (spc, source) =>
|
||||
{
|
||||
if (!CompilationHelper.HasFantasyDefine(source.Left))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.Left.GetTypeByMetadataName("Fantasy.Assembly.INetworkProtocolRegistrar") == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GenerateCode(spc, source.Left, source.Right!);
|
||||
});
|
||||
}
|
||||
|
||||
#region GenerateCode
|
||||
|
||||
private static void GenerateCode(
|
||||
SourceProductionContext context,
|
||||
Compilation compilation,
|
||||
IEnumerable<NetworkProtocolTypeInfo> networkProtocolTypeInfos)
|
||||
{
|
||||
var networkProtocolTypeInfoList = networkProtocolTypeInfos.ToList();
|
||||
// 获取当前程序集名称(仅用于注释)
|
||||
var assemblyName = compilation.AssemblyName ?? "Unknown";
|
||||
// 生成网络网络协议类型注册的类。
|
||||
GenerateNetworkProtocolTypesCode(context, assemblyName, networkProtocolTypeInfoList);
|
||||
// 生成OpCode辅助方法。
|
||||
GenerateNetworkProtocolOpCodeResolverCode(context, assemblyName, networkProtocolTypeInfoList);
|
||||
// 生成Request消息的ResponseType辅助方法。
|
||||
GenerateNetworkProtocolResponseTypesResolverCode(context, assemblyName, networkProtocolTypeInfoList);
|
||||
}
|
||||
|
||||
private static void GenerateNetworkProtocolTypesCode(
|
||||
SourceProductionContext context,
|
||||
string assemblyName,
|
||||
List<NetworkProtocolTypeInfo> networkProtocolTypeInfoList)
|
||||
{
|
||||
var builder = new SourceCodeBuilder();
|
||||
// 添加文件头
|
||||
builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
|
||||
// 添加 using
|
||||
builder.AddUsings(
|
||||
"System",
|
||||
"System.Collections.Generic",
|
||||
"Fantasy.Assembly",
|
||||
"Fantasy.DataStructure.Collection"
|
||||
);
|
||||
// 开始命名空间(固定使用 Fantasy.Generated)
|
||||
builder.BeginNamespace("Fantasy.Generated");
|
||||
// 开始类定义(实现 IEventSystemRegistrar 接口)
|
||||
builder.AddXmlComment($"Auto-generated NetworkProtocol registration class for {assemblyName}");
|
||||
builder.BeginClass("NetworkProtocolRegistrar", "internal sealed", "INetworkProtocolRegistrar");
|
||||
builder.BeginMethod("public List<Type> GetNetworkProtocolTypes()");
|
||||
|
||||
if (networkProtocolTypeInfoList.Any())
|
||||
{
|
||||
builder.AppendLine($"return new List<Type>({networkProtocolTypeInfoList.Count})");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
foreach (var system in networkProtocolTypeInfoList)
|
||||
{
|
||||
builder.AppendLine($"typeof({system.FullName}),");
|
||||
}
|
||||
builder.Unindent();
|
||||
builder.AppendLine("};");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"return new List<Type>();");
|
||||
}
|
||||
|
||||
builder.EndMethod();
|
||||
builder.AppendLine();
|
||||
// 结束类和命名空间
|
||||
builder.EndClass();
|
||||
builder.EndNamespace();
|
||||
// 输出源代码
|
||||
context.AddSource("NetworkProtocolRegistrar.g.cs", builder.ToString());
|
||||
}
|
||||
|
||||
private static void GenerateNetworkProtocolOpCodeResolverCode(
|
||||
SourceProductionContext context,
|
||||
string assemblyName,
|
||||
List<NetworkProtocolTypeInfo> networkProtocolTypeInfoList)
|
||||
{
|
||||
var routeTypeInfos = networkProtocolTypeInfoList.Where(d => d.RouteType.HasValue).ToList();
|
||||
var builder = new SourceCodeBuilder();
|
||||
// 添加文件头
|
||||
builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
|
||||
// 添加 using
|
||||
builder.AddUsings(
|
||||
"System",
|
||||
"System.Collections.Generic",
|
||||
"Fantasy.Assembly",
|
||||
"Fantasy.DataStructure.Collection",
|
||||
"System.Runtime.CompilerServices"
|
||||
);
|
||||
builder.AppendLine();
|
||||
// 开始命名空间(固定使用 Fantasy.Generated)
|
||||
builder.BeginNamespace("Fantasy.Generated");
|
||||
// 开始类定义(实现 INetworkProtocolOpCodeResolver 接口)
|
||||
builder.AddXmlComment($"Auto-generated NetworkProtocolOpCodeResolverRegistrar class for {assemblyName}");
|
||||
builder.BeginClass("NetworkProtocolOpCodeResolverRegistrar", "internal sealed", "INetworkProtocolOpCodeResolver");
|
||||
builder.AddXmlComment($"GetOpCodeCount");
|
||||
builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
builder.BeginMethod("public int GetOpCodeCount()");
|
||||
builder.AppendLine($"return {networkProtocolTypeInfoList.Count};");
|
||||
builder.EndMethod();
|
||||
builder.AddXmlComment($"GetCustomRouteTypeCount");
|
||||
builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
builder.BeginMethod("public int GetCustomRouteTypeCount()");
|
||||
builder.AppendLine($"return {routeTypeInfos.Count};");
|
||||
builder.EndMethod();
|
||||
// 开始定义GetOpCodeType方法
|
||||
builder.AddXmlComment($"GetOpCodeType");
|
||||
builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
builder.BeginMethod("public Type GetOpCodeType(uint opCode)");
|
||||
|
||||
if (networkProtocolTypeInfoList.Any())
|
||||
{
|
||||
builder.AppendLine("switch (opCode)");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
foreach (var networkProtocolTypeInfo in networkProtocolTypeInfoList)
|
||||
{
|
||||
builder.AppendLine($"case {networkProtocolTypeInfo.OpCode}:");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
builder.AppendLine($"return typeof({networkProtocolTypeInfo.FullName});");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
}
|
||||
builder.AppendLine("default:");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
builder.AppendLine($"return null!;");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"return null!;");
|
||||
}
|
||||
|
||||
builder.EndMethod();
|
||||
|
||||
// 开始定义GetRouteType方法
|
||||
builder.AddXmlComment($"CustomRouteType");
|
||||
builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
builder.BeginMethod("public int? GetCustomRouteType(uint opCode)");
|
||||
|
||||
if (routeTypeInfos.Any())
|
||||
{
|
||||
builder.AppendLine("switch (opCode)");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
foreach (var routeTypeInfo in routeTypeInfos)
|
||||
{
|
||||
builder.AppendLine($"case {routeTypeInfo.OpCode}:");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
builder.AppendLine($"return {routeTypeInfo.RouteType};");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
}
|
||||
builder.AppendLine("default:");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
builder.AppendLine($"return null;");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"return null;");
|
||||
}
|
||||
|
||||
builder.EndMethod();
|
||||
builder.AppendLine();
|
||||
// 结束类和命名空间
|
||||
builder.EndClass();
|
||||
builder.EndNamespace();
|
||||
// 输出源代码
|
||||
context.AddSource("NetworkProtocolOpCodeResolverRegistrar.g.cs", builder.ToString());
|
||||
}
|
||||
|
||||
private static void GenerateNetworkProtocolResponseTypesResolverCode(
|
||||
SourceProductionContext context,
|
||||
string assemblyName,
|
||||
List<NetworkProtocolTypeInfo> networkProtocolTypeInfoList)
|
||||
{
|
||||
var requestList = networkProtocolTypeInfoList.Where(d => d.ResponseType != null).ToList();
|
||||
var builder = new SourceCodeBuilder();
|
||||
// 添加文件头
|
||||
builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
|
||||
// 添加 using
|
||||
builder.AddUsings(
|
||||
"System",
|
||||
"System.Collections.Generic",
|
||||
"Fantasy.Assembly",
|
||||
"Fantasy.DataStructure.Collection",
|
||||
"System.Runtime.CompilerServices"
|
||||
);
|
||||
builder.AppendLine();
|
||||
// 开始命名空间(固定使用 Fantasy.Generated)
|
||||
builder.BeginNamespace("Fantasy.Generated");
|
||||
// 开始类定义(实现 IEventSystemRegistrar 接口)
|
||||
builder.AddXmlComment($"Auto-generated NetworkProtocolResponseTypeResolverRegistrar class for {assemblyName}");
|
||||
builder.BeginClass("NetworkProtocolResponseTypeResolverRegistrar", "internal sealed", "INetworkProtocolResponseTypeResolver");
|
||||
builder.AddXmlComment($"GetOpCodeCount");
|
||||
builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
builder.BeginMethod("public int GetRequestCount()");
|
||||
builder.AppendLine($"return {requestList.Count};");
|
||||
builder.EndMethod();
|
||||
// 开始定义GetOpCodeType方法
|
||||
builder.AddXmlComment($"GetOpCodeType");
|
||||
builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
builder.BeginMethod("public Type GetResponseType(uint opCode)");
|
||||
|
||||
if (requestList.Any())
|
||||
{
|
||||
builder.AppendLine("switch (opCode)");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
foreach (var request in requestList)
|
||||
{
|
||||
builder.AppendLine($"case {request.OpCode}:");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
builder.AppendLine($"return typeof({request.ResponseType});");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
}
|
||||
builder.AppendLine("default:");
|
||||
builder.AppendLine("{");
|
||||
builder.Indent();
|
||||
builder.AppendLine($"return null!;");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
builder.Unindent();
|
||||
builder.AppendLine("}");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"return null!;");
|
||||
}
|
||||
|
||||
builder.EndMethod();
|
||||
builder.AppendLine();
|
||||
// 结束类和命名空间
|
||||
builder.EndClass();
|
||||
builder.EndNamespace();
|
||||
// 输出源代码
|
||||
context.AddSource("NetworkProtocolResponseTypeResolverRegistrar.g.cs", builder.ToString());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static NetworkProtocolTypeInfo? GetNetworkProtocolInfo(GeneratorSyntaxContext context)
|
||||
{
|
||||
var classDecl = (ClassDeclarationSyntax)context.Node;
|
||||
var symbol = context.SemanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
|
||||
|
||||
if (symbol == null || !symbol.IsInstantiable())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var baseType = symbol.BaseType;
|
||||
|
||||
if (baseType == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (baseType.ToDisplayString() != "Fantasy.Network.Interface.AMessage")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取 OpCode 方法的值
|
||||
uint? opCodeValue = null;
|
||||
var opCodeMethod = symbol.GetMembers("OpCode").OfType<IMethodSymbol>().FirstOrDefault();
|
||||
var opCodeSyntax = opCodeMethod?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as MethodDeclarationSyntax;
|
||||
|
||||
if (opCodeSyntax?.Body != null)
|
||||
{
|
||||
var returnStatement = opCodeSyntax.Body.DescendantNodes()
|
||||
.OfType<ReturnStatementSyntax>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (returnStatement?.Expression != null)
|
||||
{
|
||||
var constantValue = context.SemanticModel.GetConstantValue(returnStatement.Expression);
|
||||
if (constantValue.HasValue && constantValue.Value is uint uintValue)
|
||||
{
|
||||
opCodeValue = uintValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!opCodeValue.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取 ResponseType 属性及其类型
|
||||
string? responseTypeName = null;
|
||||
var responseTypeProperty = symbol.GetMembers("ResponseType").OfType<IPropertySymbol>().FirstOrDefault();
|
||||
if (responseTypeProperty != null)
|
||||
{
|
||||
// 获取 ResponseType 属性的类型(例如 G2C_TestResponse)
|
||||
responseTypeName = responseTypeProperty.Type.GetFullName();
|
||||
}
|
||||
|
||||
// 获取 RouteType 属性的值
|
||||
int? routeTypeValue = null;
|
||||
var routeTypeProperty = symbol.GetMembers("RouteType").OfType<IPropertySymbol>().FirstOrDefault();
|
||||
var routeTypeSyntax = routeTypeProperty?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as PropertyDeclarationSyntax;
|
||||
if (routeTypeSyntax?.ExpressionBody != null)
|
||||
{
|
||||
var constantValue = context.SemanticModel.GetConstantValue(routeTypeSyntax.ExpressionBody.Expression);
|
||||
if (constantValue.HasValue && constantValue.Value is int intValue)
|
||||
{
|
||||
routeTypeValue = intValue;
|
||||
}
|
||||
}
|
||||
|
||||
return new NetworkProtocolTypeInfo(
|
||||
symbol.GetFullName(),
|
||||
opCodeValue.Value,
|
||||
responseTypeName,
|
||||
routeTypeValue
|
||||
);
|
||||
}
|
||||
|
||||
private static bool IsNetworkProtocolClass(SyntaxNode node)
|
||||
{
|
||||
if (node is not ClassDeclarationSyntax classDecl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (classDecl.BaseList == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var baseTypeSyntax in classDecl.BaseList.Types)
|
||||
{
|
||||
if (baseTypeSyntax.Type.ToString().Contains("AMessage"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private sealed record NetworkProtocolTypeInfo(
|
||||
string FullName,
|
||||
uint OpCode,
|
||||
string? ResponseType,
|
||||
int? RouteType);
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
373
Fantasy/Fantasy.Net/Fantasy.SourceGenerator/README.md
Normal file
373
Fantasy/Fantasy.Net/Fantasy.SourceGenerator/README.md
Normal file
@@ -0,0 +1,373 @@
|
||||
# Fantasy.SourceGenerator
|
||||
|
||||
Fantasy 框架的增量源代码生成器(Incremental Source Generator),在编译时自动生成注册代码,消除反射开销,提升性能并支持 Native AOT。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### ✅ 已实现的生成器
|
||||
|
||||
- **AssemblyInitializerGenerator**: 自动生成程序集初始化器(ModuleInitializer),在程序集加载时自动注册到框架
|
||||
- **EntitySystemGenerator**: 自动生成 Entity System(Awake/Update/Destroy/Deserialize/LateUpdate)注册代码
|
||||
- **EventSystemGenerator**: 自动生成 Event System(EventSystem/AsyncEventSystem/SphereEventSystem)注册代码
|
||||
- **MessageDispatcherGenerator**: 自动生成消息调度器(网络协议、消息处理器、路由处理器)注册代码
|
||||
- **EntityTypeCollectionGenerator**: 自动生成 Entity 类型集合,用于框架类型管理
|
||||
- **ProtoBufGenerator**: 自动生成 ProtoBuf 类型注册代码,用于序列化系统
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 自动集成(推荐)
|
||||
|
||||
Framework 已经完全集成了 Source Generator,开发者**无需手动配置**。
|
||||
|
||||
#### 工作流程
|
||||
|
||||
1. **编译时自动生成**: 当你构建项目时,Source Generator 会自动扫描代码并生成注册器
|
||||
2. **自动注册**: `AssemblyInitializerGenerator` 生成的 `ModuleInitializer` 会在程序集加载时自动注册所有生成器到框架
|
||||
3. **透明运行**: 框架内部自动使用生成的注册器,无需任何手动调用
|
||||
|
||||
#### 示例:添加一个 Entity System
|
||||
|
||||
```csharp
|
||||
// 只需按照 Fantasy 框架的规范编写代码
|
||||
public class MyEntityAwakeSystem : AwakeSystem<MyEntity>
|
||||
{
|
||||
protected override void Awake(MyEntity self)
|
||||
{
|
||||
// 你的逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
编译后,Source Generator 会自动:
|
||||
- 生成 `EntitySystemRegistrar.g.cs` 包含此 System
|
||||
- 在程序集加载时自动注册到框架
|
||||
- 无需任何额外步骤
|
||||
|
||||
### 手动集成(高级用途)
|
||||
|
||||
如果你需要在自己的项目中引用 Source Generator:
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Fantasy.SourceGenerator\Fantasy.SourceGenerator.csproj"
|
||||
OutputItemType="Analyzer"
|
||||
ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
确保项目定义了 `FANTASY_NET` 或 `FANTASY_UNITY` 预编译符号:
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<DefineConstants>FANTASY_NET</DefineConstants>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
## 生成的代码示例
|
||||
|
||||
Source Generator 会在 `obj/.../generated/Fantasy.SourceGenerator/` 目录下自动生成多个注册器类:
|
||||
|
||||
### AssemblyInitializer.g.cs(核心)
|
||||
|
||||
```csharp
|
||||
// 程序集初始化器 - 在程序集加载时自动执行
|
||||
namespace Fantasy.Generated
|
||||
{
|
||||
internal static class AssemblyInitializer
|
||||
{
|
||||
[ModuleInitializer] // .NET 使用 ModuleInitializer
|
||||
// [RuntimeInitializeOnLoadMethod] // Unity 使用此属性
|
||||
internal static void Initialize()
|
||||
{
|
||||
var assembly = typeof(AssemblyInitializer).Assembly;
|
||||
var assemblyManifestId = HashCodeHelper.ComputeHash64(assembly.GetName().Name);
|
||||
|
||||
// 创建所有生成的注册器
|
||||
var protoBufRegistrar = new Fantasy.Generated.ProtoBufRegistrar();
|
||||
var eventSystemRegistrar = new Fantasy.Generated.EventSystemRegistrar();
|
||||
var entitySystemRegistrar = new Fantasy.Generated.EntitySystemRegistrar();
|
||||
var messageDispatcherRegistrar = new Fantasy.Generated.MessageDispatcherRegistrar();
|
||||
var entityTypeCollectionRegistrar = new Fantasy.Generated.EntityTypeCollectionRegistrar();
|
||||
|
||||
// 一次性注册到框架
|
||||
Fantasy.Assembly.AssemblyManifest.Register(
|
||||
assemblyManifestId,
|
||||
assembly,
|
||||
protoBufRegistrar,
|
||||
eventSystemRegistrar,
|
||||
entitySystemRegistrar,
|
||||
messageDispatcherRegistrar,
|
||||
entityTypeCollectionRegistrar);
|
||||
}
|
||||
}
|
||||
|
||||
// 用于强制加载程序集的标记类
|
||||
public static class MyAssembly_AssemblyMarker
|
||||
{
|
||||
public static void EnsureLoaded() { }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### EntitySystemRegistrar.g.cs
|
||||
|
||||
```csharp
|
||||
namespace Fantasy.Generated
|
||||
{
|
||||
internal sealed class EntitySystemRegistrar : IEntitySystemRegistrar
|
||||
{
|
||||
public void RegisterSystems(
|
||||
Dictionary<Type, IAwakeSystem> awakeSystems,
|
||||
Dictionary<Type, IUpdateSystem> updateSystems,
|
||||
// ... 其他系统类型
|
||||
)
|
||||
{
|
||||
awakeSystems.Add(typeof(MyEntity), new MyEntityAwakeSystem());
|
||||
updateSystems.Add(typeof(MyEntity), new MyEntityUpdateSystem());
|
||||
// ... 自动注册所有发现的 System
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### EventSystemRegistrar.g.cs
|
||||
|
||||
```csharp
|
||||
namespace Fantasy.Generated
|
||||
{
|
||||
internal sealed class EventSystemRegistrar : IEventSystemRegistrar
|
||||
{
|
||||
private MyEventHandler _myEventHandler = new MyEventHandler();
|
||||
|
||||
public void RegisterSystems(
|
||||
OneToManyList<Type, IEvent> events,
|
||||
OneToManyList<Type, IEvent> asyncEvents,
|
||||
OneToManyList<Type, IEvent> sphereEvents)
|
||||
{
|
||||
events.Add(_myEventHandler.EventType(), _myEventHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MessageDispatcherRegistrar.g.cs
|
||||
|
||||
```csharp
|
||||
namespace Fantasy.Generated
|
||||
{
|
||||
internal sealed class MessageDispatcherRegistrar : IMessageDispatcherRegistrar
|
||||
{
|
||||
public void RegisterSystems(
|
||||
DoubleMapDictionary<uint, Type> networkProtocols,
|
||||
Dictionary<Type, Type> responseTypes,
|
||||
Dictionary<Type, IMessageHandler> messageHandlers,
|
||||
// ...
|
||||
)
|
||||
{
|
||||
var c2GLoginRequest = new C2G_LoginRequest();
|
||||
networkProtocols.Add(c2GLoginRequest.OpCode(), typeof(C2G_LoginRequest));
|
||||
responseTypes.Add(typeof(C2G_LoginRequest), typeof(G2C_LoginResponse));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能对比
|
||||
|
||||
| 场景 | 反射方式 | Source Generator | 性能提升 |
|
||||
|------|---------|------------------|---------|
|
||||
| 程序集加载时注册 | ~50ms(100个类型) | ~1ms | **50x** |
|
||||
| 启动时间 | 受反射影响 | 几乎无影响 | **显著** |
|
||||
| 运行时实例化 | ~150ns/个 | ~3ns/个 | **50x** |
|
||||
| 内存分配 | 反射元数据开销 | 无额外开销 | **更优** |
|
||||
| Native AOT 兼容 | ❌ 不支持 | ✅ 完全支持 | - |
|
||||
| IL2CPP (Unity) | ⚠️ 性能差 | ✅ 完美支持 | **必需** |
|
||||
|
||||
## 调试生成的代码
|
||||
|
||||
### 查看生成的代码
|
||||
|
||||
生成的代码位于:
|
||||
```
|
||||
<项目目录>/obj/<配置>/<目标框架>/generated/Fantasy.SourceGenerator/
|
||||
```
|
||||
|
||||
例如:
|
||||
```
|
||||
obj/Debug/net8.0/generated/Fantasy.SourceGenerator/Fantasy.SourceGenerator.Generators.AssemblyInitializerGenerator/AssemblyInitializer.g.cs
|
||||
obj/Debug/net8.0/generated/Fantasy.SourceGenerator/Fantasy.SourceGenerator.Generators.EntitySystemGenerator/EntitySystemRegistrar.g.cs
|
||||
```
|
||||
|
||||
### IDE 支持
|
||||
|
||||
- **Visual Studio**: Dependencies → Analyzers → Fantasy.SourceGenerator → 展开查看生成的文件
|
||||
- **JetBrains Rider**: Dependencies → Source Generators → Fantasy.SourceGenerator
|
||||
- **VS Code**: 需要手动浏览 `obj/` 目录
|
||||
|
||||
### 启用详细日志
|
||||
|
||||
在 `.csproj` 中添加:
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)/GeneratedFiles</CompilerGeneratedFilesOutputPath>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
这会将生成的文件输出到 `obj/GeneratedFiles/` 目录,方便查看和调试。
|
||||
|
||||
## 兼容性
|
||||
|
||||
### 平台支持
|
||||
|
||||
| 平台 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| .NET 8.0+ | ✅ 完全支持 | 使用 Roslyn 4.8.0 |
|
||||
| .NET Framework 4.x | ❌ 不支持 | Source Generator 需要 .NET Standard 2.0+ |
|
||||
| Unity 2020.2+ | ✅ 完全支持 | 使用 Roslyn 4.0.1(兼容版本) |
|
||||
| Native AOT | ✅ 完全支持 | 无反射,完美兼容 AOT |
|
||||
| IL2CPP | ✅ 完全支持 | Unity IL2CPP 必需 |
|
||||
|
||||
### 运行模式
|
||||
|
||||
- **开发环境**: Source Generator 自动生成,支持热重载(重新编译)
|
||||
- **生产环境**: Source Generator 自动生成,零反射开销
|
||||
- **热更新程序集**: 框架会检测可收集的 AssemblyLoadContext,支持程序集卸载和重载
|
||||
|
||||
## 技术特性
|
||||
|
||||
### 增量编译支持
|
||||
|
||||
所有生成器都实现了 `IIncrementalGenerator` 接口:
|
||||
- ✅ 仅在相关代码变化时重新生成
|
||||
- ✅ 大幅提升编译速度
|
||||
- ✅ 支持部分代码更新
|
||||
|
||||
### 条件编译
|
||||
|
||||
生成器会根据预编译符号调整生成代码:
|
||||
- `FANTASY_NET`: .NET 平台特定功能
|
||||
- `FANTASY_UNITY`: Unity 平台特定功能
|
||||
- 自动检测平台并生成对应代码
|
||||
|
||||
### 自动清理
|
||||
|
||||
- 程序集卸载时自动反注册(支持热更新)
|
||||
- 实现 `IDisposable` 接口,支持资源释放
|
||||
- 无内存泄漏风险
|
||||
|
||||
## 多版本构建支持
|
||||
|
||||
### Unity 版本 vs .NET 版本
|
||||
|
||||
此项目支持两种构建配置,使用不同版本的 Roslyn 编译器:
|
||||
|
||||
#### Unity 配置(用于 Unity Package)
|
||||
|
||||
```bash
|
||||
dotnet build --configuration Unity
|
||||
```
|
||||
|
||||
- **Roslyn 版本**: 4.0.1
|
||||
- **用途**: Unity 2020.2+ 项目
|
||||
- **兼容性**: Unity 2020.2 - Unity 2023.x
|
||||
- **输出**: `bin/Unity/netstandard2.0/Fantasy.SourceGenerator.dll`
|
||||
|
||||
#### Release/Debug 配置(用于 .NET 项目)
|
||||
|
||||
```bash
|
||||
dotnet build --configuration Release
|
||||
```
|
||||
|
||||
- **Roslyn 版本**: 4.8.0
|
||||
- **用途**: .NET 8/9 项目
|
||||
- **兼容性**: .NET 8.0+
|
||||
- **输出**: `bin/Release/netstandard2.0/Fantasy.SourceGenerator.dll`
|
||||
|
||||
### 为什么需要两个版本?
|
||||
|
||||
Unity 使用的 Roslyn 编译器版本较旧,Source Generator 引用的 Roslyn 版本必须**小于或等于**运行时编译器版本:
|
||||
|
||||
| 平台 | Roslyn 版本 | Source Generator 配置 |
|
||||
|------|------------|---------------------|
|
||||
| Unity 2020.2-2021.x | 3.8 - 4.0 | Unity (4.0.1) |
|
||||
| Unity 2022.x | 4.3 | Unity (4.0.1) |
|
||||
| Unity 2023.x | 4.6+ | Unity (4.0.1) |
|
||||
| .NET 8.0 | 4.8+ | Release (4.8.0) |
|
||||
| .NET 9.0 | 4.10+ | Release (4.8.0) |
|
||||
|
||||
### 自动化更新脚本
|
||||
|
||||
**更新 Unity Package:**
|
||||
```bash
|
||||
# macOS/Linux
|
||||
./update-unity-source-generator.sh
|
||||
|
||||
# Windows
|
||||
update-unity-source-generator.bat
|
||||
```
|
||||
|
||||
这些脚本会自动使用 Unity 配置构建并更新到 Fantasy.Unity package。在项目的Tools/Update-Unity-Source-Generator下。
|
||||
|
||||
### 技术实现
|
||||
|
||||
在 `.csproj` 中使用条件 PackageReference:
|
||||
|
||||
```xml
|
||||
<!-- Unity 版本 - Roslyn 4.0.1 -->
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Unity' OR '$(Configuration)' == 'UnityDebug'">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- .NET 版本 - Roslyn 4.8.0 -->
|
||||
<ItemGroup Condition="'$(Configuration)' != 'Unity' AND '$(Configuration)' != 'UnityDebug'">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
### 故障排查
|
||||
|
||||
**CS9057 警告/错误:**
|
||||
```
|
||||
warning CS9057: The analyzer assembly references version 'X' of the compiler,
|
||||
which is newer than the currently running version 'Y'.
|
||||
```
|
||||
|
||||
**解决方案**: 确保使用 Unity 配置构建用于 Unity 的版本。
|
||||
|
||||
## 常见问题(FAQ)
|
||||
|
||||
### Q: 为什么看不到生成的代码?
|
||||
A: 生成的代码在 `obj/` 目录下,需要编译项目后才能看到。可以在 IDE 的 Analyzers/Source Generators 节点中查看。
|
||||
|
||||
### Q: 可以禁用某个生成器吗?
|
||||
A: 生成器会自动检测代码特征,如果你的项目中没有相关类型,对应的生成器不会生成代码。无需手动禁用。
|
||||
|
||||
### Q: Source Generator 会影响编译速度吗?
|
||||
A: 首次编译会扫描所有代码,后续使用增量编译,仅在相关代码变化时重新生成,对编译速度影响很小。
|
||||
|
||||
### Q: Unity 项目如何使用?
|
||||
A: Fantasy.Unity package 已经包含了兼容版本的 Source Generator(Roslyn 4.0.1),无需额外配置。
|
||||
|
||||
### Q: 支持热重载吗?
|
||||
A: 支持。框架会检测 AssemblyLoadContext 的卸载事件,自动反注册。重新编译后自动注册新版本。
|
||||
|
||||
### Q: Native AOT 部署需要注意什么?
|
||||
A: 无需特别注意,Source Generator 生成的代码不使用反射,完全兼容 Native AOT。
|
||||
|
||||
## 贡献指南
|
||||
|
||||
欢迎贡献代码!如果你想添加新的生成器:
|
||||
|
||||
1. 在 `Generators/` 目录下创建新的生成器类
|
||||
2. 实现 `IIncrementalGenerator` 接口
|
||||
3. 在 `AssemblyInitializerGenerator` 中添加对应的注册器接口
|
||||
4. 更新本 README 文档
|
||||
|
||||
---
|
||||
|
||||
**维护者**: 初见
|
||||
**更新日期**: 2025-10-19
|
||||
**版本**: 1.0.0
|
||||
42
Fantasy/Fantasy.Net/Fantasy.SourceGenerator/README_DEBUG.md
Normal file
42
Fantasy/Fantasy.Net/Fantasy.SourceGenerator/README_DEBUG.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Source Generator 调试指南
|
||||
|
||||
## 当前状态
|
||||
|
||||
生成器已编译成功,但没有生成任何代码文件。
|
||||
|
||||
## 可能的原因
|
||||
|
||||
1. **类型匹配问题**:生成器的 `IsSystemClass` 或 `GetSystemType` 方法无法正确识别 System 类
|
||||
2. **Incremental Generator Pipeline 问题**:Pipeline 可能过滤掉了所有候选类
|
||||
3. **命名空间不匹配**:生成器可能在寻找错误的命名空间
|
||||
|
||||
## 调试步骤
|
||||
|
||||
### 方法 1:简化生成器逻辑
|
||||
|
||||
将 `IsSystemClass` 改为返回 `true` 以查看是否有任何类被处理:
|
||||
|
||||
```csharp
|
||||
private static bool IsSystemClass(SyntaxNode node)
|
||||
{
|
||||
return node is ClassDeclarationSyntax; // 处理所有类
|
||||
}
|
||||
```
|
||||
|
||||
### 方法 2:查看生成器输出
|
||||
|
||||
```bash
|
||||
# 启用详细日志
|
||||
dotnet build /p:EmitCompilerGeneratedFiles=true /p:CompilerGeneratedFilesOutputPath=Generated
|
||||
|
||||
# 查看生成的文件
|
||||
ls -la Generated/
|
||||
```
|
||||
|
||||
### 方法 3:使用 LinqPad/RoslynPad 测试
|
||||
|
||||
创建独立的测试项目来验证 Roslyn API 调用。
|
||||
|
||||
## 下一步
|
||||
|
||||
建议先尝试方法 2,查看 MSBuild 是否能输出生成的文件。
|
||||
Reference in New Issue
Block a user