From 48e45787f34cc203f107c0ef5e60192a03121f98 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:26:13 +0800 Subject: [PATCH] =?UTF-8?q?docs(source-generators):=20=E6=94=B6=E5=8F=A3?= =?UTF-8?q?=E4=B8=8A=E4=B8=8B=E6=96=87=E4=B8=8E=E4=BC=98=E5=85=88=E7=BA=A7?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=99=A8=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重写 ContextAware 与 Priority 专题页,按当前生成成员、priority-aware API 和兼容边界说明使用方式\n- 更新 documentation-governance-and-refresh 的 tracking 与 trace,记录 RP-007 与后续 Godot 生成器核对重点\n- 验证 docs 站点构建通过 --- ...ntation-governance-and-refresh-tracking.md | 22 +- ...umentation-governance-and-refresh-trace.md | 29 + .../context-aware-generator.md | 501 ++++------- .../source-generators/priority-generator.md | 837 ++++-------------- 4 files changed, 344 insertions(+), 1045 deletions(-) diff --git a/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md b/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md index 5368e9e1..eaeba424 100644 --- a/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md +++ b/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md @@ -7,20 +7,21 @@ ## 当前恢复点 -- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-006` +- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-007` - 当前阶段:`Phase 3` - 当前焦点: - 已完成 `docs/zh-CN/core/events.md`、`property.md` 与 `logging.md` 的专题页重写 - 已按源码与测试复核 `docs/zh-CN/core/state-management.md`、`coroutine.md`,当前内容与实现基本一致,无需再做 机械改写 - 已完成 `docs/zh-CN/game/scene.md` 与 `ui.md` 的专题页重写,当前内容已回到“项目自接 factory/root + router 基类”的真实边界 - - 下一轮需要把重心转到 `docs/zh-CN/source-generators/*` 的专题页核对 + - 已完成 `docs/zh-CN/source-generators/context-aware-generator.md` 与 `priority-generator.md` 的专题页重写,当前内容已回到“真实生成成员、推荐 API 与兼容边界”的结构 + - 下一轮需要把重心转到 Godot 相关生成器页面核对 ## 当前状态摘要 - 文档治理规则已收口到仓库规范,README、站点入口与采用链路不再依赖旧文档自证 - 高优先级模块入口与 `core` 关键专题页已回到可作为默认导航入口的状态,本轮计划中的 `core` 剩余高风险页面已完成收口 -- 当前主题仍是 active topic,因为 `source-generators` 栏目下仍可能包含与实现漂移的旧内容 +- 当前主题仍是 active topic,因为 `source-generators` 栏目下的 Godot 相关页面仍可能包含与实现漂移的旧内容 ## 当前活跃事实 @@ -45,12 +46,17 @@ - `docs/zh-CN/game/ui.md` 已改成“Page 栈、layer UI、输入动作仲裁、World 阻断与暂停语义”的结构,明确 `Show(...)` 不适用于 `UiLayer.Page` - 本轮重写后再次执行 `cd docs && bun run build` 通过,当前 `game` 栏目入口与专题页改动没有破坏站点构建 +- `docs/zh-CN/source-generators/context-aware-generator.md` 已改成“真实生成成员、provider/实例缓存语义、与 `ContextAwareBase` 的边界、测试接法”的结构, + 不再用旧版简化生成代码替代当前实现 +- `docs/zh-CN/source-generators/priority-generator.md` 已改成“生成 `IPrioritized`、priority-aware 检索 API、动态优先级边界与诊断”的结构, + 不再把 `GetAllByPriority()` / `system.Init()` 当作所有场景的默认示例 +- 本轮重写后再次执行 `cd docs && bun run build` 通过,当前 `source-generators` 栏目改动没有破坏站点构建 ## 当前风险 - 旧专题页示例失真风险:`docs/zh-CN/game/*` 与 `source-generators/*` 中仍可能保留看似合理但与真实实现不一致的示例 - - 缓解措施:`game/scene.md` 与 `ui.md` 已完成收口;继续按源码、测试、`*.csproj` 与 `ai-libs/` 下已验证参考实现核对 - `source-generators/*`,不把旧文档当事实来源 + - 缓解措施:`game/scene.md`、`ui.md`、`source-generators/context-aware-generator.md` 与 `priority-generator.md` 已完成收口; + 继续按源码、测试、`*.csproj` 与 `ai-libs/` 下已验证参考实现核对剩余 Godot 相关页面,不把旧文档当事实来源 - 采用路径误导风险:根聚合包与模块边界若再次被写错,会继续误导消费者的包选择 - 缓解措施:保持“源码与包关系优先”的证据顺序,改动采用说明时同步核对包依赖与生成器 wiring - Active 入口回膨胀风险:后续若把栏目级重写过程直接追加到 active 文档,会再次拖慢恢复 @@ -73,7 +79,7 @@ ## 下一步 -1. 继续核对 `docs/zh-CN/source-generators/*`,优先处理仍引用旧初始化方式、旧聚合包名或过时生成器 wiring 的页面 -2. 重点复核 `priority-generator.md`、`context-aware-generator.md` 与 Godot 相关生成器页面,确认示例仍与当前 runtime / - generator 入口一致 +1. 继续核对 Godot 相关生成器页面,优先处理 `godot-project-generator.md`、`get-node-generator.md` 与 + `bind-node-signal-generator.md` +2. 重点确认 `project.godot`、`AutoLoad` / `InputActions`、`GetNode` / `BindNodeSignal` 示例仍与当前包关系和生成器入口一致 3. 若 active trace 再积累新的已完成阶段,按恢复点粒度迁入 `archive/traces/`,避免默认启动入口再次膨胀 diff --git a/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md b/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md index 32f0de47..1fb0d47d 100644 --- a/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md +++ b/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md @@ -152,3 +152,32 @@ 2. 重点复核 `priority-generator.md`、`context-aware-generator.md` 与 Godot 相关生成器页面,确认示例仍与当前 runtime / generator 入口一致 3. 若 `source-generators` 出现多页连续收口结果,再按恢复点粒度整理 active trace,避免默认入口继续膨胀 + +### 阶段:Core Source Generator 关键专题页收口(RP-007) + +- 依据 `documentation-governance-and-refresh` active tracking 的下一步,优先复核 + `docs/zh-CN/source-generators/context-aware-generator.md` 与 `priority-generator.md` +- 对照 `GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs`、`GFramework.Core/Rule/ContextAwareBase.cs`、 + `GFramework.Core/Extensions/ContextAwareServiceExtensions.cs`、`GFramework.Core.SourceGenerators/Bases/PriorityGenerator.cs`、 + `GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs` 与相关诊断定义后确认: + - `context-aware-generator.md` 仍在展示旧版简化生成代码,没有说明当前实例缓存、类型级共享 provider、同步锁以及 + `ContextAwareBase` 的不同默认回退路径 + - `priority-generator.md` 仍把 `[Priority]` 写成“标了就自动改变顺序”的教程式功能说明,并大量使用 + `GetAllByPriority()`、`system.Init()` 这类不适合作为当前 `IContextAware` 路径默认示例的旧写法 +- 重写 `context-aware-generator.md`,使其回到“最小用法、当前生成成员、provider 与实例缓存语义、与 `ContextAwareBase` + 和 Context Get 注入的关系、测试边界”的结构 +- 重写 `priority-generator.md`,使其回到“只生成 `IPrioritized`、priority-aware API 在不同层上的入口、动态优先级边界、 + 诊断与约束”的结构 +- 新版两页都明确了:排序效果取决于调用方是否走 priority-aware API;`[ContextAware]` 生成路径与 + `ContextAwareBase` 不是同一套默认行为 + +### 验证(RP-007) + +- `cd docs && bun run build` + +### 下一步(RP-007) + +1. 继续核对 Godot 相关生成器页面,优先处理 `godot-project-generator.md`、`get-node-generator.md` 与 + `bind-node-signal-generator.md` +2. 重点确认 `project.godot`、`AutoLoad` / `InputActions`、`GetNode` / `BindNodeSignal` 示例仍与当前包关系和生成器入口一致 +3. 若 Godot 页面也出现连续收口结果,再按恢复点粒度整理 active trace,避免默认入口继续膨胀 diff --git a/docs/zh-CN/source-generators/context-aware-generator.md b/docs/zh-CN/source-generators/context-aware-generator.md index 9574f603..3d9aafaf 100644 --- a/docs/zh-CN/source-generators/context-aware-generator.md +++ b/docs/zh-CN/source-generators/context-aware-generator.md @@ -1,403 +1,198 @@ +--- +title: ContextAware 生成器 +description: 说明 [ContextAware] 当前会生成什么、何时使用、与 ContextAwareBase 的边界以及测试场景。 +--- + # ContextAware 生成器 -> 自动实现 IContextAware 接口,提供架构上下文访问能力 +`[ContextAware]` 是 `GFramework.Core.SourceGenerators` 中最常用的一类生成器。它的职责很明确: -## 概述 +- 为当前类型自动补齐 `IContextAware` +- 提供可复用的上下文懒加载入口 +- 让类型可以直接使用 `this.GetSystem()`、`this.GetModel()`、`this.GetUtility()` 等扩展方法 -ContextAware 生成器为标记了 `[ContextAware]` 属性的类自动生成 `IContextAware` 接口实现,使类能够便捷地访问架构上下文( -`IArchitectureContext`)。这是 GFramework 中最常用的源码生成器之一,几乎所有需要与架构交互的组件都会使用它。 +它不负责注册服务,也不会替你决定应该取哪个 `System` / `Model`。它解决的是“当前类型如何拿到架构上下文”。 -### 核心功能 +## 当前包关系 -- **自动接口实现**:无需手动实现 `IContextAware` 接口的 `SetContext()` 和 `GetContext()` 方法 -- **懒加载上下文**:`Context` 属性在首次访问时自动初始化 -- **默认提供者**:使用 `GameContextProvider` 作为默认上下文提供者 -- **测试友好**:支持通过 `SetContextProvider()` 配置自定义上下文提供者 +- 特性来源:`GFramework.Core.SourceGenerators.Abstractions` +- 生成器实现:`GFramework.Core.SourceGenerators` +- 运行时接口:`GFramework.Core.Abstractions.Rule.IContextAware` +- 常用扩展方法:`GFramework.Core.Extensions` -## 基础使用 +如果只安装运行时 `GFramework.Core` 而没有安装 `Core.SourceGenerators`,`[ContextAware]` 本身不会生效。 -### 标记类 - -使用 `[ContextAware]` 属性标记需要访问架构上下文的类: +## 最小用法 ```csharp -using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.Abstractions.Controller; +using GFramework.Core.Extensions; +using GFramework.Core.SourceGenerators.Abstractions.Rule; [ContextAware] public partial class PlayerController : IController { public void Initialize() { - // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口) - var playerModel = this.GetModel(); - var combatSystem = this.GetSystem(); + var playerModel = this.GetModel(); + var combatSystem = this.GetSystem(); - this.SendEvent(new PlayerInitializedEvent()); - } - - public void Attack(Enemy target) - { - var damage = this.GetUtility().Calculate(this, target); - this.SendCommand(new DealDamageCommand(target, damage)); + combatSystem.Bind(playerModel); } } ``` -### 必要条件 +当前最重要的前置条件只有两个: -标记的类必须满足以下条件: +- 必须是 `class` +- 必须声明为 `partial` -1. **必须是 `partial` 类**:生成器需要生成部分类代码 -2. **必须是 `class` 类型**:不能是 `struct` 或 `interface` +如果缺少这两个条件,生成器不会补代码。 + +## 当前会生成什么 + +按当前源码,`[ContextAware]` 会为目标类型生成: + +- `IContextAware` 的显式接口实现 +- 受保护的 `Context` 属性 +- 类型级静态 `SetContextProvider(...)` +- 类型级静态 `ResetContextProvider()` +- 一个实例级 `_context` 缓存字段 +- 一个类型级共享的 `_contextProvider` +- 一个类型级锁 `_contextSync` + +这意味着它不是“每次访问都现查当前架构”。当前行为更接近: + +1. 先看当前实例是否已经缓存了上下文 +2. 如果没有,就在同步锁内取共享 provider +3. 若 provider 为空,则回退到 `GameContextProvider` +4. 把拿到的上下文缓存到当前实例 + +## 当前语义里最容易误解的点 + +### provider 是按类型共享,不是按实例共享 + +`SetContextProvider(...)` 影响的是“这个生成类型的后续实例或尚未初始化上下文的实例”,不是全仓库所有 `[ContextAware]` +类型共享同一个 provider。 + +### provider 切换不会自动刷新已缓存实例 + +一旦某个实例已经把上下文缓存进 `_context`,后续再调用: + +- `SetContextProvider(...)` +- `ResetContextProvider()` + +都不会自动改写这个实例的已缓存上下文。 + +如果你确实要覆盖某个现有实例的上下文,应显式调用: ```csharp -// ✅ 正确 -[ContextAware] -public partial class MyController { } - -// ❌ 错误:缺少 partial 关键字 -[ContextAware] -public class MyController { } - -// ❌ 错误:不能用于 struct -[ContextAware] -public partial struct MyStruct { } +((IContextAware)controller).SetContext(context); ``` -## 生成的代码 +### 生成路径和 `ContextAwareBase` 不是一回事 -编译器会为标记的类自动生成以下代码: +当前源码里两者的默认回退策略不同: + +- `[ContextAware]` 生成实现 + - 通过共享 provider 回退,默认 provider 是 `GameContextProvider` + - 带同步锁,支持 `SetContextProvider(...)` / `ResetContextProvider()` +- `ContextAwareBase` + - 只维护简单的实例级缓存 + - 不维护共享 provider + - 默认直接回退到 `GameContext.GetFirstArchitectureContext()` + +因此,旧文档里把两条路径混写成“只是写法不同”已经不准确。 + +## 何时使用 `[ContextAware]` + +优先用于这些场景: + +- 你的类型不是 `AbstractSystem`、`AbstractModel`、`AbstractCommand` 这类已经继承 `ContextAwareBase` 的框架基类 +- 你希望在测试中显式切换 provider +- 你需要在同一生成类型上统一切换上下文来源 +- 你在 Godot 节点、Controller、ViewModel、包装器类型上只想获得上下文访问能力 + +典型例子: + +- `IController` 实现 +- Godot `Node` / `Control` 的项目侧包装器 +- 不继承框架基类但要访问架构的辅助类型 + +## 何时改用 `ContextAwareBase` + +以下场景优先考虑 `ContextAwareBase` 或已经继承它的框架基类: + +- 你本来就继承 `AbstractSystem`、`AbstractModel`、`AbstractCommand`、`AbstractQuery` +- 你不需要类型级共享 provider +- 你只需要简单的实例级上下文缓存 +- 调用线程模型已经天然串行,不需要生成实现那套 provider 切换与同步语义 + +如果一个类型已经通过继承链拿到了 `ContextAwareBase`,通常没必要再额外标 `[ContextAware]`。 + +## 与 Context Get 注入的关系 + +`[GetModel]`、`[GetSystem]`、`[GetUtility]`、`[GetService]` 这类字段注入生成器,并不是独立工作的。 + +按当前 `ContextGetGenerator` 的判定规则,目标类型必须满足以下三者之一: + +- 标记了 `[ContextAware]` +- 实现了 `IContextAware` +- 继承了 `ContextAwareBase` + +所以更准确的理解是: + +- `[ContextAware]` 负责“让类型成为 context-aware 类型” +- Context Get 系列特性负责“在这个前提下继续减少字段取值样板” + +## 测试场景 + +如果测试里不想依赖默认全局上下文,推荐显式配置 provider: ```csharp -// -#nullable enable +PlayerController.SetContextProvider(new TestContextProvider(testArchitecture.Context)); -namespace YourNamespace; - -partial class PlayerController : global::GFramework.Core.Abstractions.Rule.IContextAware +try { - private global::GFramework.Core.Abstractions.Architecture.IArchitectureContext? _context; - private static global::GFramework.Core.Abstractions.Architecture.IArchitectureContextProvider? _contextProvider; - - /// - /// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider) - /// - protected global::GFramework.Core.Abstractions.Architecture.IArchitectureContext Context - { - get - { - if (_context == null) - { - _contextProvider ??= new global::GFramework.Core.Architecture.GameContextProvider(); - _context = _contextProvider.GetContext(); - } - - return _context; - } - } - - /// - /// 配置上下文提供者(用于测试或多架构场景) - /// - /// 上下文提供者实例 - public static void SetContextProvider(global::GFramework.Core.Abstractions.Architecture.IArchitectureContextProvider provider) - { - _contextProvider = provider; - } - - /// - /// 重置上下文提供者为默认值(用于测试清理) - /// - public static void ResetContextProvider() - { - _contextProvider = null; - } - - void global::GFramework.Core.Abstractions.Rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.Architecture.IArchitectureContext context) - { - _context = context; - } - - global::GFramework.Core.Abstractions.Architecture.IArchitectureContext global::GFramework.Core.Abstractions.Rule.IContextAware.GetContext() - { - return Context; - } + var controller = new PlayerController(); + controller.Initialize(); } -``` - -### 代码解析 - -生成的代码包含以下关键部分: - -1. **私有字段**: - - `_context`:缓存的上下文实例 - - `_contextProvider`:静态上下文提供者(所有实例共享) - -2. **Context 属性**: - - `protected` 访问级别,子类可访问 - - 懒加载:首次访问时自动初始化 - - 使用 `GameContextProvider` 作为默认提供者 - -3. **配置方法**: - - `SetContextProvider()`:设置自定义上下文提供者 - - `ResetContextProvider()`:重置为默认提供者 - -4. **显式接口实现**: - - `IContextAware.SetContext()`:允许外部设置上下文 - - `IContextAware.GetContext()`:返回当前上下文 - -## 配置上下文提供者 - -### 测试场景 - -在单元测试中,通常需要使用自定义的上下文提供者: - -```csharp -[Test] -public async Task TestPlayerController() +finally { - // 创建测试架构 - var testArchitecture = new TestArchitecture(); - await testArchitecture.InitAsync(); - - // 配置自定义上下文提供者 - PlayerController.SetContextProvider(new TestContextProvider(testArchitecture)); - - try - { - // 测试代码 - var controller = new PlayerController(); - controller.Initialize(); - - // 验证... - } - finally - { - // 清理:重置上下文提供者 - PlayerController.ResetContextProvider(); - } -} -``` - -### 多架构场景 - -在某些高级场景中,可能需要同时运行多个架构实例: - -```csharp -public class MultiArchitectureManager -{ - private readonly Dictionary _architectures = new(); - - public void SwitchToArchitecture(string name) - { - var architecture = _architectures[name]; - var provider = new ScopedContextProvider(architecture); - - // 为所有使用 [ContextAware] 的类切换上下文 - PlayerController.SetContextProvider(provider); - EnemyController.SetContextProvider(provider); - // ... - } -} -``` - -## 使用场景 - -### 何时使用 [ContextAware] - -推荐在以下场景使用 `[ContextAware]` 属性: - -1. **Controller 层**:需要协调多个 Model/System 的控制器 -2. **Command/Query 实现**:需要访问架构服务的命令或查询 -3. **自定义组件**:不继承框架基类但需要上下文访问的组件 - -```csharp -[ContextAware] -public partial class GameFlowController : IController -{ - public async Task StartGame() - { - var saveSystem = this.GetSystem(); - var uiSystem = this.GetSystem(); - - await saveSystem.LoadAsync(); - await uiSystem.ShowMainMenuAsync(); - } -} -``` - -### 与 IController 配合使用 - -在 Godot 项目中,控制器通常同时实现 `IController` 和使用 `[ContextAware]`: - -```csharp -using GFramework.Core.Abstractions.Controller; -using GFramework.Core.SourceGenerators.Abstractions.Rule; - -[ContextAware] -public partial class PlayerController : Node, IController -{ - public override void _Ready() - { - // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口) - var playerModel = this.GetModel(); - var combatSystem = this.GetSystem(); - } -} -``` - -**说明**: - -- `IController` 是标记接口,标识这是一个控制器 -- `[ContextAware]` 提供架构访问能力 -- 两者配合使用是推荐的模式 - -### 何时继承 ContextAwareBase - -如果类需要更多框架功能(如生命周期管理),应继承 `ContextAwareBase`: - -```csharp -// 推荐:需要生命周期管理时继承基类 -public class PlayerModel : AbstractModel -{ - // AbstractModel 已经继承了 ContextAwareBase - protected override void OnInit() - { - var config = this.GetUtility().Load(); - } -} - -// 推荐:简单组件使用属性 -[ContextAware] -public partial class SimpleHelper -{ - public void DoSomething() - { - this.SendEvent(new SomethingHappenedEvent()); - } -} -``` - -## 与 IContextAware 接口的关系 - -生成的代码实现了 `IContextAware` 接口: - -```csharp -namespace GFramework.Core.Abstractions.Rule; - -public interface IContextAware -{ - void SetContext(IArchitectureContext context); - IArchitectureContext GetContext(); -} -``` - -这意味着标记了 `[ContextAware]` 的类可以: - -1. **被架构自动注入上下文**:实现 `IContextAware` 的类在注册到架构时会自动调用 `SetContext()` -2. **参与依赖注入**:可以作为 `IContextAware` 类型注入到其他组件 -3. **支持上下文传递**:可以通过 `GetContext()` 将上下文传递给其他组件 - -## 最佳实践 - -### 1. 始终使用 partial 关键字 - -```csharp -// ✅ 正确 -[ContextAware] -public partial class MyController { } - -// ❌ 错误:编译器会报错 -[ContextAware] -public class MyController { } -``` - -### 2. 在测试中清理上下文提供者 - -```csharp -[TearDown] -public void TearDown() -{ - // 避免测试之间的状态污染 PlayerController.ResetContextProvider(); - EnemyController.ResetContextProvider(); } ``` -### 3. 避免在构造函数中访问 Context +需要注意两点: -```csharp -[ContextAware] -public partial class MyController -{ - // ❌ 错误:构造函数执行时上下文可能未初始化 - public MyController() - { - var model = this.GetModel(); // 可能为 null - } +- `ResetContextProvider()` 只会重置共享 provider,不会清除已创建实例上的 `_context` +- 如果测试要复用同一实例并切换上下文,应该显式调用 `((IContextAware)instance).SetContext(...)` - // ✅ 正确:在初始化方法中访问 - public void Initialize() - { - var model = this.GetModel(); // 安全 - } -} -``` +## 诊断与约束 -### 4. 优先使用 Context 属性而非接口方法 +当前文档里最值得记住的约束只有这些: -```csharp -[ContextAware] -public partial class MyController -{ - public void DoSomething() - { - // ✅ 推荐:使用扩展方法 - var model = this.GetModel(); +- 非 `class` 会触发 `GF_Rule_001` +- 非 `partial` 不会生成实现,并会触发公共 partial 约束诊断 +- 嵌套、字段注入等其他错误通常由对应的 Context Get 生成器和其诊断补充报告 - // ❌ 不推荐:显式调用接口方法 - var context = ((IContextAware)this).GetContext(); - var model2 = context.GetModel(); - } -} -``` +## 与旧写法的边界 -## 诊断信息 +下面这些旧说法已经不够准确: -生成器会在以下情况报告编译错误: +- “`[ContextAware]` 只是帮你补一个简单的 `GetContext()`” +- “切换 provider 后,已有实例会自动跟着切换” +- “`[ContextAware]` 和 `ContextAwareBase` 的默认行为完全一致” -### GFSG001: 类必须是 partial +当前更准确的理解是: -```csharp -[ContextAware] -public class MyController { } // 错误:缺少 partial 关键字 -``` +- 生成实现带有实例缓存、类型级共享 provider 和同步锁 +- provider 切换只影响尚未缓存上下文的实例 +- `ContextAwareBase` 是更轻量的实例级缓存路径 -**解决方案**:添加 `partial` 关键字 +## 推荐阅读 -```csharp -[ContextAware] -public partial class MyController { } // ✅ 正确 -``` - -### GFSG002: ContextAware 只能用于类 - -```csharp -[ContextAware] -public partial struct MyStruct { } // 错误:不能用于 struct -``` - -**解决方案**:将 `struct` 改为 `class` - -```csharp -[ContextAware] -public partial class MyClass { } // ✅ 正确 -``` - -## 相关文档 - -- [Source Generators 概述](./index) -- [架构上下文](../core/context) -- [IContextAware 接口](../core/rule) -- [日志生成器](./logging-generator) +1. [context-get-generator.md](./context-get-generator.md) +2. [logging-generator.md](./logging-generator.md) +3. [../core/index.md](../core/index.md) +4. `GFramework.Core.SourceGenerators/README.md` diff --git a/docs/zh-CN/source-generators/priority-generator.md b/docs/zh-CN/source-generators/priority-generator.md index ef450225..3059ddb1 100644 --- a/docs/zh-CN/source-generators/priority-generator.md +++ b/docs/zh-CN/source-generators/priority-generator.md @@ -1,250 +1,104 @@ +--- +title: Priority 生成器 +description: 说明 [Priority] 当前会生成什么、何时生效、应配合哪些优先级 API 使用,以及动态优先级的边界。 +--- + # Priority 生成器 -> 自动实现 IPrioritized 接口,为类添加优先级标记 +`[Priority]` 的职责很简单:为目标类型自动生成 `IPrioritized.Priority`。 -Priority 生成器通过源代码生成器自动实现 `IPrioritized` 接口,简化优先级标记和排序逻辑的实现。 +它本身不是调度器,也不会自动改变系统、服务或处理器的执行顺序。只有调用方使用了“按优先级排序”的检索入口,生成出来的 +`Priority` 才会真正影响顺序。 -## 概述 +## 当前包关系 -### 核心功能 +- 特性来源:`GFramework.Core.SourceGenerators.Abstractions` +- 生成器实现:`GFramework.Core.SourceGenerators` +- 运行时契约:`GFramework.Core.Abstractions.Bases.IPrioritized` +- 预定义常量:`GFramework.Core.Abstractions.Bases.PriorityGroup` -- **自动实现接口**:自动实现 `IPrioritized` 接口的 `Priority` 属性 -- **优先级标记**:通过特性参数指定优先级值 -- **编译时生成**:在编译时生成代码,零运行时开销 -- **类型安全**:编译时类型检查,避免运行时错误 - -### 适用场景 - -- 系统初始化顺序控制 -- 事件处理器优先级排序 -- 服务注册顺序管理 -- 需要按优先级排序的任何场景 - -## 基础使用 - -### 标记优先级 - -使用 `[Priority]` 特性为类标记优先级: - -```csharp -using GFramework.Core.SourceGenerators.Abstractions.Bases; - -[Priority(10)] -public partial class MySystem -{ - // 自动生成 IPrioritized 接口实现 -} -``` - -### 生成代码 - -编译器会自动生成如下代码: - -```csharp -// -#nullable enable - -namespace YourNamespace; - -partial class MySystem : global::GFramework.Core.Abstractions.Bases.IPrioritized -{ - /// - /// 获取优先级值: 10 - /// - public int Priority => 10; -} -``` - -### 使用生成的优先级 - -生成的 `Priority` 属性可用于排序: - -```csharp -using GFramework.Core.Abstractions.Bases; - -// 获取所有实现了 IPrioritized 的系统 -var systems = new List { system1, system2, system3 }; - -// 按优先级排序(值越小,优先级越高) -var sorted = systems.OrderBy(s => s.Priority).ToList(); - -// 依次初始化 -foreach (var system in sorted) -{ - system.Initialize(); -} -``` - -## 优先级值语义 - -### 值的含义 - -| 优先级值范围 | 含义 | 使用场景 | -|--------|-------|------------------| -| 负数 | 高优先级 | 核心系统、关键事件处理器 | -| 0 | 默认优先级 | 普通系统、一般事件处理器 | -| 正数 | 低优先级 | 可延迟初始化的系统、非关键处理器 | - -### PriorityGroup 常量 - -推荐使用 `PriorityGroup` 预定义常量来标记优先级: +## 最小用法 ```csharp using GFramework.Core.Abstractions.Bases; using GFramework.Core.SourceGenerators.Abstractions.Bases; -[Priority(PriorityGroup.Critical)] // -100 -public partial class InputSystem : AbstractSystem { } - -[Priority(PriorityGroup.High)] // -50 -public partial class PhysicsSystem : AbstractSystem { } - -[Priority(PriorityGroup.Normal)] // 0 -public partial class GameplaySystem : AbstractSystem { } - -[Priority(PriorityGroup.Low)] // 50 -public partial class AudioSystem : AbstractSystem { } - -[Priority(PriorityGroup.Deferred)] // 100 -public partial class CleanupSystem : AbstractSystem { } -``` - -**PriorityGroup 常量定义**: - -```csharp -namespace GFramework.Core.Abstractions.Bases; - -public static class PriorityGroup +[Priority(PriorityGroup.High)] +public partial class SaveSystem : AbstractSystem { - public const int Critical = -100; // 关键:输入、网络等 - public const int High = -50; // 高:物理、碰撞等 - public const int Normal = 0; // 正常:游戏逻辑等 - public const int Low = 50; // 低:音频、特效等 - public const int Deferred = 100; // 延迟:清理、统计等 + protected override void OnInit() + { + } } ``` -## 使用场景 - -### 系统初始化顺序 - -控制系统初始化的顺序,确保依赖关系正确: +当前生成器会补出: ```csharp +public int Priority => PriorityGroup.High; +``` + +优先级值越小,优先级越高。 + +## 当前真正会读取优先级的入口 + +### `IIocContainer` + +如果你直接在容器层取集合,使用: + +```csharp +var handlers = container.GetAllByPriority(); +``` + +### `IArchitectureContext` + +当前推荐按组件类别使用这些 API: + +- `GetServicesByPriority()` +- `GetSystemsByPriority()` +- `GetModelsByPriority()` +- `GetUtilitiesByPriority()` + +### `IContextAware` 扩展方法 + +如果你已经在 `[ContextAware]` 类型或 `ContextAwareBase` 派生类型里,直接用: + +- `this.GetServicesByPriority()` +- `this.GetSystemsByPriority()` +- `this.GetModelsByPriority()` +- `this.GetUtilitiesByPriority()` + +这比旧文档里反复出现的 `this.GetAllByPriority()` 更贴近当前公开扩展方法。 + +## 最小接入示例 + +### 系统排序 + +```csharp +using GFramework.Core.Abstractions.Bases; using GFramework.Core.Abstractions.Systems; +using GFramework.Core.Extensions; using GFramework.Core.SourceGenerators.Abstractions.Bases; -using GFramework.Core.Abstractions.Bases; +using GFramework.Core.SourceGenerators.Abstractions.Rule; -// 输入系统最先初始化 [Priority(PriorityGroup.Critical)] public partial class InputSystem : AbstractSystem { protected override void OnInit() { - // 初始化输入处理 } } -// 物理系统次之 -[Priority(PriorityGroup.High)] -public partial class PhysicsSystem : AbstractSystem +[ContextAware] +public partial class SystemBootstrapper : IController { - protected override void OnInit() + public void Start() { - // 初始化物理引擎 - } -} - -// 游戏逻辑系统在中间 -[Priority(PriorityGroup.Normal)] -public partial class GameplaySystem : AbstractSystem -{ - protected override void OnInit() - { - // 初始化游戏逻辑 - } -} - -// 音频系统可以稍后 -[Priority(PriorityGroup.Low)] -public partial class AudioSystem : AbstractSystem -{ - protected override void OnInit() - { - // 初始化音频引擎 - } -} -``` - -在架构中按优先级初始化: - -```csharp -public class GameArchitecture : Architecture -{ - protected override void InitSystems() - { - // 获取所有系统并按优先级排序 - var systems = this.GetAllByPriority(); + var systems = this.GetSystemsByPriority(); foreach (var system in systems) { - system.Init(); - } - } -} -``` - -### 事件处理器优先级 - -控制事件处理器的执行顺序: - -```csharp -using GFramework.Core.Abstractions.Events; - -// 关键事件处理器最先执行 -[Priority(PriorityGroup.Critical)] -public partial class CriticalEventHandler : IEventHandler -{ - public void Handle(CriticalEvent e) - { - // 处理关键事件 - } -} - -// 普通处理器在中间执行 -[Priority(PriorityGroup.Normal)] -public partial class NormalEventHandler : IEventHandler -{ - public void Handle(CriticalEvent e) - { - // 处理普通逻辑 - } -} - -// 日志记录器最后执行 -[Priority(PriorityGroup.Deferred)] -public partial class EventLogger : IEventHandler -{ - public void Handle(CriticalEvent e) - { - // 记录日志 - } -} -``` - -事件总线按优先级调用处理器: - -```csharp -public class EventBus : IEventBus -{ - public void Send(TEvent e) where TEvent : IEvent - { - // 获取所有处理器并按优先级排序 - var handlers = this.GetAllByPriority>(); - - foreach (var handler in handlers) - { - handler.Handle(e); + system.Initialize(); } } } @@ -252,496 +106,111 @@ public class EventBus : IEventBus ### 服务排序 -控制多个服务实现的优先级: - ```csharp -// 高优先级服务 [Priority(PriorityGroup.High)] -public partial class PremiumService : IService +public partial class PremiumSaveMigration : ISaveMigration { - public void Execute() - { - // 优先执行 - } } -// 默认服务 -[Priority(PriorityGroup.Normal)] -public partial class DefaultService : IService -{ - public void Execute() - { - // 默认执行 - } -} - -// 后备服务 [Priority(PriorityGroup.Low)] -public partial class FallbackService : IService +public partial class MetricsSaveMigration : ISaveMigration { - public void Execute() +} + +var migrations = architecture.Context.GetServicesByPriority(); +``` + +## `PriorityGroup` 的角色 + +当前仓库提供了这些预定义常量: + +- `PriorityGroup.Critical` +- `PriorityGroup.High` +- `PriorityGroup.Normal` +- `PriorityGroup.Low` +- `PriorityGroup.Deferred` + +文档不应该把这些值解释成硬编码的生命周期阶段。它们只是团队共享的排序语义常量,具体“高优先级意味着先做什么”仍然取决于 +调用方对排序结果的使用方式。 + +如果项目有更细粒度的排序约定,也可以直接传 `int`,或在项目层自定义自己的优先级常量。 + +## 何时使用 `[Priority]` + +适合以下场景: + +- 类型顺序在编译期就能确定 +- 你不想手写 `IPrioritized` +- 同一类型的所有实例都应共享同一个优先级 + +常见例子: + +- 初始化顺序明确的系统 +- 顺序敏感的服务实现 +- 有先后要求的处理器或迁移器 + +## 何时不要使用 `[Priority]` + +以下场景应改为手写 `IPrioritized`: + +- 优先级要依赖运行时配置 +- 优先级要根据环境、开关或状态动态变化 +- 你已经手动实现了 `IPrioritized` + +例如: + +```csharp +public sealed class DynamicPrioritySystem : IPrioritized +{ + private readonly bool _enabled; + + public DynamicPrioritySystem(bool enabled) { - // 最后备选 + _enabled = enabled; } + + public int Priority => _enabled ? PriorityGroup.High : PriorityGroup.Deferred; } ``` -## 与 PriorityUsageAnalyzer 集成 +## 当前诊断与约束 -### GF_Priority_Usage_001 诊断 +`[Priority]` 当前有几条直接约束: -`PriorityUsageAnalyzer` 分析器会检测应该使用 `GetAllByPriority()` 而非 `GetAll()` 的场景: +- `GF_Priority_001` + - 只能标在 `class` +- `GF_Priority_002` + - 目标类型已经手写实现 `IPrioritized` +- `GF_Priority_003` + - 类型必须是 `partial` +- `GF_Priority_004` + - 特性值缺失或无效 +- `GF_Priority_005` + - 不支持嵌套类 -**错误示例**: +对文档而言,最关键的结论是: -```csharp -// ❌ 不推荐:可能未按优先级排序 -var systems = context.GetAll(); -``` +- `partial` 是强约束 +- 顶层类是强约束 +- 手写实现与生成实现只能二选一 -**正确示例**: +## 与旧写法的边界 -```csharp -// ✅ 推荐:确保按优先级排序 -var systems = context.GetAllByPriority(); -``` +下面这些旧写法或旧表述已经不再适合作为默认指导: -### 分析器规则 +- 在 `IContextAware` 类型里统一写 `this.GetAllByPriority()` +- 继续用 `system.Init()` 作为系统初始化示例 +- 把 `[Priority]` 写成“标了就会自动改变执行顺序” -当满足以下条件时,分析器会报告 `GF_Priority_Usage_001` 诊断: +当前更准确的理解是: -1. 类型实现了 `IPrioritized` 接口 -2. 使用了 `GetAll()` 方法 -3. 建议改用 `GetAllByPriority()` 方法 +- `[Priority]` 只生成 `Priority` +- 排序效果依赖容器、上下文或扩展方法是否走了 priority-aware API +- `IContextAware` 路径更推荐按组件类别使用 `GetSystemsByPriority` / `GetServicesByPriority` 等入口 -## 诊断信息 +## 推荐阅读 -### GF_Priority_001 - 只能应用于类 - -**错误信息**:`Priority attribute can only be applied to classes` - -**场景**:将 `[Priority]` 特性应用于非类类型 - -```csharp -[Priority(10)] -public interface IMyInterface // ❌ 错误 -{ -} - -[Priority(10)] -public struct MyStruct // ❌ 错误 -{ -} -``` - -**解决方案**:只在类上使用 `[Priority]` 特性 - -```csharp -[Priority(10)] -public partial class MyClass // ✅ 正确 -{ -} -``` - -### GF_Priority_002 - 已实现 IPrioritized 接口 - -**错误信息**:`Type '{ClassName}' already implements IPrioritized interface` - -**场景**:类已手动实现 `IPrioritized` 接口 - -```csharp -[Priority(10)] -public partial class MySystem : IPrioritized // ❌ 冲突 -{ - public int Priority => 10; // 手动实现 -} -``` - -**解决方案**:移除 `[Priority]` 特性或移除手动实现 - -```csharp -// 方案1:移除特性,使用手动实现 -public partial class MySystem : IPrioritized -{ - public int Priority => 10; -} - -// 方案2:移除手动实现,使用生成器 -[Priority(10)] -public partial class MySystem -{ - // 生成器自动实现 -} -``` - -### GF_Priority_003 - 必须声明为 partial - -**错误信息**:`Class '{ClassName}' must be declared as partial` - -**场景**:类未声明为 `partial` - -```csharp -[Priority(10)] -public class MySystem // ❌ 缺少 partial -{ -} -``` - -**解决方案**:添加 `partial` 关键字 - -```csharp -[Priority(10)] -public partial class MySystem // ✅ 正确 -{ -} -``` - -### GF_Priority_004 - 优先级值无效 - -**错误信息**:`Priority value is invalid` - -**场景**:特性参数无效或未提供 - -```csharp -[Priority] // ❌ 缺少参数 -public partial class MySystem -{ -} -``` - -**解决方案**:提供有效的优先级值 - -```csharp -[Priority(10)] // ✅ 正确 -public partial class MySystem -{ -} -``` - -### GF_Priority_005 - 不支持嵌套类 - -**错误信息**:`Nested class '{ClassName}' is not supported` - -**场景**:在嵌套类中使用 `[Priority]` 特性 - -```csharp -public partial class OuterClass -{ - [Priority(10)] - public partial class InnerClass // ❌ 错误 - { - } -} -``` - -**解决方案**:将嵌套类提取为独立的类 - -```csharp -[Priority(10)] -public partial class InnerClass // ✅ 正确 -{ -} -``` - -## 最佳实践 - -### 1. 使用 PriorityGroup 常量 - -避免使用魔法数字,优先使用预定义常量: - -```csharp -// ✅ 推荐:使用常量 -[Priority(PriorityGroup.Critical)] -public partial class InputSystem { } - -// ❌ 不推荐:魔法数字 -[Priority(-100)] -public partial class InputSystem { } -``` - -### 2. 预留优先级间隔 - -为未来扩展预留间隔: - -```csharp -public static class SystemPriority -{ - public const int Input = -100; - public const int PrePhysics = -90; // 预留扩展 - public const int Physics = -80; - public const int PostPhysics = -70; // 预留扩展 - public const int Gameplay = 0; - public const int PostGameplay = 10; // 预留扩展 - public const int Audio = 50; - public const int Cleanup = 100; -} -``` - -### 3. 文档化优先级语义 - -为自定义优先级值添加注释: - -```csharp -/// -/// 输入系统,优先级 -100,需要最先初始化以接收输入事件 -/// -[Priority(PriorityGroup.Critical)] -public partial class InputSystem : AbstractSystem -{ -} -``` - -### 4. 避免优先级冲突 - -当多个类有相同优先级时,执行顺序不确定。应避免依赖特定顺序: - -```csharp -// ❌ 不推荐:相同优先级,顺序不确定 -[Priority(0)] -public partial class SystemA { } - -[Priority(0)] -public partial class SystemB { } - -// ✅ 推荐:明确区分优先级 -[Priority(-10)] -public partial class SystemA { } - -[Priority(0)] -public partial class SystemB { } -``` - -### 5. 只在真正需要排序的场景使用 - -不要滥用优先级特性,只在确实需要排序的场景使用: - -```csharp -// ✅ 推荐:需要排序的系统 -[Priority(PriorityGroup.Critical)] -public partial class InputSystem : AbstractSystem { } - -// ❌ 不推荐:不需要排序的工具类 -[Priority(10)] -public static partial class MathHelper // 静态工具类无需优先级 -{ - public static int Add(int a, int b) => a + b; -} -``` - -## 高级场景 - -### 泛型类支持 - -Priority 特性支持泛型类: - -```csharp -[Priority(20)] -public partial class GenericSystem : ISystem -{ - public void Init() - { - // 泛型系统的初始化 - } -} -``` - -### 与其他特性组合 - -Priority 可以与其他源代码生成器特性组合使用: - -```csharp -using GFramework.Core.SourceGenerators.Abstractions.Bases; -using GFramework.Core.SourceGenerators.Abstractions.Logging; -using GFramework.Core.SourceGenerators.Abstractions.Rule; - -[Priority(PriorityGroup.High)] -[Log] -[ContextAware] -public partial class HighPrioritySystem : AbstractSystem -{ - protected override void OnInit() - { - Logger.Info("High priority system initialized"); - } -} -``` - -### 运行时优先级查询 - -可以在运行时查询优先级值: - -```csharp -public void ProcessSystems() -{ - var systems = this.GetAllByPriority(); - - foreach (var system in systems) - { - if (system is IPrioritized prioritized) - { - Console.WriteLine($"System: {system.GetType().Name}, Priority: {prioritized.Priority}"); - } - - system.Init(); - } -} -``` - -## 常见问题 - -### Q: 优先级值可以是任意整数吗? - -**A**: 是的,优先级值可以是任何 `int` 类型的值。推荐使用 `PriorityGroup` 预定义常量或在项目中定义自己的优先级常量。 - -### Q: 多个类有相同优先级会怎样? - -**A**: 当多个类有相同的优先级值时,它们的相对顺序是不确定的。建议为每个类设置不同的优先级值,或在文档中明确说明相同优先级类的执行顺序不保证。 - -### Q: 可以在运行时改变优先级吗? - -**A**: 不可以。`Priority` 属性是只读的,值在编译时确定。如果需要运行时改变优先级,应手动实现 `IPrioritized` 接口。 - -```csharp -public class DynamicPrioritySystem : IPrioritized -{ - private int _priority; - - public int Priority => _priority; - - public void SetPriority(int value) - { - _priority = value; - } -} -``` - -### Q: 优先级支持继承吗? - -**A**: `[Priority]` 特性标记为 `Inherited = false`,不会被子类继承。每个子类需要独立标记优先级。 - -```csharp -[Priority(10)] -public partial class BaseSystem { } - -public partial class DerivedSystem : BaseSystem // 不会继承 Priority = 10 -{ - // 需要重新标记 - // [Priority(20)] -} -``` - -### Q: 如何在不使用特性的情况下实现优先级? - -**A**: 可以手动实现 `IPrioritized` 接口: - -```csharp -public class ManualPrioritySystem : IPrioritized -{ - public int Priority => 10; - - // 手动实现提供更大的灵活性 - public int Priority => _config.Enabled ? 10 : 100; -} -``` - -### Q: 负数优先级和正数优先级的区别是什么? - -**A**: 优先级值用于排序,通常值越小优先级越高: - -- 负数(-100):高优先级,最先执行 -- 零(0):默认优先级 -- 正数(100):低优先级,最后执行 - -具体含义取决于使用场景,但推荐遵循 `PriorityGroup` 定义的语义。 - -## 实际应用示例 - -### 游戏系统架构 - -完整的游戏系统初始化示例: - -```csharp -using GFramework.Core.Abstractions.Systems; -using GFramework.Core.SourceGenerators.Abstractions.Bases; -using GFramework.Core.Abstractions.Bases; - -// 输入系统(最先初始化) -[Priority(PriorityGroup.Critical)] -[Log] -public partial class InputSystem : AbstractSystem -{ - protected override void OnInit() - { - Logger.Info("Input system initialized"); - } -} - -// 物理系统 -[Priority(PriorityGroup.High)] -[Log] -public partial class PhysicsSystem : AbstractSystem -{ - protected override void OnInit() - { - Logger.Info("Physics system initialized"); - } -} - -// 游戏逻辑系统 -[Priority(PriorityGroup.Normal)] -[Log] -[ContextAware] -public partial class GameplaySystem : AbstractSystem -{ - protected override void OnInit() - { - Logger.Info("Gameplay system initialized"); - } -} - -// 音频系统 -[Priority(PriorityGroup.Low)] -[Log] -public partial class AudioSystem : AbstractSystem -{ - protected override void OnInit() - { - Logger.Info("Audio system initialized"); - } -} - -// 清理系统(最后执行) -[Priority(PriorityGroup.Deferred)] -[Log] -public partial class CleanupSystem : AbstractSystem -{ - protected override void OnInit() - { - Logger.Info("Cleanup system initialized"); - } -} -``` - -在架构中初始化: - -```csharp -public class GameArchitecture : Architecture -{ - protected override async Task InitAsync() - { - // 获取所有系统并按优先级排序 - var systems = this.GetAllByPriority(); - - foreach (var system in systems) - { - await system.InitAsync(); - } - } -} -``` - -## 相关文档 - -- [Source Generators 概述](./index.md) -- [ContextAware 生成器](./context-aware-generator.md) -- [系统初始化](../core/system.md) +1. [context-aware-generator.md](./context-aware-generator.md) +2. [context-get-generator.md](./context-get-generator.md) +3. [../core/index.md](../core/index.md) +4. `GFramework.Core.SourceGenerators/README.md`