docs(source-generators): 收口上下文与优先级生成器文档

- 重写 ContextAware 与 Priority 专题页,按当前生成成员、priority-aware API 和兼容边界说明使用方式\n- 更新 documentation-governance-and-refresh 的 tracking 与 trace,记录 RP-007 与后续 Godot 生成器核对重点\n- 验证 docs 站点构建通过
This commit is contained in:
GeWuYou 2026-04-21 16:26:13 +08:00
parent da707c7b4f
commit 48e45787f3
4 changed files with 344 additions and 1045 deletions

View File

@ -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<T>()` / `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/`,避免默认启动入口再次膨胀

View File

@ -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<T>()``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避免默认入口继续膨胀

View File

@ -1,403 +1,198 @@
---
title: ContextAware 生成器
description: 说明 [ContextAware] 当前会生成什么、何时使用、与 ContextAwareBase 的边界以及测试场景。
---
# ContextAware 生成器
> 自动实现 IContextAware 接口,提供架构上下文访问能力
`[ContextAware]``GFramework.Core.SourceGenerators` 中最常用的一类生成器。它的职责很明确:
## 概述
- 为当前类型自动补齐 `IContextAware`
- 提供可复用的上下文懒加载入口
- 让类型可以直接使用 `this.GetSystem<T>()``this.GetModel<T>()``this.GetUtility<T>()` 等扩展方法
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<PlayerModel>();
var combatSystem = this.GetSystem<CombatSystem>();
var playerModel = this.GetModel<IPlayerModel>();
var combatSystem = this.GetSystem<ICombatSystem>();
this.SendEvent(new PlayerInitializedEvent());
}
public void Attack(Enemy target)
{
var damage = this.GetUtility<DamageCalculator>().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
// <auto-generated/>
#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;
/// <summary>
/// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider
/// </summary>
protected global::GFramework.Core.Abstractions.Architecture.IArchitectureContext Context
{
get
{
if (_context == null)
{
_contextProvider ??= new global::GFramework.Core.Architecture.GameContextProvider();
_context = _contextProvider.GetContext();
}
return _context;
}
}
/// <summary>
/// 配置上下文提供者(用于测试或多架构场景)
/// </summary>
/// <param name="provider">上下文提供者实例</param>
public static void SetContextProvider(global::GFramework.Core.Abstractions.Architecture.IArchitectureContextProvider provider)
{
_contextProvider = provider;
}
/// <summary>
/// 重置上下文提供者为默认值(用于测试清理)
/// </summary>
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<string, IArchitecture> _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<SaveSystem>();
var uiSystem = this.GetSystem<UISystem>();
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<PlayerModel>();
var combatSystem = this.GetSystem<CombatSystem>();
}
}
```
**说明**
- `IController` 是标记接口,标识这是一个控制器
- `[ContextAware]` 提供架构访问能力
- 两者配合使用是推荐的模式
### 何时继承 ContextAwareBase
如果类需要更多框架功能(如生命周期管理),应继承 `ContextAwareBase`
```csharp
// 推荐:需要生命周期管理时继承基类
public class PlayerModel : AbstractModel
{
// AbstractModel 已经继承了 ContextAwareBase
protected override void OnInit()
{
var config = this.GetUtility<ConfigLoader>().Load<PlayerConfig>();
}
}
// 推荐:简单组件使用属性
[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<SomeModel>(); // 可能为 null
}
- `ResetContextProvider()` 只会重置共享 provider不会清除已创建实例上的 `_context`
- 如果测试要复用同一实例并切换上下文,应该显式调用 `((IContextAware)instance).SetContext(...)`
// ✅ 正确:在初始化方法中访问
public void Initialize()
{
var model = this.GetModel<SomeModel>(); // 安全
}
}
```
## 诊断与约束
### 4. 优先使用 Context 属性而非接口方法
当前文档里最值得记住的约束只有这些:
```csharp
[ContextAware]
public partial class MyController
{
public void DoSomething()
{
// ✅ 推荐:使用扩展方法
var model = this.GetModel<SomeModel>();
- 非 `class` 会触发 `GF_Rule_001`
- 非 `partial` 不会生成实现,并会触发公共 partial 约束诊断
- 嵌套、字段注入等其他错误通常由对应的 Context Get 生成器和其诊断补充报告
// ❌ 不推荐:显式调用接口方法
var context = ((IContextAware)this).GetContext();
var model2 = context.GetModel<SomeModel>();
}
}
```
## 与旧写法的边界
## 诊断信息
下面这些旧说法已经不够准确:
生成器会在以下情况报告编译错误:
- “`[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`

View File

@ -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
// <auto-generated/>
#nullable enable
namespace YourNamespace;
partial class MySystem : global::GFramework.Core.Abstractions.Bases.IPrioritized
{
/// <summary>
/// 获取优先级值: 10
/// </summary>
public int Priority => 10;
}
```
### 使用生成的优先级
生成的 `Priority` 属性可用于排序:
```csharp
using GFramework.Core.Abstractions.Bases;
// 获取所有实现了 IPrioritized 的系统
var systems = new List<IPrioritized> { 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<IMyHandler>();
```
### `IArchitectureContext`
当前推荐按组件类别使用这些 API
- `GetServicesByPriority<TService>()`
- `GetSystemsByPriority<TSystem>()`
- `GetModelsByPriority<TModel>()`
- `GetUtilitiesByPriority<TUtility>()`
### `IContextAware` 扩展方法
如果你已经在 `[ContextAware]` 类型或 `ContextAwareBase` 派生类型里,直接用:
- `this.GetServicesByPriority<TService>()`
- `this.GetSystemsByPriority<TSystem>()`
- `this.GetModelsByPriority<TModel>()`
- `this.GetUtilitiesByPriority<TUtility>()`
这比旧文档里反复出现的 `this.GetAllByPriority<T>()` 更贴近当前公开扩展方法。
## 最小接入示例
### 系统排序
```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<ISystem>();
var systems = this.GetSystemsByPriority<ISystem>();
foreach (var system in systems)
{
system.Init();
}
}
}
```
### 事件处理器优先级
控制事件处理器的执行顺序:
```csharp
using GFramework.Core.Abstractions.Events;
// 关键事件处理器最先执行
[Priority(PriorityGroup.Critical)]
public partial class CriticalEventHandler : IEventHandler<CriticalEvent>
{
public void Handle(CriticalEvent e)
{
// 处理关键事件
}
}
// 普通处理器在中间执行
[Priority(PriorityGroup.Normal)]
public partial class NormalEventHandler : IEventHandler<CriticalEvent>
{
public void Handle(CriticalEvent e)
{
// 处理普通逻辑
}
}
// 日志记录器最后执行
[Priority(PriorityGroup.Deferred)]
public partial class EventLogger : IEventHandler<CriticalEvent>
{
public void Handle(CriticalEvent e)
{
// 记录日志
}
}
```
事件总线按优先级调用处理器:
```csharp
public class EventBus : IEventBus
{
public void Send<TEvent>(TEvent e) where TEvent : IEvent
{
// 获取所有处理器并按优先级排序
var handlers = this.GetAllByPriority<IEventHandler<TEvent>>();
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<ISaveMigration>();
```
## `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<T>()` 而非 `GetAll<T>()` 的场景:
- `GF_Priority_001`
- 只能标在 `class`
- `GF_Priority_002`
- 目标类型已经手写实现 `IPrioritized`
- `GF_Priority_003`
- 类型必须是 `partial`
- `GF_Priority_004`
- 特性值缺失或无效
- `GF_Priority_005`
- 不支持嵌套类
**错误示例**
对文档而言,最关键的结论是
```csharp
// ❌ 不推荐:可能未按优先级排序
var systems = context.GetAll<ISystem>();
```
- `partial` 是强约束
- 顶层类是强约束
- 手写实现与生成实现只能二选一
**正确示例**
## 与旧写法的边界
```csharp
// ✅ 推荐:确保按优先级排序
var systems = context.GetAllByPriority<ISystem>();
```
下面这些旧写法或旧表述已经不再适合作为默认指导:
### 分析器规则
- 在 `IContextAware` 类型里统一写 `this.GetAllByPriority<T>()`
- 继续用 `system.Init()` 作为系统初始化示例
- 把 `[Priority]` 写成“标了就会自动改变执行顺序”
当满足以下条件时,分析器会报告 `GF_Priority_Usage_001` 诊断:
前更准确的理解是
1. 类型实现了 `IPrioritized` 接口
2. 使用了 `GetAll<T>()` 方法
3. 建议改用 `GetAllByPriority<T>()` 方法
- `[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
/// <summary>
/// 输入系统,优先级 -100需要最先初始化以接收输入事件
/// </summary>
[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<T> : 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<ISystem>();
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<ISystem>();
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`