ET 框架源码生成与静态分析体系:cn.etetet.sourcegenerator 包中的 Roslyn Generator、Analyzer 与 CodeFixer 全解析
2026/9/16 19:47:27 网站建设 项目流程

ET 框架源码生成与静态分析体系:cn.etetet.sourcegenerator 包中的 Roslyn Generator、Analyzer 与 CodeFixer 全解析

【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET

本文以cn.etetet.sourcegenerator包为切入点,系统讲解 ET 框架(Unity3D 客户端 + C# 服务端双端框架)如何借助 Roslyn 编译器平台,将 ECS 架构下的 EntitySystem、消息 Handler、事件、GetComponent 扩展与对象池清理等样板代码自动生成,并通过 40+ 条诊断规则在编译期约束代码风格与框架契约。读完本文,你将理解该包的目录结构与四类组成(Source Generator、Analyzer、CodeFixer、生成器标记类型),掌握新增一条诊断规则时必须同步维护的配置文件和验证流程,并能依据源码证据判断每条规则的具体行为。

一、包的定位与总体组成

cn.etetet.sourcegenerator是 ET 框架中负责编译期代码生成与静态检查的基础包,其定位在包描述中写得很清楚:"et框架的分析器跟代码生成器"。它本身不承载游戏运行时逻辑,而是以 Roslyn 编译器插件的形式存在:在dotnet build阶段扫描项目源码,一边生成 ECS 生命周期 System、消息 Handler 等样板代码,一边对违反框架契约的写法直接报编译错误。

根据 包的 AGENTS.md,该包承载四类组件:

组件类型职责仓库中的落地位置
Source Generator(源生成器)依据标记自动生成 System / Handler / 扩展方法等代码DotNet~/ET.SourceGenerator/Generator/
Analyzer(分析器)编译期静态检查,产出ET00xx/ET10xx诊断DotNet~/ET.SourceGenerator/Analyzer/
CodeFixer(代码修复器)针对诊断提供一键修复(如自动生成 EntitySystem)DotNet~/ET.SourceGenerator/CodeFixer/
生成器标记类型(Attribute)供业务代码使用的特性,是生成与分析的前提Scripts/Core/Share/

从整体目录结构看,包内分为两大块:

  • DotNet~/ET.SourceGenerator/:真正的 Roslyn 工程,包含Analyzer/CodeFixer/Config/Generator/四个子目录,以及AnalyzerGlobalSetting.csAnalyzerHelper.csStringHashHelper.cs等公共设施;
  • Scripts/Core/Share/:暴露给 Unity 业务工程的 Attribute 标记类型(EntitySystemOfAttributeComponentOfAttributeDisableNewAttribute等);
  • Scripts/Model/Share/PackageType.cs:记录包的唯一 ID(与packagegit.json中的Id字段对应,用于包依赖与访问控制分析)。

二、生成器标记类型:业务代码与编译器之间的"契约"

所有生成与分析行为都由 Attribute 驱动。业务代码先打标记,SourceGenerator/Analyzer 再据此工作。标记类型集中在 Scripts/Core/Share 下,其中Core/对应所有程序集通用的标记,Model/对应 Model 程序集专用的标记:

ECS 结构与生命周期标记

  • EntitySystemOfAttribute(见 EntitySystemOf.cs):标记某个静态类为指定 Entity 的 System 类,构造函数接收 Entity 类型与ignoreAwake(是否忽略生成 AwakeSystem),由它触发 EntitySystem 生成;
  • LSEntitySystemOfAttribute:锁定帧(LockStep)实体 LSEntity 对应的 System 标记;
  • ComponentOfAttribute(见 ComponentOfAttribute.cs):声明组件允许挂载的父级实体类型——"父级实体类型唯一的标记[ComponentOf(typeof(parentType))],不唯一则标记[ComponentOf]";
  • ChildOfAttribute:声明子实体允许的父级类型,是AddChild类型约束分析的依据;
  • EnableAccessEntiyChildAttribute:允许在实体类中直接访问 Child/Component。

能力开关标记

  • EnableClassAttribute(见 EnableClassAttribute.cs):Model 程序集默认禁止声明非实体类,声明普通类必须加此标记;
  • EnableMethodAttributeEnableGetComponentAttributeAllowEntityMemberAttribute:分别放开方法、被禁组件的获取、实体成员声明;
  • DisableGetComponentAttributeDisableNewAttribute:禁止直接GetComponent/ 禁止new构造,配合ET0037ET0031两条规则使用;
  • StaticFieldAttribute:静态字段必须打标记(对应ET0015);
  • UniqueIdAttribute:唯一 ID 字段约束(对应ET0011/ET0012);
  • IgnoreCircularDependencyAttributeModuleAttributeBTNodeGenAttributeSkipAwaitEntityCheck等:分别用于循环依赖豁免、模块声明、行为树节点生成与异步实体检查豁免。

这些标记的字符串全名被集中定义在 Definition.cs(如ET.ChildOfAttributeET.ComponentOfAttributeET.DisableNewAttribute),Generator 与 Analyzer 均以该文件为统一事实来源,避免硬编码散落各处。

三、诊断规则体系:DiagnosticIds 与 DiagnosticRules

3.1 规则的"身份证":DiagnosticIds.cs

ET 框架的全部诊断 ID 集中维护在 Config/DiagnosticIds.cs,这是"新增诊断时同步维护"约定的落点之一。当前已登记约 40 条规则,按 ID 前缀分为两组:

  • ET0001ET0040:框架契约类规则;
  • ET1001ET1004:代码风格与调用链类规则。

主要规则清单如下(描述取自 DiagnosticRules.cs 中各 Rule 的 Title/MessageFormat):

ID规则核心约束
ET0001AddChild 类型约束子实体类型必须用ChildOfAttribute标记父级
ET0003实体类限制多层继承直接继承Entity的子类,禁止再被继承
ET0007组件类型约束组件必须用ComponentOfAttribute声明父级实体
ET0008/ET0009ETTask 调用规范同步方法内需加.Coroutine();异步方法内需await.Coroutine()
ET0010实体类禁止委托字段/属性防止引用生命周期问题
ET0011/ET0012UniqueId 区间/重复约束唯一 ID 必须在指定区间且全局不重复
ET0014禁止直接访问 Child/ComponentEntity 基类场景下需EnableAccessEntiyChild
ET0015静态字段必须打标记配合StaticFieldAttribute
ET0016–ET0019ETCancellationToken 规范await 后必须判断IsCancel;必须透传同一 token;禁止默认值;禁止传 null
ET0020实体类禁止实体字段必须改用EntityRef
ET0021禁止void异步方法统一返回ETTask
ET0022禁止 Server 引用ET.Client命名空间双端隔离
ET0023LSEntity 禁止浮点字段锁定帧确定性要求
ET0024/ET0025EntitySystem 生成完整性存在未生成的生命周期函数;标签位置错误
ET0026实体类内必须用 Fiber 输出日志多 Fiber 隔离日志
ET0027实体类 HashCode 禁止重复名称哈希冲突检查
ET0028禁止同时标记 Component 和 Child角色冲突
ET0029禁止泛型实体类简化类型系统
ET0030消息类禁止实体字段网络消息可序列化要求
ET0031禁止new构造禁用类型强制走对象池
ET0032Model 程序集禁止非实体类除非加[EnableClass]
ET0034/ET0035CoroutineLock 生命周期禁止using获取;禁止手动Dispose(锁可能超时自动释放,重复释放会出问题)
ET0036测试用例命名规范测试方法命名检查
ET0037禁止直接 GetComponent 被禁组件[EnableGetComponent(typeof(T))]
ET0038System 类目录位置一致Model/ModelView 中的 Entity,其 System 必须在 Hotfix/HotfixView 对应相对位置
ET0039禁止全局入口访问未授权 Singleton[AllowInstance],改用Entity.GetSingleton<T>()/Fiber.GetSingleton<T>()
ET0040IPool 类型必须可清理必须能生成或手写显式IPool.Clear()
ET1001ETSystem 函数必须位于静态分部类生成前提
ET1002–ET1004await 实体检查 / 字段访问 / 循环调用异步与调用链安全

3.2 规则描述与分类:DiagnosticRules.cs

每条规则的 Title、MessageFormat、Description、所属分类与严重级别都定义在 Config/DiagnosticRules.cs 中。以ETTaskInSyncMethodAnalyzerRule为例:

private const string Title = "ETTask方法调用在非异步方法体内使用错误"; private const string MessageFormat = "方法: {0} 在非异步方法体内使用时需要添加.Coroutine()后缀"; private const string Description = "ETTask方法调用在非异步方法体内使用错误."; public static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticIds.ETTaskInSyncMethodAnalyzerRuleId, // "ET0008" Title, MessageFormat, DiagnosticCategories.Hotfix, // 分类:Hotfix 程序集 DiagnosticSeverity.Error, // 级别:编译错误 true, Description);

规则分类定义在 Config/DiagnosticCategories.cs,共四类:

public const string Generator = "ETGeneratorAnalyzers"; // 生成器相关 public const string Hotfix = "ETHotfixProjectAnalyzers"; // Hotfix/HotfixView public const string Model = "ETModelProjectAnalyzers"; // Model/ModelView public const string All = "ETAllProjectAnalyzers"; // 全部程序集

而某条规则具体作用在哪些程序集,由 Config/AnalyzeAssembly.cs 定义:AllModel(Model、ModelView)、AllHotfix(Hotfix、HotfixView)、AllModelHotfix(四者)、All(Core、Loader、Model、Hotfix、ModelView、HotfixView)。例如EntityComponentAnalyzerAnalyzeAssembly.AllModelHotfix上注册(见 EntityComponentAnalyzer.cs),意味着该约束同时作用于 Model 与 Hotfix 两侧代码。

3.3 分析器的统一开关与公共设施

所有 Analyzer 都受一个全局开关控制。在 AnalyzerGlobalSetting.cs 中:

/// <summary> /// 是否开启项目的所有分析器 /// </summary> public static bool EnableAnalyzer = true;

Analyzer 在Initialize中首先判断AnalyzerGlobalSetting.EnableAnalyzer,为false时直接返回,从而可以整体关闭静态检查(例如在生成代码等特殊场景)。

公共语法/符号工具集中在 AnalyzerHelper.cs,提供了如GetFirstChild<T>()(获取首个指定类型子节点)、GetParentClassDeclaration()(向上查找所属类声明)、HasAttribute()(判断类型是否带指定 Attribute)等扩展方法,所有 Analyzer 复用。

四、Analyzer 实现原理:以 EntityComponentAnalyzer 为例

分析器的典型实现路径可以以 EntityComponentAnalyzer.cs 为样板进行拆解:

  1. 注册[DiagnosticAnalyzer(LanguageNames.CSharp)]标记类;SupportedDiagnostics声明产出EntityComponentAnalyzerRule.Rule(ET0007)与DisableAccessEntityChildAnalyzerRule.Rule(ET0014);
  2. 装配Initialize中先判断AnalyzerGlobalSetting.EnableAnalyzer,再通过RegisterCompilationStartAction校验程序集是否在AnalyzeAssembly.AllModelHotfix中,命中后注册语义模型分析回调;
  3. 语义分析:遍历语法树中所有成员访问表达式,筛选AddComponent/GetComponent(即Definition.ComponentMethod),解析调用者类型与组件类型:
    • 若调用者是Entity/LSEntity基类,则进入"禁止直接访问 Child/Component"检查(除非标记了EnableAccessEntiyChild);
    • 若调用者是 Entity 的直接子类,则进一步校验组件类型是否被允许:泛型调用从TypeArgumentListSyntax提取组件类型,非泛型调用(AddComponent(typeOf(...))或变量形式)则从实参符号解析;
  4. 上报:校验失败即context.ReportDiagnostic(...),提示开发者"若要允许该类型作为参数,请使用ComponentOfAttribute对组件类标记父级实体类型"。

这条链路完整展示了 ET 静态检查的核心思路:不依赖运行时,而是在编译期用 Roslyn 语义模型把"组件—父实体"关系契约固化成编译错误。类似地,AddChildTypeAnalyzer(ET0001)、EntitySystemAnalyzer(ET0024,检查生命周期函数是否已生成)、CoroutineLockAnalyzer(ET0034/ET0035)都遵循同一模式。

五、Source Generator:样板代码的"自动生产线"

Generator 目录下共有四个生成器:ETSystemGenerator、ETGetComponentGenerator、IPoolClearGenerator 与ETEntitySerializeFormatterGenerator(实体序列化 Formatter 生成)。

5.1 ETSystemGenerator:ECS 生命周期的核心生成器

这是 ET 框架"Entity 只管数据、System 只管逻辑"分层原则的编译期实现。其入口标注[Generator(LanguageNames.CSharp)](见 ETSystemGenerator.cs),通过SyntaxContextReceiver收集所有带模板 Attribute 的方法声明,再按"命名空间 + 类名"分组,为每个静态类生成一个{namespace}.{className}.EntitySystems.g.cs分部类文件。

生成器会先校验 System 类必须是静态分部类(否则上报 ET1001),随后从方法符号中解析出参数类型列表,按模板逐项替换占位符。模板定义在 AttributeTemplate.cs,共 6 套:

① EntitySystem(实体生命周期,如 Awake/Update/Destroy):

$attribute$ public class $argsTypesUnderLine$_$methodName$System: $methodName$System<$argsTypes$> { protected override $returnType$ $methodName$($argsTypesVars$) { $return$$argsVars0$.$methodName$($argsVarsWithout0$); } }

② LSEntitySystem(锁定帧实体生命周期):与 EntitySystem 类似,但强制void返回,保证确定性。

③ MessageHandler:

$attribute$ public class $className$_$methodName$_Handler: MessageHandler<$argsTypesWithout0$> { protected override async ETTask Run($argsTypesVars$) { await $className$.$methodName$($argsVars$); } }

④ ActorMessageHandler:生成继承ActorMessageHandler<$argsTypes$>的 Handler;⑤ ActorMessageLocationHandler:生成继承ActorMessageLocationHandler<$argsTypes$>的 Handler;⑥ Event:生成继承AEvent<$argsTypes$>的事件处理类,Runawait $className$.$methodName$(...)

模板中$argsTypesUnderLine$是参数类型拼接(./<>/[]替换为_/Array)得到的唯一类名片段,$argsVarsWithout0$去掉首个参数后的实参列表。也就是说,开发者只需在一个静态类里写普通静态方法并打上标记,编译器就会自动产出对应的 System 子类、Handler 子类或 AEvent 子类,这正是 ET 代码量大幅缩减的根源。

5.2 ETGetComponentGenerator:组件访问的强类型扩展

该生成器扫描所有带ComponentOfAttribute且带类型参数的类(见 ETGetComponentGenerator.cs),为每个"父实体 + 组件"组合生成强类型扩展方法:

public static {{getComponentName}} Get{{getComponentName}} (this {{parentEntityName}} self) { return self.GetComponent<{{getComponentName}}>(); }

生成结果按命名空间聚合到ETGetComponentGenerator.{nameSpace}.g.cs文件中,类名为{AssemblyName}_ETGetComponentExtension。这样业务代码可以写unit.GetMoveComponent()这样语义化的访问,而不必每次都写泛型GetComponent<MoveComponent>()

5.3 IPoolClearGenerator:对象池回池清理的自动补齐

ET 的对象池要求实现IPool的类型提供显式void IPool.Clear(),用于回池前清理字段、避免与业务Clear()冲突。但手写这段样板既繁琐又易漏,于是有了 IPoolClearGenerator:

  • 生成条件ShouldGenerate):类型直接实现ET.IPool、是partial类、未手写显式IPool.Clear(),且没有需要手工处理的成员;
  • 清理策略:可自动清理的集合字段/属性(实现了ICollection/IDictionary/ISet等且有无参Clear())生成this.xxx?.Clear();;其他普通字段/属性生成this.xxx = default;IsFromPoolInstanceIdISceneViewGO等基础设施成员跳过;
  • 基类处理:若基类链上有可清理的集合类型,会先追加base.Clear();
  • 兜底检查:若成员中存在IDisposable/IEnumerable等需要手工管理清理的类型(NeedsManualClear),则不自动生成,转由 ET0040 规则提示开发者手写。

生成代码形态(见 IPoolClearGenerator.cs):

public partial class MyPoolObject { void global::ET.IPool.Clear() { this.bag?.Clear(); this.owner = default; } }

对应的 ET0040 诊断(IPoolClearAnalyzerRule)在生成器无法完成时提示:"Type: {0} 实现了 IPool,但无法生成或找到显式 IPool.Clear() 方法。请将类改为 partial,或手写 void IPool.Clear()。" 生成器与分析器在此形成闭环。

六、CodeFixer:编译错误的一键修复

CodeFixer 目录下有 EntitySystemCodeFixProvider.cs、BracesCodeFixProvider.csModuleCodeFixProvider.cs三个修复器。

EntitySystemCodeFixProvider为例:它针对EntitySystemAnalyzerRuleId(ET0024,"Entity 类存在未生成的生命周期函数")提供名为"Generate Entity System"的代码操作。修复流程是:

  1. 从诊断位置向上定位到目标ClassDeclarationSyntax
  2. 从诊断Properties中读取生命周期接口序列(EntitySystemInterfaceSequence,以/分隔)及各方法的参数描述;
  3. 逐个调用CreateEntitySystemMethodSyntax生成对应的 System 方法语法节点(含[EntitySystem]标签);
  4. 批量插入类成员开头,并套用格式化注解后替换语法树,完成一键修复。

这解释了 ET0024 为什么被设计为可修复诊断:开发者为 Entity 声明了生命周期接口(如IAwake)但忘了写 System 方法时,IDE 会直接提示并自动补齐。另外BracesCodeFixProvider处理花括号风格问题,ModuleCodeFixProvider处理模块声明问题,共同构成"报错—修复"的完整闭环。

七、包的构建与集成方式

7.1 工程配置要点

生成器工程 ET.SourceGenerator.csproj 的关键配置:

  • TargetFramework=netstandard2.0:Roslyn 分析器/生成器必须在 netstandard2.0 下编译才能被dotnet build与 IDE 加载;
  • 引用Microsoft.CodeAnalysis.CSharp.WorkspacesMicrosoft.CodeAnalysis.CSharp4.3.0、Microsoft.CodeAnalysis.Analyzers3.3.3(PrivateAssets=all,不向外传播);
  • <Compile Include="$(SolutionDir)Packages\cn.etetet.*\DotNet~\SourceGenerator\**\*.cs">把仓库内所有包的DotNet~/SourceGenerator目录下的源码统一编入本工程——即其他包也可以在自己的DotNet~/SourceGenerator中贡献生成器/分析器代码,实现按包分发的分布式扩展;
  • 自定义CustomAfterBuild目标:构建完成后把生成的ET.SourceGenerator.dll拷贝到包根目录(即仓库中可见的ET.SourceGenerator.dll),供 Unity/IDE 作为 analyzer 加载。

7.2 标记程序集与包发布

  • 包根目录下已附带编译产物ET.SourceGenerator.dll(及.meta),是分析器在 Unity 中的实际加载入口;
  • DotNet~/ET.SourceGeneratorAttribute/是独立的标记程序集工程(ET.SourceGeneratorAttribute.csproj),其中NoCut.cs提供空方法占位,防止链接器裁剪掉 Attribute 程序集;
  • package.json 声明包名为cn.etetet.sourcegenerator、显示名ET.SourceGenerator、版本0.0.7、最低 Unity 版本2022.3,描述即"et框架的分析器跟代码生成器"。

八、开发约定:如何正确扩展这个包

包的 AGENTS.md 规定了四条硬性约定,这也是任何新增/修改生成器与分析器时必须遵守的流程:

8.1 通用代码规则以上层规范为准

通用代码规则以 Packages/cn.etetet.harness/AGENTS.md 和et-codeskill 为准。

cn.etetet.harness是 ET 项目的 AI 技能分发包,其中et-codeskill 覆盖"新建或修改 Entity、Component、System、Helper""处理 ECS 分层、组件存在性契约、Module 分析器、分析器报错"等场景;同时该文件明确了项目唯一编译入口:"分析器编译要使用ET.sln",并且"项目只有一个编译命令,必须使用dotnet build ET.sln"。因此开发本包代码前,应先把et-code相关规则作为代码风格与契约的基线。

8.2 修改后必须用dotnet build ET.sln验证

分析器和生成器修改后必须使用dotnet build ET.sln验证。

这是本包唯一的验证手段:分析器/生成器属于编译期设施,必须通过全量解决方案构建来确认(1)生成器工程自身可编译;(2)生成的代码能被解决方案中所有引用它的工程接受;(3)没有引入新的诊断误报。验证命令(在仓库根目录执行):

dotnet build ET.sln

8.3 新增诊断时同步维护两个 Config 文件

新增诊断时同步维护Config/DiagnosticIds.csConfig/DiagnosticRules.cs

也就是说,一条新规则需要三处配套改动才能合入:

  1. 在 Config/DiagnosticIds.cs 中登记新的规则 ID 常量(遵循ET00xx/ET10xx编号体系);
  2. 在 Config/DiagnosticRules.cs 中定义对应的DiagnosticDescriptor(Title/MessageFormat/Description/分类/严重级别);
  3. Analyzer/下实现DiagnosticAnalyzer,并依据AnalyzeAssembly选择作用的程序集集合。

同时应保持 ID 常量与 Rule 描述一一对应,避免出现"有 ID 无规则"或"有规则无 ID"的半成品状态。

8.4 不手工生成.meta、不修改 Unity 工程文件

不手工生成.meta或修改 Unity 工程文件。

.meta文件由 Unity 资源数据库维护(对应仓库中的*.cs.meta),手工编辑会破坏 GUID 引用;生成器/分析器代码位于DotNet~目录,属于纯 .NET 工程,也不需要改动任何 Unity 工程文件。需要时通过 Unity 刷新自动生成。

九、小结

cn.etetet.sourcegenerator是 ET 框架工程化的"编译期引擎",它把三类工作固化在构建阶段:

  • 生成ETSystemGenerator依据标记把 System/Handler/Event 样板自动产出,ETGetComponentGenerator生成强类型组件访问扩展,IPoolClearGenerator自动补齐对象池清理;
  • 检查:以ET0001ET0040ET1001ET1004近 40 条诊断,在编译期锁定 ECS 契约(组件/子实体父级声明、EntityRef 替换实体字段、LSEntity 确定性、Fiber 日志、CoroutineLock 生命周期、Singleton 访问边界等);
  • 修复EntitySystemCodeFixProvider等 CodeFixer 对可修复诊断提供一键生成。

对框架使用者而言,只需记住"打标记、写静态方法、交给编译器"的开发范式,并理解dotnet build ET.sln是分析与生成生效的唯一入口;对框架贡献者而言,则需严格遵守 AGENTS.md 的开发约定——以et-codeskill 为代码基线、改后必须全量构建验证、新增诊断时同步维护DiagnosticIds.csDiagnosticRules.cs、绝不手工改动.meta与 Unity 工程文件。正是这套"标记驱动、编译期产出、规则约束"的体系,支撑起 ET 框架大规模 ECS 代码的整洁、一致与高性能。

【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询