mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-08 09:34:30 +08:00
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:
parent
da707c7b4f
commit
48e45787f3
@ -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/`,避免默认启动入口再次膨胀
|
||||
|
||||
@ -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,避免默认入口继续膨胀
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user